@blamejs/core 0.15.12 → 0.15.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (365) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/index.js +2 -0
  3. package/lib/a2a-tasks.js +45 -29
  4. package/lib/acme.js +6 -5
  5. package/lib/agent-event-bus.js +18 -4
  6. package/lib/agent-idempotency.js +7 -6
  7. package/lib/agent-orchestrator.js +2 -5
  8. package/lib/agent-saga.js +3 -5
  9. package/lib/agent-snapshot.js +32 -2
  10. package/lib/agent-tenant.js +2 -5
  11. package/lib/ai-adverse-decision.js +2 -15
  12. package/lib/ai-aedt-bias-audit.js +2 -1
  13. package/lib/ai-capability.js +1 -6
  14. package/lib/ai-content-detect.js +1 -3
  15. package/lib/ai-dp.js +1 -5
  16. package/lib/ai-frontier-protocol.js +1 -1
  17. package/lib/ai-input.js +2 -2
  18. package/lib/ai-model-manifest.js +1 -1
  19. package/lib/ai-output.js +16 -7
  20. package/lib/ai-pref.js +3 -8
  21. package/lib/ai-quota.js +3 -14
  22. package/lib/api-key.js +37 -28
  23. package/lib/api-snapshot.js +4 -1
  24. package/lib/app-shutdown.js +7 -1
  25. package/lib/archive-adapters.js +2 -4
  26. package/lib/archive-entry-policy.js +32 -0
  27. package/lib/archive-gz.js +9 -0
  28. package/lib/archive-read.js +14 -24
  29. package/lib/archive-tar-read.js +56 -24
  30. package/lib/archive.js +6 -12
  31. package/lib/arg-parser.js +7 -6
  32. package/lib/asn1-der.js +70 -22
  33. package/lib/asyncapi-traits.js +2 -6
  34. package/lib/atomic-file.js +312 -33
  35. package/lib/audit-chain.js +183 -54
  36. package/lib/audit-daily-review.js +36 -16
  37. package/lib/audit-emit.js +82 -0
  38. package/lib/audit-sign.js +258 -0
  39. package/lib/audit-tools.js +108 -23
  40. package/lib/audit.js +91 -2
  41. package/lib/auth/access-lock.js +7 -29
  42. package/lib/auth/bot-challenge.js +1 -1
  43. package/lib/auth/ciba.js +43 -4
  44. package/lib/auth/dpop.js +29 -36
  45. package/lib/auth/fido-mds3.js +14 -16
  46. package/lib/auth/jwt-external.js +26 -8
  47. package/lib/auth/jwt.js +15 -17
  48. package/lib/auth/lockout.js +24 -27
  49. package/lib/auth/oauth.js +67 -45
  50. package/lib/auth/oid4vci.js +55 -32
  51. package/lib/auth/oid4vp.js +3 -2
  52. package/lib/auth/openid-federation.js +6 -6
  53. package/lib/auth/passkey.js +4 -4
  54. package/lib/auth/password.js +8 -2
  55. package/lib/auth/saml.js +70 -40
  56. package/lib/auth/sd-jwt-vc-holder.js +2 -14
  57. package/lib/auth/sd-jwt-vc-issuer.js +2 -14
  58. package/lib/auth/sd-jwt-vc.js +25 -5
  59. package/lib/auth/status-list.js +21 -9
  60. package/lib/auth/step-up.js +15 -13
  61. package/lib/auth-bot-challenge.js +27 -39
  62. package/lib/backup/bundle.js +18 -20
  63. package/lib/backup/crypto.js +23 -8
  64. package/lib/backup/index.js +55 -67
  65. package/lib/backup/manifest.js +7 -1
  66. package/lib/bounded-map.js +112 -1
  67. package/lib/breach-deadline.js +1 -11
  68. package/lib/break-glass.js +41 -22
  69. package/lib/cache.js +7 -18
  70. package/lib/cbor.js +36 -12
  71. package/lib/cdn-cache-control.js +15 -12
  72. package/lib/cert.js +8 -13
  73. package/lib/chain-writer.js +162 -47
  74. package/lib/cli.js +10 -2
  75. package/lib/client-hints.js +7 -9
  76. package/lib/cloud-events.js +43 -33
  77. package/lib/cluster-storage.js +9 -41
  78. package/lib/cms-codec.js +2 -1
  79. package/lib/codepoint-class.js +84 -1
  80. package/lib/compliance-ai-act-logging.js +1 -6
  81. package/lib/compliance-ai-act-transparency.js +2 -4
  82. package/lib/compliance-eaa.js +2 -12
  83. package/lib/compliance-sanctions-fetcher.js +21 -28
  84. package/lib/compliance-sanctions.js +11 -21
  85. package/lib/compliance.js +3 -10
  86. package/lib/config-drift.js +24 -20
  87. package/lib/content-credentials.js +11 -8
  88. package/lib/content-digest.js +21 -12
  89. package/lib/cookies.js +15 -13
  90. package/lib/cose.js +39 -11
  91. package/lib/cra-report.js +1 -11
  92. package/lib/crdt.js +2 -1
  93. package/lib/crypto-field.js +34 -33
  94. package/lib/crypto.js +138 -7
  95. package/lib/csp.js +239 -3
  96. package/lib/daemon.js +46 -42
  97. package/lib/data-act.js +46 -13
  98. package/lib/db-file-lifecycle.js +14 -6
  99. package/lib/db-query.js +75 -49
  100. package/lib/db.js +66 -27
  101. package/lib/dbsc.js +5 -6
  102. package/lib/ddl-change-control.js +18 -14
  103. package/lib/deprecate.js +4 -5
  104. package/lib/did.js +6 -2
  105. package/lib/dora.js +43 -13
  106. package/lib/dr-runbook.js +1 -1
  107. package/lib/dsa.js +2 -1
  108. package/lib/dsr.js +30 -20
  109. package/lib/early-hints.js +19 -0
  110. package/lib/eat.js +5 -1
  111. package/lib/external-db-migrate.js +21 -22
  112. package/lib/external-db.js +74 -30
  113. package/lib/fda-21cfr11.js +42 -13
  114. package/lib/fdx.js +22 -18
  115. package/lib/file-upload.js +80 -66
  116. package/lib/flag-evaluation-context.js +5 -8
  117. package/lib/flag-providers.js +6 -2
  118. package/lib/forms.js +1 -1
  119. package/lib/framework-error.js +12 -0
  120. package/lib/framework-schema.js +19 -0
  121. package/lib/fsm.js +7 -12
  122. package/lib/gate-contract.js +911 -39
  123. package/lib/gdpr-ropa.js +22 -22
  124. package/lib/graphql-federation.js +18 -5
  125. package/lib/guard-agent-registry.js +2 -1
  126. package/lib/guard-all.js +3 -2
  127. package/lib/guard-archive.js +9 -30
  128. package/lib/guard-auth.js +23 -80
  129. package/lib/guard-cidr.js +20 -96
  130. package/lib/guard-csv.js +135 -196
  131. package/lib/guard-domain.js +23 -106
  132. package/lib/guard-dsn.js +17 -14
  133. package/lib/guard-email.js +46 -53
  134. package/lib/guard-envelope.js +2 -2
  135. package/lib/guard-event-bus-topic.js +2 -1
  136. package/lib/guard-filename.js +12 -60
  137. package/lib/guard-graphql.js +28 -75
  138. package/lib/guard-html-wcag.js +15 -2
  139. package/lib/guard-html.js +74 -128
  140. package/lib/guard-idempotency-key.js +2 -1
  141. package/lib/guard-image.js +280 -77
  142. package/lib/guard-imap-command.js +9 -10
  143. package/lib/guard-jmap.js +1 -1
  144. package/lib/guard-json.js +101 -109
  145. package/lib/guard-jsonpath.js +20 -88
  146. package/lib/guard-jwt.js +32 -114
  147. package/lib/guard-list-id.js +2 -7
  148. package/lib/guard-list-unsubscribe.js +2 -7
  149. package/lib/guard-mail-compose.js +5 -6
  150. package/lib/guard-mail-move.js +3 -2
  151. package/lib/guard-mail-query.js +5 -3
  152. package/lib/guard-mail-sieve.js +6 -7
  153. package/lib/guard-managesieve-command.js +6 -5
  154. package/lib/guard-markdown.js +76 -110
  155. package/lib/guard-message-id.js +5 -6
  156. package/lib/guard-mime.js +20 -99
  157. package/lib/guard-oauth.js +19 -73
  158. package/lib/guard-pdf.js +65 -72
  159. package/lib/guard-pop3-command.js +13 -14
  160. package/lib/guard-posture-chain.js +2 -1
  161. package/lib/guard-regex.js +24 -99
  162. package/lib/guard-saga-config.js +2 -1
  163. package/lib/guard-shell.js +22 -87
  164. package/lib/guard-smtp-command.js +9 -12
  165. package/lib/guard-sql.js +15 -13
  166. package/lib/guard-stream-args.js +2 -1
  167. package/lib/guard-svg.js +103 -149
  168. package/lib/guard-template.js +23 -80
  169. package/lib/guard-tenant-id.js +2 -1
  170. package/lib/guard-text.js +592 -0
  171. package/lib/guard-time.js +27 -95
  172. package/lib/guard-uuid.js +21 -93
  173. package/lib/guard-xml.js +76 -106
  174. package/lib/guard-yaml.js +24 -60
  175. package/lib/html-balance.js +7 -3
  176. package/lib/http-client-cache.js +5 -12
  177. package/lib/http-client-cookie-jar.js +35 -16
  178. package/lib/http-client.js +233 -74
  179. package/lib/http-message-signature.js +3 -2
  180. package/lib/i18n-messageformat.js +5 -7
  181. package/lib/i18n.js +83 -26
  182. package/lib/iab-tcf.js +3 -2
  183. package/lib/importmap-integrity.js +41 -1
  184. package/lib/inbox.js +8 -8
  185. package/lib/incident-report.js +12 -27
  186. package/lib/ip-utils.js +49 -6
  187. package/lib/jobs.js +3 -2
  188. package/lib/json-patch.js +1 -1
  189. package/lib/json-path.js +24 -3
  190. package/lib/jtd.js +2 -2
  191. package/lib/keychain.js +6 -18
  192. package/lib/legal-hold.js +30 -23
  193. package/lib/log-stream-cloudwatch.js +44 -112
  194. package/lib/log-stream-otlp-grpc.js +17 -14
  195. package/lib/log-stream-otlp.js +16 -80
  196. package/lib/log-stream-webhook.js +20 -92
  197. package/lib/log.js +2 -2
  198. package/lib/mail-agent.js +2 -2
  199. package/lib/mail-arc-sign.js +8 -3
  200. package/lib/mail-arf.js +4 -3
  201. package/lib/mail-auth.js +43 -69
  202. package/lib/mail-bimi.js +35 -55
  203. package/lib/mail-bounce.js +3 -3
  204. package/lib/mail-crypto-pgp.js +3 -2
  205. package/lib/mail-crypto-smime.js +71 -6
  206. package/lib/mail-dav.js +8 -35
  207. package/lib/mail-deploy.js +15 -14
  208. package/lib/mail-dkim.js +19 -38
  209. package/lib/mail-greylist.js +17 -30
  210. package/lib/mail-helo.js +4 -7
  211. package/lib/mail-journal.js +13 -9
  212. package/lib/mail-mdn.js +12 -7
  213. package/lib/mail-rbl.js +7 -12
  214. package/lib/mail-scan.js +5 -7
  215. package/lib/mail-send-deliver.js +33 -20
  216. package/lib/mail-server-imap.js +32 -87
  217. package/lib/mail-server-jmap.js +39 -52
  218. package/lib/mail-server-managesieve.js +29 -83
  219. package/lib/mail-server-mx.js +33 -74
  220. package/lib/mail-server-net.js +177 -0
  221. package/lib/mail-server-pop3.js +30 -83
  222. package/lib/mail-server-rate-limit.js +30 -6
  223. package/lib/mail-server-registry.js +1 -1
  224. package/lib/mail-server-submission.js +34 -73
  225. package/lib/mail-server-tls.js +98 -2
  226. package/lib/mail-spam-score.js +9 -12
  227. package/lib/mail-store-fts.js +1 -1
  228. package/lib/mail-store.js +3 -2
  229. package/lib/mail.js +28 -13
  230. package/lib/markup-escape.js +31 -0
  231. package/lib/markup-tokenizer.js +78 -0
  232. package/lib/mcp-tool-registry.js +26 -23
  233. package/lib/mcp.js +7 -5
  234. package/lib/mdoc.js +36 -14
  235. package/lib/metrics.js +46 -33
  236. package/lib/middleware/age-gate.js +3 -23
  237. package/lib/middleware/api-encrypt.js +13 -24
  238. package/lib/middleware/assetlinks.js +2 -7
  239. package/lib/middleware/bearer-auth.js +2 -1
  240. package/lib/middleware/body-parser.js +41 -52
  241. package/lib/middleware/bot-disclose.js +7 -10
  242. package/lib/middleware/bot-guard.js +28 -28
  243. package/lib/middleware/clear-site-data.js +5 -1
  244. package/lib/middleware/compression.js +9 -0
  245. package/lib/middleware/cors.js +32 -23
  246. package/lib/middleware/csrf-protect.js +73 -23
  247. package/lib/middleware/daily-byte-quota.js +12 -30
  248. package/lib/middleware/deny-response.js +19 -1
  249. package/lib/middleware/fetch-metadata.js +35 -4
  250. package/lib/middleware/idempotency-key.js +4 -20
  251. package/lib/middleware/network-allowlist.js +61 -30
  252. package/lib/middleware/rate-limit.js +45 -44
  253. package/lib/middleware/scim-server.js +5 -3
  254. package/lib/middleware/security-headers.js +33 -7
  255. package/lib/middleware/security-txt.js +2 -6
  256. package/lib/middleware/speculation-rules.js +6 -3
  257. package/lib/middleware/tus-upload.js +14 -29
  258. package/lib/middleware/web-app-manifest.js +2 -7
  259. package/lib/migrations.js +4 -13
  260. package/lib/mime-parse.js +34 -17
  261. package/lib/module-loader.js +44 -0
  262. package/lib/money.js +105 -0
  263. package/lib/mtls-ca.js +125 -41
  264. package/lib/network-byte-quota.js +4 -18
  265. package/lib/network-dane.js +8 -6
  266. package/lib/network-dns-resolver.js +98 -7
  267. package/lib/network-dns.js +9 -2
  268. package/lib/network-dnssec.js +18 -17
  269. package/lib/network-heartbeat.js +3 -2
  270. package/lib/network-smtp-policy.js +52 -46
  271. package/lib/network-tls.js +67 -47
  272. package/lib/network-tsig.js +2 -2
  273. package/lib/nis2-report.js +2 -12
  274. package/lib/nist-crosswalk.js +1 -1
  275. package/lib/nonce-store.js +81 -3
  276. package/lib/ntp-check.js +28 -0
  277. package/lib/numeric-bounds.js +31 -8
  278. package/lib/object-store/azure-blob-bucket-ops.js +2 -6
  279. package/lib/object-store/azure-blob.js +6 -47
  280. package/lib/object-store/gcs-bucket-ops.js +4 -2
  281. package/lib/object-store/gcs.js +14 -75
  282. package/lib/object-store/http-put.js +2 -7
  283. package/lib/object-store/http-request.js +157 -0
  284. package/lib/object-store/local.js +37 -17
  285. package/lib/object-store/sigv4-bucket-ops.js +2 -1
  286. package/lib/object-store/sigv4.js +10 -79
  287. package/lib/observability-otlp-exporter.js +29 -29
  288. package/lib/observability.js +95 -0
  289. package/lib/openapi-paths-builder.js +7 -3
  290. package/lib/otel-export.js +7 -10
  291. package/lib/outbox.js +54 -41
  292. package/lib/pagination.js +11 -15
  293. package/lib/parsers/safe-env.js +5 -4
  294. package/lib/parsers/safe-ini.js +10 -4
  295. package/lib/parsers/safe-toml.js +11 -9
  296. package/lib/parsers/safe-xml.js +3 -3
  297. package/lib/parsers/safe-yaml.js +28 -9
  298. package/lib/pick.js +63 -5
  299. package/lib/pipl-cn.js +69 -59
  300. package/lib/privacy-pass.js +8 -6
  301. package/lib/problem-details.js +2 -5
  302. package/lib/pubsub.js +4 -8
  303. package/lib/queue-local.js +10 -3
  304. package/lib/redact.js +9 -4
  305. package/lib/render.js +4 -2
  306. package/lib/request-helpers.js +334 -29
  307. package/lib/resource-access-lock.js +3 -3
  308. package/lib/restore-bundle.js +46 -18
  309. package/lib/restore-rollback.js +10 -4
  310. package/lib/restore.js +24 -22
  311. package/lib/retention.js +23 -9
  312. package/lib/router.js +17 -4
  313. package/lib/safe-async.js +457 -0
  314. package/lib/safe-buffer.js +137 -6
  315. package/lib/safe-decompress.js +2 -1
  316. package/lib/safe-dns.js +2 -1
  317. package/lib/safe-ical.js +14 -28
  318. package/lib/safe-icap.js +1 -1
  319. package/lib/safe-json.js +51 -8
  320. package/lib/safe-jsonpath.js +6 -5
  321. package/lib/safe-mime.js +25 -29
  322. package/lib/safe-redirect.js +2 -5
  323. package/lib/safe-schema.js +7 -6
  324. package/lib/safe-sieve.js +1 -1
  325. package/lib/safe-sql.js +126 -0
  326. package/lib/safe-vcard.js +13 -27
  327. package/lib/sandbox-worker.js +15 -2
  328. package/lib/sandbox.js +4 -3
  329. package/lib/scheduler.js +22 -13
  330. package/lib/sec-cyber.js +82 -37
  331. package/lib/seeders.js +4 -11
  332. package/lib/self-update-standalone-verifier.js +16 -0
  333. package/lib/self-update.js +92 -69
  334. package/lib/session-device-binding.js +112 -66
  335. package/lib/session.js +219 -7
  336. package/lib/sql.js +228 -81
  337. package/lib/sse.js +6 -10
  338. package/lib/static.js +201 -111
  339. package/lib/storage.js +4 -2
  340. package/lib/structured-fields.js +275 -16
  341. package/lib/template.js +7 -5
  342. package/lib/tenant-quota.js +53 -38
  343. package/lib/tsa.js +16 -17
  344. package/lib/validate-opts.js +154 -8
  345. package/lib/vault/index.js +5 -0
  346. package/lib/vault/passphrase-ops.js +22 -26
  347. package/lib/vault/passphrase-source.js +8 -3
  348. package/lib/vault/rotate.js +13 -18
  349. package/lib/vault/seal-pem-file.js +34 -49
  350. package/lib/vault-aad.js +3 -2
  351. package/lib/vc.js +3 -8
  352. package/lib/vendor/MANIFEST.json +10 -10
  353. package/lib/vendor/public-suffix-list.dat +23 -10
  354. package/lib/vendor/public-suffix-list.data.js +5498 -5494
  355. package/lib/vex.js +35 -13
  356. package/lib/web-push-vapid.js +23 -20
  357. package/lib/webhook-dispatcher.js +706 -0
  358. package/lib/webhook.js +59 -19
  359. package/lib/websocket-channels.js +9 -7
  360. package/lib/websocket.js +8 -4
  361. package/lib/worm.js +3 -4
  362. package/lib/ws-client.js +65 -56
  363. package/lib/x509-chain.js +44 -0
  364. package/package.json +1 -1
  365. package/sbom.cdx.json +6 -6
package/lib/mtls-ca.js CHANGED
@@ -58,6 +58,9 @@ var nodeCrypto = require("node:crypto");
58
58
  var atomicFile = require("./atomic-file");
59
59
  var C = require("./constants");
60
60
  var lazyRequire = require("./lazy-require");
61
+ // Lazy — the SHA3-512 fingerprint surfaced from issuance must match the one
62
+ // the require-mtls gate pins (b.crypto.sha3Hash(certPem)).
63
+ var bCrypto = lazyRequire(function () { return require("./crypto"); });
61
64
  var { boot } = require("./log");
62
65
  var safeBuffer = require("./safe-buffer");
63
66
  var safeJson = require("./safe-json");
@@ -193,7 +196,7 @@ function create(opts) {
193
196
  opts = opts || {};
194
197
  validateOpts(opts, [
195
198
  "dataDir", "paths", "vault",
196
- "caKeySealedMode", "generation", "engine",
199
+ "caKeySealedMode", "generation", "engine", "revocationStore",
197
200
  ], "b.mtlsCa");
198
201
  validateOpts.requireNonEmptyString(opts.dataDir, "mtlsCa.create: opts.dataDir", MtlsCaError, "mtls-ca/no-datadir");
199
202
  // Auto-create the dataDir with restrictive perms (CA keys live here).
@@ -208,7 +211,7 @@ function create(opts) {
208
211
  var paths = _resolvePaths(opts.dataDir, opts.paths);
209
212
  var vault = opts.vault || null;
210
213
  var caKeySealedMode = (opts.caKeySealedMode || "required").toLowerCase();
211
- if (!VALID_SEAL_MODES[caKeySealedMode]) {
214
+ if (!Object.prototype.hasOwnProperty.call(VALID_SEAL_MODES, caKeySealedMode)) {
212
215
  throw new MtlsCaError("mtls-ca/bad-mode",
213
216
  "caKeySealedMode must be 'required' or 'disabled' " +
214
217
  "(legacy 'auto' was removed — it defaulted to plaintext-on-disk)");
@@ -243,7 +246,7 @@ function create(opts) {
243
246
  current: generation,
244
247
  };
245
248
  }
246
- var pem = nodeFs.readFileSync(paths.caCert);
249
+ var pem = atomicFile.fdSafeReadSync(paths.caCert, { maxBytes: C.BYTES.mib(1) });
247
250
  var gen = parseGeneration(pem);
248
251
  return {
249
252
  exists: true,
@@ -269,7 +272,9 @@ function create(opts) {
269
272
  "CA_KEY_SEALED='required' but " + paths.caKeySealed + " does not exist");
270
273
  }
271
274
  _requireVault("sealed CA key load");
272
- var sealedBytes = nodeFs.readFileSync(paths.caKeySealed, "utf8").trim();
275
+ // Cap + fd-bound CA-private-key read. NO refuseSymlink: caKeySealed may be
276
+ // an operator-absolute path on a k8s/KMS secret volume that symlinks it.
277
+ var sealedBytes = atomicFile.fdSafeReadSync(paths.caKeySealed, { maxBytes: C.BYTES.kib(64), encoding: "utf8" }).trim();
273
278
  var pem = vault.unseal(sealedBytes);
274
279
  if (!pem) {
275
280
  throw new MtlsCaError("mtls-ca/unseal-failed",
@@ -282,7 +287,9 @@ function create(opts) {
282
287
  throw new MtlsCaError("mtls-ca/plain-required",
283
288
  "caKeySealedMode='disabled' but " + paths.caKey + " does not exist");
284
289
  }
285
- return nodeFs.readFileSync(paths.caKey);
290
+ // Cap + fd-bound plaintext CA-private-key read (disabled mode = dev opt-out).
291
+ // NO refuseSymlink (operator-absolute path may symlink).
292
+ return atomicFile.fdSafeReadSync(paths.caKey, { maxBytes: C.BYTES.kib(64) });
286
293
  }
287
294
 
288
295
  function loadCert() {
@@ -290,7 +297,7 @@ function create(opts) {
290
297
  throw new MtlsCaError("mtls-ca/missing-cert",
291
298
  "no CA cert on disk at " + paths.caCert);
292
299
  }
293
- return nodeFs.readFileSync(paths.caCert);
300
+ return atomicFile.fdSafeReadSync(paths.caCert, { maxBytes: C.BYTES.mib(1) });
294
301
  }
295
302
 
296
303
  // Atomic commit: write .tmp + atomic rename for both key and cert.
@@ -370,6 +377,29 @@ function create(opts) {
370
377
  return fresh;
371
378
  }
372
379
 
380
+ // Recover the issued certificate's identity from its PEM so issuance and
381
+ // any later revocation/indexing share identifiers without a round-trip back
382
+ // through an X.509 parser. serialNumber is normalized to the same hex form
383
+ // revoke() stores; fingerprint is the SHA3-512 the require-mtls gate pins.
384
+ function _certIdentity(certPem) {
385
+ // serialNumber comes from an X.509 parse, which is best-effort: a custom
386
+ // engine (or a test double) may return a cert in a shape this Node build
387
+ // cannot parse as X.509. The fingerprint is a hash of the returned bytes,
388
+ // so it is always available and is what the require-mtls gate pins —
389
+ // revoke()/isRevoked() by fingerprint keep working even when the serial
390
+ // can't be recovered. Never let optional identity enrichment crash issuance.
391
+ var serialNumber = null;
392
+ try {
393
+ serialNumber = _normalizeSerial(new nodeCrypto.X509Certificate(certPem).serialNumber);
394
+ } catch (_e) {
395
+ serialNumber = null;
396
+ }
397
+ return {
398
+ serialNumber: serialNumber,
399
+ fingerprint: bCrypto().sha3Hash(certPem),
400
+ };
401
+ }
402
+
373
403
  async function generateClientCert(opts2) {
374
404
  opts2 = opts2 || {};
375
405
  var ca = await initCA();
@@ -379,7 +409,10 @@ function create(opts) {
379
409
  throw new MtlsCaError("mtls-ca/bad-engine-output",
380
410
  "engine.signClientCert must return { cert, key, ca?, issuedAt?, expiresAt? }");
381
411
  }
382
- return result;
412
+ // Surface the issued serial + fingerprint so the caller can track/revoke
413
+ // the cert by the same identifiers without re-parsing the PEM.
414
+ var id = _certIdentity(result.cert);
415
+ return Object.assign({}, result, { serialNumber: id.serialNumber, fingerprint: id.fingerprint });
383
416
  }
384
417
 
385
418
  async function generateClientP12(opts2) {
@@ -395,32 +428,63 @@ function create(opts) {
395
428
  throw new MtlsCaError("mtls-ca/bad-engine-output",
396
429
  "engine.packageP12 must return { p12: Buffer, certPem, issuedAt, expiresAt }");
397
430
  }
431
+ if (typeof result.certPem === "string") {
432
+ var id12 = _certIdentity(result.certPem);
433
+ return Object.assign({}, result, { serialNumber: id12.serialNumber, fingerprint: id12.fingerprint });
434
+ }
398
435
  return result;
399
436
  }
400
437
 
401
438
  // ---- Revocation registry + CRL ----
402
439
 
403
- function _loadRevocations() {
404
- if (!nodeFs.existsSync(paths.revocations)) return { revocations: [] };
405
- try {
406
- // safeJson.parse caps depth + size + protects against
407
- // proto-pollution; the revocation file is under the operator's
408
- // dataDir but a tampered or truncated file shouldn't be able to
409
- // corrupt the rotator process.
410
- var json = safeJson.parse(nodeFs.readFileSync(paths.revocations, "utf8"),
411
- { maxBytes: C.BYTES.mib(16) });
412
- if (!json || !Array.isArray(json.revocations)) return { revocations: [] };
413
- return json;
414
- } catch (e) {
415
- throw new MtlsCaError("mtls-ca/revocation-corrupt",
416
- "could not parse " + paths.revocations + ": " +
417
- ((e && e.message) || String(e)));
440
+ // Revocation entries are read + written through a store so the registry can
441
+ // live somewhere other than the default plaintext revocations.json e.g. an
442
+ // operator-supplied encrypted / clustered store (the bring-your-own-store
443
+ // precedent b.queue's config.db set). Contract (sync):
444
+ // list() -> array of revocation entries
445
+ // add(entry) -> append one entry (the caller has already deduped)
446
+ function _defaultFileStore() {
447
+ function _list() {
448
+ if (!nodeFs.existsSync(paths.revocations)) return [];
449
+ try {
450
+ // safeJson.parse caps depth + size + protects against
451
+ // proto-pollution; a tampered or truncated file shouldn't be able to
452
+ // corrupt the rotator process.
453
+ var json = safeJson.parse(atomicFile.fdSafeReadSync(paths.revocations, { maxBytes: C.BYTES.mib(16), encoding: "utf8" }),
454
+ { maxBytes: C.BYTES.mib(16) });
455
+ return (json && Array.isArray(json.revocations)) ? json.revocations : [];
456
+ } catch (e) {
457
+ throw new MtlsCaError("mtls-ca/revocation-corrupt",
458
+ "could not parse " + paths.revocations + ": " + ((e && e.message) || String(e)));
459
+ }
418
460
  }
461
+ return {
462
+ list: _list,
463
+ add: function (entry) {
464
+ var entries = _list();
465
+ entries.push(entry);
466
+ atomicFile.writeSync(paths.revocations,
467
+ JSON.stringify({ revocations: entries }, null, 2) + "\n", { mode: 0o600 });
468
+ },
469
+ };
419
470
  }
471
+ var revocationStore = opts.revocationStore || _defaultFileStore();
472
+ validateOpts.requireMethods(revocationStore, ["list", "add"],
473
+ "opts.revocationStore", MtlsCaError, "mtls-ca/bad-revocation-store");
420
474
 
421
- function _saveRevocations(state) {
422
- atomicFile.writeSync(paths.revocations,
423
- JSON.stringify(state, null, 2) + "\n", { mode: 0o600 });
475
+ // A fingerprint is the SHA3-512 hex the require-mtls gate pins. Normalize it
476
+ // like a serial (strip 0x / separators / whitespace, lowercase, hex-validate)
477
+ // so a consumer can revoke by the same value the gate compares against.
478
+ function _normalizeFingerprint(fp) {
479
+ if (!fp || typeof fp !== "string") {
480
+ throw new MtlsCaError("mtls-ca/bad-fingerprint", "fingerprint must be a non-empty string");
481
+ }
482
+ var stripped = fp.replace(/^0x/i, "").replace(/[:\-\s]/g, "");
483
+ if (!safeBuffer.isHex(stripped)) {
484
+ throw new MtlsCaError("mtls-ca/bad-fingerprint",
485
+ "fingerprint contains non-hex characters: " + JSON.stringify(fp));
486
+ }
487
+ return stripped.toLowerCase();
424
488
  }
425
489
 
426
490
  function _normalizeSerial(s) {
@@ -466,46 +530,66 @@ function create(opts) {
466
530
  "aACompromise": 10,
467
531
  };
468
532
 
469
- function revoke(serialNumber, opts3) {
470
- var serial = _normalizeSerial(serialNumber);
533
+ function revoke(idOrOpts, opts3) {
534
+ // Accept either revoke(serialString, { reason, fingerprint }) — the
535
+ // backward-compatible serial-keyed form — or revoke({ serial?,
536
+ // fingerprint?, reason? }). The require-mtls gate denies by fingerprint,
537
+ // so a fingerprint-indexed consumer can revoke by the same value it pins
538
+ // on; serial-keyed behavior stays the default. At least one key required.
539
+ var spec = (idOrOpts && typeof idOrOpts === "object") ? idOrOpts : null;
471
540
  opts3 = opts3 || {};
472
- var reasonName = opts3.reason || "unspecified";
541
+ var serialIn = spec ? spec.serial : idOrOpts;
542
+ var fingerprintIn = spec ? spec.fingerprint : opts3.fingerprint;
543
+ var reasonName = (spec ? spec.reason : opts3.reason) || "unspecified";
544
+
545
+ var serial = (serialIn !== undefined && serialIn !== null) ? _normalizeSerial(serialIn) : null;
546
+ var fingerprint = (fingerprintIn !== undefined && fingerprintIn !== null)
547
+ ? _normalizeFingerprint(fingerprintIn) : null;
548
+ if (!serial && !fingerprint) {
549
+ throw new MtlsCaError("mtls-ca/no-revocation-key",
550
+ "revoke requires a serial number or a fingerprint " +
551
+ "(revoke(serial, opts) or revoke({ serial, fingerprint }))");
552
+ }
473
553
  var reasonCode = CRL_REASON_BY_NAME[reasonName];
474
554
  if (reasonCode === undefined) {
475
555
  throw new MtlsCaError("mtls-ca/bad-reason",
476
556
  "revoke: unknown reason '" + reasonName + "' (valid: " +
477
557
  Object.keys(CRL_REASON_BY_NAME).join(", ") + ")");
478
558
  }
479
- var state = _loadRevocations();
480
- var existing = state.revocations.find(function (r) {
481
- return r.serialNumber === serial;
559
+ var existing = revocationStore.list().find(function (r) {
560
+ return (serial && r.serialNumber === serial) || (fingerprint && r.fingerprint === fingerprint);
482
561
  });
483
562
  if (existing) {
484
- // Idempotent — repeated revoke() of the same serial doesn't
563
+ // Idempotent — repeated revoke() of the same serial/fingerprint doesn't
485
564
  // shift the revokedAt timestamp.
486
565
  return existing;
487
566
  }
488
567
  var entry = {
489
568
  serialNumber: serial,
569
+ fingerprint: fingerprint,
490
570
  reason: reasonName,
491
571
  reasonCode: reasonCode,
492
572
  revokedAt: Date.now(),
493
573
  };
494
- state.revocations.push(entry);
495
- _saveRevocations(state);
574
+ revocationStore.add(entry);
496
575
  return entry;
497
576
  }
498
577
 
499
- function isRevoked(serialNumber) {
500
- var serial = _normalizeSerial(serialNumber);
501
- var state = _loadRevocations();
502
- return state.revocations.some(function (r) {
503
- return r.serialNumber === serial;
578
+ function isRevoked(serialOrFingerprint) {
579
+ // Accept a serial number OR a SHA3-512 fingerprint — both are hex, so one
580
+ // normalized form is matched against either key each entry carries.
581
+ if (!serialOrFingerprint || typeof serialOrFingerprint !== "string") {
582
+ throw new MtlsCaError("mtls-ca/bad-revocation-key",
583
+ "isRevoked requires a serial number or a fingerprint (hex string)");
584
+ }
585
+ var norm = _normalizeFingerprint(serialOrFingerprint);
586
+ return revocationStore.list().some(function (r) {
587
+ return r.serialNumber === norm || r.fingerprint === norm;
504
588
  });
505
589
  }
506
590
 
507
591
  function getRevocations() {
508
- return _loadRevocations().revocations.slice();
592
+ return revocationStore.list().slice();
509
593
  }
510
594
 
511
595
  // Generate a signed X.509 CRL covering every entry in the registry.
@@ -521,7 +605,7 @@ function create(opts) {
521
605
  "framework's bundled CA engine, which supports it");
522
606
  }
523
607
  var ca = await initCA();
524
- var revocations = _loadRevocations().revocations;
608
+ var revocations = revocationStore.list();
525
609
  var nowMs = Date.now();
526
610
  var thisUpdate = opts3.thisUpdate || new Date(nowMs);
527
611
  var nextUpdate = opts3.nextUpdate ||
@@ -39,6 +39,7 @@
39
39
  */
40
40
 
41
41
  var C = require("./constants");
42
+ var boundedMap = require("./bounded-map");
42
43
  var defineClass = require("./framework-error").defineClass;
43
44
  var lazyRequire = require("./lazy-require");
44
45
  var validateOpts = require("./validate-opts");
@@ -79,9 +80,7 @@ function _slideAndSum(entry, nowHour) {
79
80
  function _memoryBackend() {
80
81
  var store = new Map();
81
82
  function _get(key) {
82
- var entry = store.get(key);
83
- if (!entry) { entry = _newEntry(); store.set(key, entry); }
84
- return entry;
83
+ return boundedMap.getOrInsert(store, key, function () { return _newEntry(); });
85
84
  }
86
85
  return {
87
86
  async total(key, nowMs) {
@@ -172,27 +171,14 @@ function create(opts) {
172
171
  validateOpts(opts, ["bytesPerDay", "cache", "audit", "now"], "network.byteQuota");
173
172
  _requirePositiveBytes("bytesPerDay", opts.bytesPerDay);
174
173
  var bytesPerDay = opts.bytesPerDay;
175
- var auditOn = opts.audit !== false;
176
174
  var now = typeof opts.now === "function" ? opts.now : function () { return Date.now(); };
177
175
  var backend = opts.cache && typeof opts.cache.get === "function"
178
176
  ? _cacheBackend(opts.cache)
179
177
  : _memoryBackend();
180
178
 
181
- function _emitAudit(action, outcome, metadata) {
182
- if (!auditOn) return;
183
- try {
184
- audit().safeEmit({
185
- action: "network.byte_quota." + action,
186
- outcome: outcome,
187
- metadata: metadata || {},
188
- });
189
- } catch (_e) { /* drop-silent — audit is best-effort */ }
190
- }
179
+ var _emitAudit = audit().namespaced("network.byte_quota", opts.audit);
191
180
 
192
- function _emitMetric(verb, n, labels) {
193
- try { observability().safeEvent("network.byte_quota." + verb, n || 1, labels || {}); }
194
- catch (_e) { /* drop-silent */ }
195
- }
181
+ var _emitMetric = observability().namespaced("network.byte_quota");
196
182
 
197
183
  // check(key, bytes) — preflight without mutation. Returns
198
184
  // { allowed, total, remaining, quota, retryAfterSec, degraded }
@@ -31,6 +31,7 @@
31
31
 
32
32
  var nodeCrypto = require("node:crypto");
33
33
  var bCrypto = require("./crypto");
34
+ var safeBuffer = require("./safe-buffer");
34
35
  var validateOpts = require("./validate-opts");
35
36
  var { defineClass } = require("./framework-error");
36
37
 
@@ -44,12 +45,13 @@ var SELECTORS = { 0: "Cert", 1: "SPKI" };
44
45
  // refused rather than guessed.
45
46
  var MATCHING = { 0: null, 1: "sha256", 2: "sha512" };
46
47
 
47
- function _bytes(x, what) {
48
- if (Buffer.isBuffer(x)) return x;
49
- if (x instanceof Uint8Array) return Buffer.from(x);
50
- if (typeof x === "string") return Buffer.from(x, "hex");
51
- throw new DaneError("dane/bad-bytes", "dane: " + what + " must be a Buffer / Uint8Array / hex string");
52
- }
48
+ var _bytes = safeBuffer.makeByteCoercer({
49
+ errorClass: DaneError,
50
+ typeCode: "dane/bad-bytes",
51
+ messagePrefix: "dane: ",
52
+ messageSuffix: " must be a Buffer / Uint8Array / hex string",
53
+ encoding: "hex",
54
+ });
53
55
 
54
56
  function _selectedData(x509, selector) {
55
57
  if (selector === 0) return Buffer.from(x509.raw); // full certificate DER
@@ -179,7 +179,7 @@ var QTYPE_BY_NAME = Object.freeze({
179
179
  function create(opts) {
180
180
  opts = opts || {};
181
181
  var profile = opts.profile || (opts.posture && safeDns.compliancePosture(opts.posture)) || DEFAULT_PROFILE;
182
- if (!safeDns.PROFILES[profile]) {
182
+ if (!Object.prototype.hasOwnProperty.call(safeDns.PROFILES, profile)) {
183
183
  throw new ResolverError("resolver/bad-profile",
184
184
  "create: unknown profile '" + profile + "'");
185
185
  }
@@ -188,7 +188,9 @@ function create(opts) {
188
188
  var serveStale = opts.serveStale === false ? 0 :
189
189
  typeof opts.serveStale === "number" ? opts.serveStale : DEFAULT_STALE_WINDOW;
190
190
  var transport = opts.transport || _defaultTransport();
191
- var auditImpl = opts.audit || audit();
191
+ // Audit goes to the operator-supplied sink (opts.audit) when present, else the
192
+ // framework chain — b.audit.namespaced no-ops if the sink has no safeEmit.
193
+ var _baseAudit = audit().namespaced("network.dns.resolver", { sink: opts.audit });
192
194
 
193
195
  if (typeof transport.lookup !== "function") {
194
196
  throw new ResolverError("resolver/bad-transport",
@@ -238,11 +240,7 @@ function create(opts) {
238
240
  }
239
241
 
240
242
  function _safeEmit(action, metadata) {
241
- try {
242
- if (auditImpl && typeof auditImpl.safeEmit === "function") {
243
- auditImpl.safeEmit({ action: "network.dns.resolver." + action, outcome: "success", metadata: metadata });
244
- }
245
- } catch (_e) { /* audit drop-silent per validation tier policy */ }
243
+ _baseAudit(action, "success", metadata);
246
244
  }
247
245
 
248
246
  async function query(name, type, qopts) {
@@ -526,8 +524,101 @@ function _minTtl(rrs) {
526
524
  return min === Infinity ? 0 : min;
527
525
  }
528
526
 
527
+ // _sharedTxtResolver — one process-wide validating resolver (with its TTL
528
+ // cache) shared by every email-auth policy TXT lookup, instead of each module
529
+ // constructing its own (or reaching for plaintext system DNS).
530
+ var _sharedResolver = null;
531
+ function _sharedTxtResolver() {
532
+ if (!_sharedResolver) _sharedResolver = create();
533
+ return _sharedResolver;
534
+ }
535
+
536
+ /**
537
+ * @primitive b.network.dns.resolver.resolveTxt
538
+ * @signature b.network.dns.resolver.resolveTxt(qname, dnsLookup?, resolver?)
539
+ * @since 0.15.13
540
+ * @status stable
541
+ * @related b.network.dns.resolver.safeResolveTxt, b.network.dns.resolver.create
542
+ *
543
+ * Resolve TXT records for `qname` via the operator's `dnsLookup(qname, "TXT")`
544
+ * override when supplied, otherwise the framework's shared validating
545
+ * (DoH / DoT / system, DNSSEC-checked) resolver — normalized to the
546
+ * `string[][]` shape `dnsPromises.resolveTxt` returns. Throws an `ENODATA`
547
+ * Error when no TXT records exist.
548
+ *
549
+ * This is the secure DNS path for every email-auth policy lookup (DMARC / DKIM /
550
+ * ARC / BIMI / MTA-STS / TLS-RPT). Plaintext system DNS (`dnsPromises.resolveTxt`)
551
+ * is spoofable — DNS cache-poisoning / Kaminsky-class — and must NOT be used for
552
+ * records a downgrade or redirect would exploit.
553
+ *
554
+ * Pass `resolver` (a `b.network.dns.resolver.create()` instance) to reshape TXT
555
+ * records off a caller-owned resolver instead of the shared one — the mail-auth
556
+ * and mail-dkim modules use this so their TXT lookups share the same resolver
557
+ * (and cache) they use for A / MX / PTR, instead of each re-rolling the reshape.
558
+ *
559
+ * @example
560
+ * var rrs = await b.network.dns.resolver.resolveTxt("_dmarc.example.com");
561
+ * // → [ ["v=DMARC1; p=reject"] ]
562
+ */
563
+ async function resolveTxt(qname, dnsLookup, resolver) {
564
+ if (dnsLookup) return dnsLookup(qname, "TXT");
565
+ var r = await (resolver || _sharedTxtResolver()).queryTxt(qname);
566
+ var out = [];
567
+ for (var i = 0; i < r.rrs.length; i += 1) {
568
+ var rr = r.rrs[i];
569
+ if (rr && rr.type === 16) { // IANA DNS qtype TXT
570
+ out.push(Array.isArray(rr.decoded) ? rr.decoded : [String(rr.decoded)]);
571
+ }
572
+ }
573
+ if (out.length === 0) {
574
+ var err = new Error("no TXT records for " + qname);
575
+ err.code = "ENODATA";
576
+ throw err;
577
+ }
578
+ return out;
579
+ }
580
+
581
+ /**
582
+ * @primitive b.network.dns.resolver.safeResolveTxt
583
+ * @signature b.network.dns.resolver.safeResolveTxt(qname, opts?)
584
+ * @since 0.15.13
585
+ * @status stable
586
+ * @related b.network.dns.resolver.resolveTxt
587
+ *
588
+ * `resolveTxt` wrapped with the email-auth policy-fetch convention: a domain
589
+ * with no record (ENOTFOUND / ENODATA) returns `null` (absence is not an
590
+ * error); any other failure throws the caller's typed error via
591
+ * `errorFactory(code, message)` so each protocol keeps its own error class.
592
+ * The same secure resolver path as `resolveTxt` — never plaintext system DNS.
593
+ *
594
+ * @opts
595
+ * dnsLookup: function, // operator override (qname, "TXT") => string[][]
596
+ * errorFactory: function, // (code, message) => Error, thrown on non-absence failures
597
+ * code: string, // error code passed to errorFactory
598
+ *
599
+ * @example
600
+ * var rrs = await b.network.dns.resolver.safeResolveTxt("_mta-sts.example.com", {
601
+ * errorFactory: function (code, msg) { return new SmtpPolicyError(code, msg); },
602
+ * code: "smtp/mta-sts-txt-lookup-failed",
603
+ * });
604
+ * if (rrs === null) return null; // no MTA-STS record published
605
+ */
606
+ async function safeResolveTxt(qname, opts) {
607
+ opts = opts || {};
608
+ try {
609
+ return await resolveTxt(qname, opts.dnsLookup);
610
+ } catch (e) {
611
+ if (e && (e.code === "ENOTFOUND" || e.code === "ENODATA")) return null;
612
+ var msg = "TXT lookup for " + qname + " failed: " + ((e && e.message) || String(e));
613
+ if (typeof opts.errorFactory === "function") throw opts.errorFactory(opts.code, msg);
614
+ throw e;
615
+ }
616
+ }
617
+
529
618
  module.exports = {
530
619
  create: create,
620
+ resolveTxt: resolveTxt,
621
+ safeResolveTxt: safeResolveTxt,
531
622
  ResolverError: ResolverError,
532
623
  QTYPE_BY_NAME: QTYPE_BY_NAME,
533
624
  };
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
 
3
3
  var dns = require("node:dns");
4
+ var numericBounds = require("./numeric-bounds");
4
5
  var net = require("node:net");
5
6
  var https = require("node:https");
6
7
  var nodeTls = require("node:tls");
@@ -347,10 +348,16 @@ function _decodeDnsAnswer(buf, qtype) {
347
348
  for (var a = 0; a < ancount; a++) {
348
349
  _skipDnsName(buf, state);
349
350
  var off = state.off;
351
+ // Bound the RR fixed header (10 bytes: type+class+ttl+rdlen) and the rdata
352
+ // before indexing — ancount comes from the (untrusted upstream) header, so a
353
+ // truncated reply would otherwise over-read into a raw RangeError. Mirrors
354
+ // the hardened _decodeDnsAnswerRaw guards.
355
+ if (off + 10 > buf.length) throw new DnsError("dns/bad-reply", "answer record truncated");
350
356
  var rtype = buf.readUInt16BE(off); off += 2;
351
357
  off += 2;
352
358
  off += 4;
353
359
  var rdlen = buf.readUInt16BE(off); off += 2;
360
+ if (off + rdlen > buf.length) throw new DnsError("dns/bad-reply", "answer rdata truncated");
354
361
  if (rtype === qtype && qtype === 1 && rdlen === 4) {
355
362
  addrs.push(buf[off] + "." + buf[off + 1] + "." + buf[off + 2] + "." + buf[off + 3]);
356
363
  } else if (rtype === qtype && qtype === DNS_QTYPE_AAAA && rdlen === IPV6_ADDR_BYTES) {
@@ -1851,7 +1858,7 @@ var DS_DIGEST_TYPES = Object.freeze({
1851
1858
  });
1852
1859
 
1853
1860
  function classifyDnskeyAlgorithm(algorithm) {
1854
- if (typeof algorithm !== "number" || !isFinite(algorithm) || Math.floor(algorithm) !== algorithm) {
1861
+ if (!numericBounds.isFiniteInt(algorithm)) {
1855
1862
  return null;
1856
1863
  }
1857
1864
  var row = DNSKEY_ALGORITHMS[algorithm];
@@ -1874,7 +1881,7 @@ function classifyDnskeyAlgorithm(algorithm) {
1874
1881
  }
1875
1882
 
1876
1883
  function classifyDsDigestType(digestType) {
1877
- if (typeof digestType !== "number" || !isFinite(digestType) || Math.floor(digestType) !== digestType) {
1884
+ if (!numericBounds.isFiniteInt(digestType)) {
1878
1885
  return null;
1879
1886
  }
1880
1887
  var row = DS_DIGEST_TYPES[digestType];
@@ -45,7 +45,9 @@
45
45
  */
46
46
 
47
47
  var nodeCrypto = require("node:crypto");
48
+ var numericBounds = require("./numeric-bounds");
48
49
  var bCrypto = require("./crypto");
50
+ var safeBuffer = require("./safe-buffer");
49
51
  var validateOpts = require("./validate-opts");
50
52
  var { defineClass } = require("./framework-error");
51
53
 
@@ -79,11 +81,14 @@ var TYPE_NUM = {
79
81
  SMIMEA: 53, CDS: 59, CDNSKEY: 60, OPENPGPKEY: 61, CAA: 257, HINFO: 13,
80
82
  };
81
83
 
82
- function _bytes(x, what) {
83
- if (Buffer.isBuffer(x)) return x;
84
- if (x instanceof Uint8Array) return Buffer.from(x);
85
- throw new DnssecError("dnssec/bad-bytes", "dnssec: " + what + " must be a Buffer");
86
- }
84
+ // DNSSEC wire data is bytes, never text (allowString:false).
85
+ var _bytes = safeBuffer.makeByteCoercer({
86
+ errorClass: DnssecError,
87
+ typeCode: "dnssec/bad-bytes",
88
+ messagePrefix: "dnssec: ",
89
+ messageSuffix: " must be a Buffer",
90
+ allowString: false,
91
+ });
87
92
 
88
93
  // Canonical wire form of a domain name (RFC 4034 §6.2): each label
89
94
  // length-prefixed, ASCII lowercased, terminated by the root label.
@@ -157,8 +162,11 @@ function _dnskeyToKey(algId, publicKey) {
157
162
  return _jwkKey({ kty: "OKP", crv: "Ed25519", x: pk.toString("base64url") });
158
163
  }
159
164
  function _jwkKey(jwk) {
160
- try { return nodeCrypto.createPublicKey({ key: jwk, format: "jwk" }); }
161
- catch (e) { throw new DnssecError("dnssec/bad-key", "dnssec: could not import DNSKEY: " + ((e && e.message) || e)); }
165
+ return bCrypto.importPublicJwk(jwk, {
166
+ errorClass: DnssecError,
167
+ code: "dnssec/bad-key",
168
+ messagePrefix: "dnssec: could not import DNSKEY: ",
169
+ });
162
170
  }
163
171
 
164
172
  /**
@@ -284,15 +292,8 @@ function verifyRrset(opts) {
284
292
  }
285
293
 
286
294
  // Validity window (fail closed on a bad opts.at).
287
- var atMs;
288
- if (opts.at !== undefined && opts.at !== null) {
289
- if (!(opts.at instanceof Date) || !isFinite(opts.at.getTime())) {
290
- throw new DnssecError("dnssec/bad-at", "dnssec.verifyRrset: opts.at must be a valid Date");
291
- }
292
- atMs = opts.at.getTime();
293
- } else {
294
- atMs = Date.now();
295
- }
295
+ validateOpts.optionalDate(opts.at, "dnssec.verifyRrset: opts.at", DnssecError, "dnssec/bad-at");
296
+ var atMs = (opts.at !== undefined && opts.at !== null) ? opts.at.getTime() : Date.now();
296
297
  var nowSec = Math.floor(atMs / 1000);
297
298
  if (nowSec < (rrsig.inception >>> 0)) throw new DnssecError("dnssec/not-yet-valid", "dnssec.verifyRrset: RRSIG inception is in the future");
298
299
  if (nowSec > (rrsig.expiration >>> 0)) throw new DnssecError("dnssec/expired", "dnssec.verifyRrset: RRSIG has expired");
@@ -515,7 +516,7 @@ function nsec3Hash(name, opts) {
515
516
  validateOpts(opts, ["salt", "iterations"], "dnssec.nsec3Hash");
516
517
  var salt = _bytes(opts.salt, "salt");
517
518
  var iters = opts.iterations;
518
- if (typeof iters !== "number" || !isFinite(iters) || iters < 0 || Math.floor(iters) !== iters) {
519
+ if (!numericBounds.isNonNegativeFiniteInt(iters)) {
519
520
  throw new DnssecError("dnssec/bad-iterations", "dnssec.nsec3Hash: iterations must be a non-negative integer");
520
521
  }
521
522
  return _nsec3HashWire(_canonicalName(name), salt, iters);
@@ -5,6 +5,7 @@ var net = require("node:net");
5
5
  var C = require("./constants");
6
6
  var validateOpts = require("./validate-opts");
7
7
  var lazyRequire = require("./lazy-require");
8
+ var boundedMap = require("./bounded-map");
8
9
  var { defineClass } = require("./framework-error");
9
10
 
10
11
  var HeartbeatError = defineClass("HeartbeatError", { alwaysPermanent: true });
@@ -198,9 +199,9 @@ function start(opts) {
198
199
  var started = [];
199
200
  for (var j = 0; j < opts.targets.length; j++) {
200
201
  var t = opts.targets[j];
201
- if (TARGETS.has(t.name)) {
202
+ boundedMap.requireAbsent(TARGETS, t.name, function () {
202
203
  throw new HeartbeatError("heartbeat/duplicate", "heartbeat target '" + t.name + "' already started");
203
- }
204
+ });
204
205
  var entry = {
205
206
  target: t,
206
207
  intervalMs: t.intervalMs || DEFAULT_INTERVAL_MS,