@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/auth/oauth.js CHANGED
@@ -215,11 +215,8 @@ var DEFAULT_CLOCK_SKEW_MS = C.TIME.minutes(1);
215
215
  // OAuth 2.0 Threat Model §4.4.1.8 / §4.4.1.13.
216
216
  var PKCE_VERIFIER_BYTES = C.BYTES.bytes(32);
217
217
  var STATE_NONCE_BYTES = C.BYTES.bytes(16);
218
- // JOSE PSS salt lengths (RFC 7518 §3.5) match the hash-output size:
219
- // PS256/SHA-256 32, PS384/SHA-384 48, PS512/SHA-512 → 64.
220
- var PSS_SALT_BYTES_SHA256 = C.BYTES.bytes(32);
221
- var PSS_SALT_BYTES_SHA384 = C.BYTES.bytes(48);
222
- var PSS_SALT_BYTES_SHA512 = C.BYTES.bytes(64);
218
+ // JOSE PSS salt lengths (RFC 7518 §3.5) now live with the shared alg table
219
+ // in jwtExternal.algParams; _verifyParamsForAlg here is a thin wrapper.
223
220
 
224
221
  // RFC 8628 §3.4 — device_code length cap. The spec doesn't fix a max
225
222
  // length but 8 KiB comfortably accommodates any legitimate base64url
@@ -253,11 +250,12 @@ var RFC_8693_TOKEN_TYPES = Object.freeze([
253
250
 
254
251
  function _b64urlEncode(buf) { return bCrypto.toBase64Url(buf); }
255
252
 
256
- function _b64urlDecode(s) {
257
- if (typeof s !== "string") throw new OAuthError("auth-oauth/bad-base64", "expected base64url string");
258
- try { return bCrypto.fromBase64Url(s); }
259
- catch (_e) { throw new OAuthError("auth-oauth/bad-base64", "segment is not valid base64url"); }
260
- }
253
+ var _b64urlDecode = bCrypto.makeBase64UrlDecoder({
254
+ errorClass: OAuthError,
255
+ code: "auth-oauth/bad-base64",
256
+ typeMessage: "expected base64url string",
257
+ badMessage: "segment is not valid base64url",
258
+ });
261
259
 
262
260
  function _generateRandomToken(bytes) {
263
261
  return _b64urlEncode(generateBytes(bytes));
@@ -329,29 +327,27 @@ function _validateUrl(url, allowHttp, label) {
329
327
  // ---- JOSE alg → node:crypto verify parameters ----
330
328
 
331
329
  function _verifyParamsForAlg(alg) {
332
- // Returns { hash, padding, dsaEncoding } for node:crypto.verify.
333
- if (alg === "RS256") return { hash: "sha256", padding: nodeCrypto.constants.RSA_PKCS1_PADDING };
334
- if (alg === "RS384") return { hash: "sha384", padding: nodeCrypto.constants.RSA_PKCS1_PADDING };
335
- if (alg === "RS512") return { hash: "sha512", padding: nodeCrypto.constants.RSA_PKCS1_PADDING };
336
- if (alg === "PS256") return { hash: "sha256", padding: nodeCrypto.constants.RSA_PKCS1_PSS_PADDING, saltLength: PSS_SALT_BYTES_SHA256 };
337
- if (alg === "PS384") return { hash: "sha384", padding: nodeCrypto.constants.RSA_PKCS1_PSS_PADDING, saltLength: PSS_SALT_BYTES_SHA384 };
338
- if (alg === "PS512") return { hash: "sha512", padding: nodeCrypto.constants.RSA_PKCS1_PSS_PADDING, saltLength: PSS_SALT_BYTES_SHA512 };
339
- if (alg === "ES256") return { hash: "sha256", dsaEncoding: "ieee-p1363" };
340
- if (alg === "ES384") return { hash: "sha384", dsaEncoding: "ieee-p1363" };
341
- if (alg === "ES512") return { hash: "sha512", dsaEncoding: "ieee-p1363" };
342
- throw new OAuthError("auth-oauth/unsupported-alg",
343
- "alg '" + alg + "' is not supported for ID-token verification");
330
+ // Returns { hash, padding, dsaEncoding } for node:crypto.verify, from the
331
+ // shared classical-JOSE table (jwtExternal.algParams). ID-token verification
332
+ // deliberately does NOT accept EdDSA most OIDC OPs sign with RS256/ES256
333
+ // and the framework keeps the ID-token verify surface to the RSA/ECDSA set.
334
+ var params = jwtExternal.algParams(alg);
335
+ if (!params || alg === "EdDSA") {
336
+ throw new OAuthError("auth-oauth/unsupported-alg",
337
+ "alg '" + alg + "' is not supported for ID-token verification");
338
+ }
339
+ return params;
344
340
  }
345
341
 
346
342
  // ---- JWKS → KeyObject ----
347
343
 
348
344
  function _jwkToKey(jwk) {
349
345
  // node's createPublicKey accepts JWK directly since Node 16.
350
- try { return nodeCrypto.createPublicKey({ key: jwk, format: "jwk" }); }
351
- catch (e) {
352
- throw new OAuthError("auth-oauth/bad-jwk",
353
- "could not import JWK (kid=" + (jwk && jwk.kid) + "): " + ((e && e.message) || String(e)));
354
- }
346
+ return bCrypto.importPublicJwk(jwk, {
347
+ errorClass: OAuthError,
348
+ code: "auth-oauth/bad-jwk",
349
+ messagePrefix: "could not import JWK (kid=" + (jwk && jwk.kid) + "): ",
350
+ });
355
351
  }
356
352
 
357
353
  // ---- RFC 9396 Rich Authorization Requests (RAR) ----
@@ -657,6 +653,16 @@ function _publicCnfJwk(jwk, label) {
657
653
  label + ": instanceKeyJwk.kty='" + jwk.kty + "' is not an asymmetric public JWK");
658
654
  }
659
655
 
656
+ // Config-time check for an OPTIONAL epoch-seconds override (iat / nbf): if
657
+ // present it must be a finite number, so a typo is caught at build time
658
+ // rather than silently ignored by the `typeof === "number"` body guard.
659
+ function _optionalFiniteNumber(value, label, code) {
660
+ if (value === undefined || value === null) return;
661
+ if (typeof value !== "number" || !isFinite(value)) {
662
+ throw new OAuthError(code, label + " must be a finite number (epoch seconds)");
663
+ }
664
+ }
665
+
660
666
  /**
661
667
  * @primitive b.auth.oauth.buildClientAttestation
662
668
  * @signature b.auth.oauth.buildClientAttestation(opts)
@@ -700,14 +706,23 @@ function _publicCnfJwk(jwk, label) {
700
706
  */
701
707
  function buildClientAttestation(aopts) {
702
708
  aopts = aopts || {};
703
- validateOpts(aopts, [
704
- "clientId", "attesterPrivateKey", "instanceKeyJwk", "algorithm",
705
- "expiresInSec", "nbf", "iat", "extraClaims",
706
- ], "auth.oauth.buildClientAttestation");
707
- validateOpts.requireNonEmptyString(aopts.clientId,
708
- "buildClientAttestation: clientId", OAuthError, "auth-oauth/attestation-no-client-id");
709
- validateOpts.optionalPositiveInt(aopts.expiresInSec,
710
- "buildClientAttestation: expiresInSec", OAuthError, "auth-oauth/attestation-bad-expiry");
709
+ validateOpts.shape(aopts, {
710
+ clientId: { rule: "required-string", code: "auth-oauth/attestation-no-client-id" },
711
+ // KeyObject | PEM string | JWK object — typed downstream by
712
+ // _toAttestationPrivateKey; the shape only enforces presence.
713
+ attesterPrivateKey: function (v, l) {
714
+ if (v === undefined || v === null) {
715
+ throw new OAuthError("auth-oauth/attestation-no-attester-key",
716
+ l + " (Attester signing key) is required");
717
+ }
718
+ },
719
+ instanceKeyJwk: { rule: "required-object", code: "auth-oauth/attestation-bad-cnf" },
720
+ algorithm: { rule: "optional-string", code: "auth-oauth/attestation-bad-alg" },
721
+ nbf: function (v, l) { _optionalFiniteNumber(v, l, "auth-oauth/attestation-bad-nbf"); },
722
+ iat: function (v, l) { _optionalFiniteNumber(v, l, "auth-oauth/attestation-bad-iat"); },
723
+ extraClaims: { rule: "optional-plain-object", code: "auth-oauth/attestation-bad-extra-claims" },
724
+ expiresInSec: { rule: "optional-positive-int", code: "auth-oauth/attestation-bad-expiry" },
725
+ }, "buildClientAttestation", OAuthError, "auth-oauth/attestation-no-client-id");
711
726
  var key = _toAttestationPrivateKey(aopts.attesterPrivateKey, "buildClientAttestation");
712
727
  var alg = _resolveAttestationAlg(aopts.algorithm, key, "buildClientAttestation");
713
728
  var cnfJwk = _publicCnfJwk(aopts.instanceKeyJwk, "buildClientAttestation");
@@ -769,16 +784,23 @@ function buildClientAttestation(aopts) {
769
784
  */
770
785
  function buildClientAttestationPop(popts) {
771
786
  popts = popts || {};
772
- validateOpts(popts, [
773
- "instancePrivateKey", "audience", "algorithm", "challenge",
774
- "jti", "iat", "expiresInSec",
775
- ], "auth.oauth.buildClientAttestationPop");
776
- validateOpts.requireNonEmptyString(popts.audience,
777
- "buildClientAttestationPop: audience (AS issuer)", OAuthError, "auth-oauth/attestation-pop-no-aud");
778
- validateOpts.optionalNonEmptyString(popts.challenge,
779
- "buildClientAttestationPop: challenge", OAuthError, "auth-oauth/attestation-pop-bad-challenge");
780
- validateOpts.optionalPositiveInt(popts.expiresInSec,
781
- "buildClientAttestationPop: expiresInSec", OAuthError, "auth-oauth/attestation-pop-bad-expiry");
787
+ validateOpts.shape(popts, {
788
+ audience: { rule: "required-string", code: "auth-oauth/attestation-pop-no-aud",
789
+ label: "buildClientAttestationPop: audience (AS issuer)" },
790
+ // KeyObject | PEM string | JWK object — typed downstream by
791
+ // _toAttestationPrivateKey; the shape only enforces presence.
792
+ instancePrivateKey: function (v, l) {
793
+ if (v === undefined || v === null) {
794
+ throw new OAuthError("auth-oauth/attestation-pop-no-instance-key",
795
+ l + " (instance signing key matching cnf.jwk) is required");
796
+ }
797
+ },
798
+ algorithm: { rule: "optional-string", code: "auth-oauth/attestation-pop-bad-alg" },
799
+ jti: { rule: "optional-string", code: "auth-oauth/attestation-pop-bad-jti" },
800
+ iat: function (v, l) { _optionalFiniteNumber(v, l, "auth-oauth/attestation-pop-bad-iat"); },
801
+ challenge: { rule: "optional-string", code: "auth-oauth/attestation-pop-bad-challenge" },
802
+ expiresInSec: { rule: "optional-positive-int", code: "auth-oauth/attestation-pop-bad-expiry" },
803
+ }, "buildClientAttestationPop", OAuthError, "auth-oauth/attestation-pop-no-aud");
782
804
  var key = _toAttestationPrivateKey(popts.instancePrivateKey, "buildClientAttestationPop");
783
805
  var alg = _resolveAttestationAlg(popts.algorithm, key, "buildClientAttestationPop");
784
806
  var iatSec = typeof popts.iat === "number" ? popts.iat : Math.floor(Date.now() / C.TIME.seconds(1));
@@ -54,7 +54,7 @@ var lazyRequire = require("../lazy-require");
54
54
  var validateOpts = require("../validate-opts");
55
55
  var safeJson = require("../safe-json");
56
56
  var nodeCrypto = require("node:crypto");
57
- var { generateToken, sha3Hash, timingSafeEqual } = require("../crypto");
57
+ var { generateToken, sha3Hash, timingSafeEqual, importPublicJwk } = require("../crypto");
58
58
  // Shared JOSE defenses (CVE-2026-22817 alg/kty cross-check). Top-of-
59
59
  // file per project convention §3; no circular load — jwt-external
60
60
  // requires nothing from oid4vci.
@@ -297,11 +297,11 @@ async function _verifyProofJwt(proofJwt, expectedAud, expectedCNonce, expectedCl
297
297
  // A resolver that returns an RSA key for a proof declaring an HMAC
298
298
  // alg would otherwise be verified as an HMAC secret.
299
299
  jwtExternal._assertAlgKtyMatch(header.alg, holderKeyJwk);
300
- try { keyObj = nodeCrypto.createPublicKey({ key: holderKeyJwk, format: "jwk" }); }
301
- catch (e) {
302
- throw new AuthError("auth-oid4vci/bad-resolved-key",
303
- "credential issuance: resolveKid-returned key is not importable as a public key: " + ((e && e.message) || String(e)));
304
- }
300
+ keyObj = importPublicJwk(holderKeyJwk, {
301
+ errorClass: AuthError,
302
+ code: "auth-oid4vci/bad-resolved-key",
303
+ messagePrefix: "credential issuance: resolveKid-returned key is not importable as a public key: ",
304
+ });
305
305
  } else if (!holderKeyJwk && header.x5c) {
306
306
  // RFC 7515 §4.1.6 / OID4VCI §8.2.1.1 — the wallet ships a base64 DER
307
307
  // certificate chain; the LEAF (first) cert's SPKI is the holder key.
@@ -333,11 +333,11 @@ async function _verifyProofJwt(proofJwt, expectedAud, expectedCNonce, expectedCl
333
333
  // A leaf cert holding an RSA key against a proof declaring an HMAC
334
334
  // alg would otherwise be verified as an HMAC secret.
335
335
  jwtExternal._assertAlgKtyMatch(header.alg, holderKeyJwk);
336
- try { keyObj = nodeCrypto.createPublicKey({ key: holderKeyJwk, format: "jwk" }); }
337
- catch (e) {
338
- throw new AuthError("auth-oid4vci/bad-x5c",
339
- "credential issuance: proof JWT `x5c` leaf public key is not importable: " + ((e && e.message) || String(e)));
340
- }
336
+ keyObj = importPublicJwk(holderKeyJwk, {
337
+ errorClass: AuthError,
338
+ code: "auth-oid4vci/bad-x5c",
339
+ messagePrefix: "credential issuance: proof JWT `x5c` leaf public key is not importable: ",
340
+ });
341
341
  } else {
342
342
  if (!holderKeyJwk) {
343
343
  throw new AuthError("auth-oid4vci/no-jwk-in-header",
@@ -349,11 +349,11 @@ async function _verifyProofJwt(proofJwt, expectedAud, expectedCNonce, expectedCl
349
349
  // key as an HMAC secret. Routed through the shared helper so every
350
350
  // JWT verifier in the framework enforces the same check.
351
351
  jwtExternal._assertAlgKtyMatch(header.alg, holderKeyJwk);
352
- try { keyObj = nodeCrypto.createPublicKey({ key: holderKeyJwk, format: "jwk" }); }
353
- catch (e) {
354
- throw new AuthError("auth-oid4vci/bad-jwk",
355
- "credential issuance: proof JWT jwk is not parseable: " + ((e && e.message) || String(e)));
356
- }
352
+ keyObj = importPublicJwk(holderKeyJwk, {
353
+ errorClass: AuthError,
354
+ code: "auth-oid4vci/bad-jwk",
355
+ messagePrefix: "credential issuance: proof JWT jwk is not parseable: ",
356
+ });
357
357
  }
358
358
 
359
359
  var signingInput = parts[0] + "." + parts[1];
@@ -428,13 +428,36 @@ async function _verifyProofJwt(proofJwt, expectedAud, expectedCNonce, expectedCl
428
428
  * });
429
429
  */
430
430
  function create(opts) {
431
+ // Preserve the original object-guard's exact label + default code
432
+ // (distinct from the per-field "issuer.create:" label prefix below).
431
433
  validateOpts.requireObject(opts, "auth.oid4vci.issuer.create", AuthError);
432
- validateOpts.requireNonEmptyString(opts.credentialIssuerUrl,
433
- "issuer.create: credentialIssuerUrl", AuthError, "auth-oid4vci/no-issuer-url");
434
- validateOpts.requireNonEmptyString(opts.credentialEndpoint,
435
- "issuer.create: credentialEndpoint", AuthError, "auth-oid4vci/no-credential-endpoint");
436
- validateOpts.requireNonEmptyString(opts.tokenEndpoint,
437
- "issuer.create: tokenEndpoint", AuthError, "auth-oid4vci/no-token-endpoint");
434
+ validateOpts.shape(opts, {
435
+ credentialIssuerUrl: { rule: "required-string", code: "auth-oid4vci/no-issuer-url" },
436
+ credentialEndpoint: { rule: "required-string", code: "auth-oid4vci/no-credential-endpoint" },
437
+ tokenEndpoint: { rule: "required-string", code: "auth-oid4vci/no-token-endpoint" },
438
+ // sdJwtIssuer + supportedCredentials carry distinct bespoke checks
439
+ // (instance shape / non-empty map / per-credential format+vct) below;
440
+ // declare them here so the exhaustive contract accepts the keys —
441
+ // required-object validates only that they are objects, which the
442
+ // bespoke checks then refine.
443
+ sdJwtIssuer: "required-object",
444
+ supportedCredentials: "required-object",
445
+ proofAlgorithms: "optional-string-array",
446
+ // resolveKid / validateX5c carry distinct per-field codes the inline
447
+ // config-time guards used; the descriptor form keeps the exact code +
448
+ // label inside the exhaustive shape.
449
+ resolveKid: { rule: "optional-function", code: "auth-oid4vci/bad-resolve-kid" },
450
+ validateX5c: { rule: "optional-function", code: "auth-oid4vci/bad-validate-x5c" },
451
+ preAuthCodeTtlMs: "optional-positive-finite",
452
+ accessTokenTtlMs: "optional-positive-finite",
453
+ cNonceTtlMs: "optional-positive-finite",
454
+ proofMaxAgeMs: "optional-positive-finite",
455
+ accessTokenSingleUse: "optional-boolean",
456
+ codeStore: "optional-plain-object",
457
+ accessTokenStore: "optional-plain-object",
458
+ cNonceStore: "optional-plain-object",
459
+ authorizationServers: "optional-string-array",
460
+ }, "issuer.create", AuthError);
438
461
  if (!opts.sdJwtIssuer || typeof opts.sdJwtIssuer.issue !== "function") {
439
462
  throw new AuthError("auth-oid4vci/no-sd-jwt-issuer",
440
463
  "issuer.create: sdJwtIssuer must be a b.auth.sdJwtVc.issuer instance");
@@ -464,18 +487,18 @@ function create(opts) {
464
487
  ? opts.proofAlgorithms : ["ES256", "ES384", "EdDSA"];
465
488
 
466
489
  // Optional kid-resolver for kid-only proofs (EUDI-Wallet attested-key
467
- // flow). Config-time throw if supplied but not a function. Absent →
468
- // kid-only proofs keep the clear refusal (back-compat).
469
- var resolveKid = validateOpts.optionalFunction(opts.resolveKid,
470
- "issuer.create: resolveKid", AuthError, "auth-oid4vci/bad-resolve-kid");
490
+ // flow). Validated by the shape above (config-time throw if supplied
491
+ // but not a function). Absent → kid-only proofs keep the clear refusal
492
+ // (back-compat).
493
+ var resolveKid = opts.resolveKid;
471
494
 
472
495
  // Optional x5c chain-trust policy for x5c proofs (RFC 7515 §4.1.6 /
473
- // OID4VCI §8.2.1.1). Config-time throw if supplied but not a function.
474
- // Absent → the leaf-cert SPKI binds at the same self-asserted trust
475
- // level as an inline `jwk` (the proof signature binds the key); chain
476
- // anchoring beyond that is the operator's to enforce via this callback.
477
- var validateX5c = validateOpts.optionalFunction(opts.validateX5c,
478
- "issuer.create: validateX5c", AuthError, "auth-oid4vci/bad-validate-x5c");
496
+ // OID4VCI §8.2.1.1). Validated by the shape above (config-time throw if
497
+ // supplied but not a function). Absent → the leaf-cert SPKI binds at the
498
+ // same self-asserted trust level as an inline `jwk` (the proof signature
499
+ // binds the key); chain anchoring beyond that is the operator's to
500
+ // enforce via this callback.
501
+ var validateX5c = opts.validateX5c;
479
502
 
480
503
  var preAuthTtl = opts.preAuthCodeTtlMs || DEFAULT_PRE_AUTH_TTL_MS;
481
504
  var accessTokenTtl = opts.accessTokenTtlMs || DEFAULT_ACCESS_TOKEN_TTL;
@@ -52,6 +52,7 @@
52
52
 
53
53
  var lazyRequire = require("../lazy-require");
54
54
  var validateOpts = require("../validate-opts");
55
+ var boundedMap = require("../bounded-map");
55
56
  var { generateToken } = require("../crypto");
56
57
  var { AuthError } = require("../framework-error");
57
58
 
@@ -90,10 +91,10 @@ function _validateDcql(dcql) {
90
91
  throw new AuthError("auth-oid4vp/no-credential-id",
91
92
  "DCQL: credentials[" + i + "].id is required");
92
93
  }
93
- if (seenIds.has(cred.id)) {
94
+ boundedMap.requireAbsentMember(seenIds, cred.id, function () {
94
95
  throw new AuthError("auth-oid4vp/duplicate-id",
95
96
  "DCQL: credentials[" + i + "].id \"" + cred.id + "\" duplicated");
96
- }
97
+ });
97
98
  seenIds.add(cred.id);
98
99
  if (typeof cred.format !== "string" || cred.format.length === 0) {
99
100
  throw new AuthError("auth-oid4vp/no-format",
@@ -58,6 +58,7 @@ var lazyRequire = require("../lazy-require");
58
58
  var validateOpts = require("../validate-opts");
59
59
  var safeJson = require("../safe-json");
60
60
  var nodeCrypto = require("node:crypto");
61
+ var bCrypto = require("../crypto");
61
62
  var C = require("../constants");
62
63
  // Shared JOSE defenses (CVE-2026-22817 alg/kty cross-check +
63
64
  // CVE-2026-23552 constant-time iss compare). Top-of-file per project
@@ -195,12 +196,11 @@ function verifyEntityStatement(jwt, jwks, vopts) {
195
196
  // same check.
196
197
  jwtExternal._assertAlgKtyMatch(parsed.header.alg, key);
197
198
 
198
- var keyObj;
199
- try { keyObj = nodeCrypto.createPublicKey({ key: key, format: "jwk" }); }
200
- catch (e) {
201
- throw new AuthError("auth-openid-federation/bad-jwk",
202
- "verifyEntityStatement: JWK is not parseable: " + ((e && e.message) || String(e)));
203
- }
199
+ var keyObj = bCrypto.importPublicJwk(key, {
200
+ errorClass: AuthError,
201
+ code: "auth-openid-federation/bad-jwk",
202
+ messagePrefix: "verifyEntityStatement: JWK is not parseable: ",
203
+ });
204
204
 
205
205
  var hash = _hashByAlg(parsed.header.alg);
206
206
  var verifyOpts = { key: keyObj };
@@ -325,7 +325,7 @@ function _b64urlExtInput(value, name, maxBytes) {
325
325
  }
326
326
  if (typeof maxBytes === "number") {
327
327
  var decoded = Buffer.from(value, "base64url");
328
- if (decoded.length > maxBytes) {
328
+ if (safeBuffer.byteLengthOf(decoded) > maxBytes) {
329
329
  throw new AuthError("auth-passkey/extension-input-too-large",
330
330
  name + " decoded length " + decoded.length + " exceeds " + maxBytes + " bytes");
331
331
  }
@@ -333,14 +333,14 @@ function _b64urlExtInput(value, name, maxBytes) {
333
333
  return value;
334
334
  }
335
335
  if (Buffer.isBuffer(value)) {
336
- if (typeof maxBytes === "number" && value.length > maxBytes) {
336
+ if (typeof maxBytes === "number" && safeBuffer.byteLengthOf(value) > maxBytes) {
337
337
  throw new AuthError("auth-passkey/extension-input-too-large",
338
338
  name + " length " + value.length + " exceeds " + maxBytes + " bytes");
339
339
  }
340
340
  return value.toString("base64url");
341
341
  }
342
342
  if (value instanceof Uint8Array) {
343
- if (typeof maxBytes === "number" && value.length > maxBytes) {
343
+ if (typeof maxBytes === "number" && safeBuffer.byteLengthOf(value) > maxBytes) {
344
344
  throw new AuthError("auth-passkey/extension-input-too-large",
345
345
  name + " length " + value.length + " exceeds " + maxBytes + " bytes");
346
346
  }
@@ -385,7 +385,7 @@ function _largeBlobExt(args) {
385
385
  var SUPPORT = { preferred: 1, required: 1 };
386
386
  var modes = 0;
387
387
  if (args.support !== undefined) {
388
- if (!SUPPORT[args.support]) {
388
+ if (!Object.prototype.hasOwnProperty.call(SUPPORT, args.support)) {
389
389
  throw new AuthError("auth-passkey/bad-largeblob-support",
390
390
  "extensions.largeBlob support must be 'preferred' or 'required'");
391
391
  }
@@ -282,7 +282,7 @@ function policy(opts) {
282
282
  // Apply named profile FIRST, then operator opts on top so the
283
283
  // operator can override profile defaults per-field.
284
284
  if (typeof opts.profile === "string" && opts.profile.length > 0) {
285
- if (!POLICY_PROFILES[opts.profile]) {
285
+ if (!Object.prototype.hasOwnProperty.call(POLICY_PROFILES, opts.profile)) {
286
286
  throw new AuthError("auth-password/bad-policy",
287
287
  "policy.profile must be one of " + Object.keys(POLICY_PROFILES).join("/") +
288
288
  ", got " + JSON.stringify(opts.profile));
@@ -421,7 +421,13 @@ function policy(opts) {
421
421
  var sha1Full = hibpSha1.sha1Hex(plaintext).toUpperCase();
422
422
  var prefix = sha1Full.slice(0, 5);
423
423
  var suffix = sha1Full.slice(5);
424
- var url = p.hibpEndpoint.replace(/\/+$/, "") + "/range/" + prefix;
424
+ // Strip trailing slashes with a linear backward scan rather than
425
+ // /\/+$/ — the `$`-after-greedy-`+` regex is O(n^2) in V8 on a value
426
+ // that is a long run of '/' (the engine retries from every offset).
427
+ // Byte-identical result, unconditionally linear.
428
+ var endEp = p.hibpEndpoint.length;
429
+ while (endEp > 0 && p.hibpEndpoint.charCodeAt(endEp - 1) === 0x2f) { endEp -= 1; }
430
+ var url = p.hibpEndpoint.slice(0, endEp) + "/range/" + prefix;
425
431
  var resp;
426
432
  try {
427
433
  resp = await httpClient.request({
package/lib/auth/saml.js CHANGED
@@ -170,7 +170,7 @@ function _verifyXmldsig(envelope, signatureNode, certPem) {
170
170
  }
171
171
  var sigMethodNode = _findChild(signedInfo, "SignatureMethod");
172
172
  var sigAlgo = sigMethodNode && _attr(sigMethodNode, "Algorithm");
173
- if (!SUPPORTED_SIG[sigAlgo]) {
173
+ if (!Object.prototype.hasOwnProperty.call(SUPPORTED_SIG, sigAlgo)) {
174
174
  throw new AuthError("auth-saml/unsupported-sig-alg",
175
175
  "Unsupported SignatureMethod: " + sigAlgo);
176
176
  }
@@ -184,7 +184,7 @@ function _verifyXmldsig(envelope, signatureNode, certPem) {
184
184
  var refId = refUri.substring(1);
185
185
  var digestMethodNode = _findChild(refNode, "DigestMethod");
186
186
  var digestAlgo = digestMethodNode && _attr(digestMethodNode, "Algorithm");
187
- if (!SUPPORTED_DIGEST[digestAlgo]) {
187
+ if (!Object.prototype.hasOwnProperty.call(SUPPORTED_DIGEST, digestAlgo)) {
188
188
  throw new AuthError("auth-saml/unsupported-digest",
189
189
  "Unsupported DigestMethod: " + digestAlgo);
190
190
  }
@@ -332,13 +332,18 @@ function _verifyXmldsig(envelope, signatureNode, certPem) {
332
332
  * });
333
333
  */
334
334
  function create(opts) {
335
- validateOpts.requireObject(opts, "auth.saml.sp.create", AuthError);
336
- validateOpts.requireNonEmptyString(opts.entityId, "entityId", AuthError, "auth-saml/no-entity-id");
337
- validateOpts.requireNonEmptyString(opts.assertionConsumerServiceUrl, "assertionConsumerServiceUrl",
338
- AuthError, "auth-saml/no-acs");
339
- validateOpts.requireNonEmptyString(opts.idpEntityId, "idpEntityId", AuthError, "auth-saml/no-idp-entity-id");
340
- validateOpts.requireNonEmptyString(opts.idpSsoUrl, "idpSsoUrl", AuthError, "auth-saml/no-idp-sso");
341
- validateOpts.requireNonEmptyString(opts.idpCertPem, "idpCertPem", AuthError, "auth-saml/no-idp-cert");
335
+ validateOpts.shape(opts, {
336
+ entityId: { rule: "required-string", label: "entityId", code: "auth-saml/no-entity-id" },
337
+ assertionConsumerServiceUrl: { rule: "required-string", label: "assertionConsumerServiceUrl", code: "auth-saml/no-acs" },
338
+ idpEntityId: { rule: "required-string", label: "idpEntityId", code: "auth-saml/no-idp-entity-id" },
339
+ idpSsoUrl: { rule: "required-string", label: "idpSsoUrl", code: "auth-saml/no-idp-sso" },
340
+ idpCertPem: { rule: "required-string", label: "idpCertPem", code: "auth-saml/no-idp-cert" },
341
+ audience: "optional-string",
342
+ clockSkewSec: "optional-non-negative",
343
+ nameIdFormat: "optional-string",
344
+ singleLogoutServiceUrl: "optional-string",
345
+ idpSloUrl: "optional-string",
346
+ }, "auth.saml.sp.create", AuthError);
342
347
 
343
348
  var audience = opts.audience || opts.entityId;
344
349
  var clockSkewSec = typeof opts.clockSkewSec === "number" ? opts.clockSkewSec : 60; // allow:raw-time-literal — clock-skew default
@@ -740,26 +745,46 @@ function create(opts) {
740
745
  var cNotOnOrAfter = _attr(conditions, "NotOnOrAfter");
741
746
  if (cNotBefore) {
742
747
  var cnb = Date.parse(cNotBefore) / 1000; // ms→s
743
- if (isFinite(cnb) && cnb > nowSec + clockSkewSec) {
748
+ // Fail CLOSED on a present-but-unparseable bound (mirrors the Bearer
749
+ // SCD path at line ~708) instead of skipping the window via isFinite().
750
+ if (!isFinite(cnb)) {
751
+ throw new AuthError("auth-saml/conditions-bad-timestamp",
752
+ "Conditions NotBefore is present but unparseable");
753
+ }
754
+ if (cnb > nowSec + clockSkewSec) {
744
755
  throw new AuthError("auth-saml/conditions-not-yet-valid",
745
756
  "Conditions NotBefore is in the future");
746
757
  }
747
758
  }
748
759
  if (cNotOnOrAfter) {
749
760
  var cnoa = Date.parse(cNotOnOrAfter) / 1000; // ms→s
750
- if (isFinite(cnoa) && cnoa < nowSec - clockSkewSec) {
761
+ if (!isFinite(cnoa)) {
762
+ throw new AuthError("auth-saml/conditions-bad-timestamp",
763
+ "Conditions NotOnOrAfter is present but unparseable");
764
+ }
765
+ if (cnoa < nowSec - clockSkewSec) {
751
766
  throw new AuthError("auth-saml/conditions-expired",
752
767
  "Conditions NotOnOrAfter has passed");
753
768
  }
754
769
  }
755
- var ar = _findChild(conditions, "AudienceRestriction", SAML_NS.assertion);
756
- if (ar) {
757
- var audiences = _findAllChildren(ar, "Audience", SAML_NS.assertion).map(_textContent);
758
- if (audiences.indexOf(audience) === -1) {
759
- throw new AuthError("auth-saml/wrong-audience",
760
- "Audience \"" + audience + "\" not in assertion's AudienceRestriction (got " +
761
- JSON.stringify(audiences) + ")");
762
- }
770
+ }
771
+ // Audience binding — a signed assertion is bound to THIS SP via
772
+ // AudienceRestriction. A missing Conditions or AudienceRestriction means
773
+ // it is not bound here (audience-confusion: an assertion minted for another
774
+ // SP). Fail closed when an audience is configured; opt out only via
775
+ // vopts.requireAudienceRestriction === false.
776
+ if (audience && vopts.requireAudienceRestriction !== false) {
777
+ var ar = conditions && _findChild(conditions, "AudienceRestriction", SAML_NS.assertion);
778
+ if (!ar) {
779
+ throw new AuthError("auth-saml/no-audience-restriction",
780
+ "verifyResponse: assertion has no AudienceRestriction binding it to \"" +
781
+ audience + "\" (audience-confusion defense; set requireAudienceRestriction:false to opt out)");
782
+ }
783
+ var audiences = _findAllChildren(ar, "Audience", SAML_NS.assertion).map(_textContent);
784
+ if (audiences.indexOf(audience) === -1) {
785
+ throw new AuthError("auth-saml/wrong-audience",
786
+ "Audience \"" + audience + "\" not in assertion's AudienceRestriction (got " +
787
+ JSON.stringify(audiences) + ")");
763
788
  }
764
789
  }
765
790
 
@@ -973,6 +998,25 @@ function create(opts) {
973
998
  * idpVerifyAlg: "ml-dsa-65",
974
999
  * });
975
1000
  */
1001
+ // _extractRedirectSignature(queryString) — split a SAML HTTP-Redirect
1002
+ // binding query string, pulling the URL-decoded `Signature=` value out and
1003
+ // collecting the remaining (signed) portion in order. Shared by
1004
+ // parseLogoutRequest / parseLogoutResponse so the two cannot drift on which
1005
+ // bytes the signature covers — a drift would be a signature-bypass.
1006
+ function _extractRedirectSignature(queryString) {
1007
+ var parts = queryString.split("&");
1008
+ var sigValue = null;
1009
+ var signedPortion = [];
1010
+ for (var i = 0; i < parts.length; i += 1) {
1011
+ if (parts[i].indexOf("Signature=") === 0) {
1012
+ sigValue = decodeURIComponent(parts[i].slice("Signature=".length));
1013
+ } else {
1014
+ signedPortion.push(parts[i]);
1015
+ }
1016
+ }
1017
+ return { sigValue: sigValue, signedPortion: signedPortion };
1018
+ }
1019
+
976
1020
  function parseLogoutRequest(samlRequestB64, vopts) {
977
1021
  vopts = vopts || {};
978
1022
  if (typeof samlRequestB64 !== "string" || samlRequestB64.length === 0) {
@@ -998,16 +1042,9 @@ function create(opts) {
998
1042
  throw new AuthError("auth-saml/bad-verify-alg",
999
1043
  "parseLogoutRequest: idpVerifyAlg must be 'ml-dsa-65' / 'ml-dsa-87' / 'ed25519'");
1000
1044
  }
1001
- var parts = vopts.queryString.split("&");
1002
- var sigValue = null;
1003
- var signedPortion = [];
1004
- for (var i = 0; i < parts.length; i += 1) {
1005
- if (parts[i].indexOf("Signature=") === 0) {
1006
- sigValue = decodeURIComponent(parts[i].slice("Signature=".length));
1007
- } else {
1008
- signedPortion.push(parts[i]);
1009
- }
1010
- }
1045
+ var sig = _extractRedirectSignature(vopts.queryString);
1046
+ var sigValue = sig.sigValue;
1047
+ var signedPortion = sig.signedPortion;
1011
1048
  if (!sigValue) {
1012
1049
  throw new AuthError("auth-saml/no-signature",
1013
1050
  "parseLogoutRequest: queryString lacks Signature parameter");
@@ -1193,16 +1230,9 @@ function create(opts) {
1193
1230
  throw new AuthError("auth-saml/bad-verify-alg",
1194
1231
  "parseLogoutResponse: idpVerifyAlg must be 'ml-dsa-65' / 'ml-dsa-87' / 'ed25519'");
1195
1232
  }
1196
- var parts = vopts.queryString.split("&");
1197
- var sigValue = null;
1198
- var signedPortion = [];
1199
- for (var i = 0; i < parts.length; i += 1) {
1200
- if (parts[i].indexOf("Signature=") === 0) {
1201
- sigValue = decodeURIComponent(parts[i].slice("Signature=".length));
1202
- } else {
1203
- signedPortion.push(parts[i]);
1204
- }
1205
- }
1233
+ var sig = _extractRedirectSignature(vopts.queryString);
1234
+ var sigValue = sig.sigValue;
1235
+ var signedPortion = sig.signedPortion;
1206
1236
  if (!sigValue) {
1207
1237
  throw new AuthError("auth-saml/no-signature",
1208
1238
  "parseLogoutResponse: queryString lacks Signature parameter");
@@ -1887,7 +1917,7 @@ function _verifyEmbeddedXmlDsig(xml, idpVerifyKey, idpVerifyAlg, expectedRootLoc
1887
1917
  // Allow either sha3-512 (framework default) or the SHA-2 family.
1888
1918
  var digestAlgName;
1889
1919
  if (digestUri === "http://www.w3.org/2007/05/xmldsig-more#sha3-512") digestAlgName = "sha3-512";
1890
- else if (SUPPORTED_DIGEST[digestUri]) digestAlgName = SUPPORTED_DIGEST[digestUri];
1920
+ else if (Object.prototype.hasOwnProperty.call(SUPPORTED_DIGEST, digestUri)) digestAlgName = SUPPORTED_DIGEST[digestUri];
1891
1921
  else {
1892
1922
  throw new AuthError("auth-saml/unsupported-digest",
1893
1923
  "_verifyEmbeddedXmlDsig: DigestMethod " + digestUri + " not supported");
@@ -144,21 +144,9 @@ function create(opts) {
144
144
  var algorithm = _resolveHolderAlg(opts.holderKey, opts.algorithm);
145
145
  var auditOn = opts.auditOn !== false;
146
146
 
147
- function _emitAudit(action, outcome, metadata) {
148
- if (!auditOn) return;
149
- try {
150
- audit().safeEmit({
151
- action: action,
152
- outcome: outcome,
153
- metadata: metadata || {},
154
- });
155
- } catch (_e) { /* drop-silent */ }
156
- }
147
+ var _emitAudit = audit().namespaced(null, { audit: auditOn });
157
148
 
158
- function _emitMetric(verb) {
159
- try { observability().safeEvent("auth.sdJwtVc.holder." + verb, 1, {}); }
160
- catch (_e) { /* drop-silent */ }
161
- }
149
+ var _emitMetric = observability().namespaced("auth.sdJwtVc.holder");
162
150
 
163
151
  async function store(spec) {
164
152
  if (!spec || typeof spec !== "object") {
@@ -104,21 +104,9 @@ function create(opts) {
104
104
  keysRotated: 0,
105
105
  };
106
106
 
107
- function _emitAudit(action, outcome, metadata) {
108
- if (!auditOn) return;
109
- try {
110
- audit().safeEmit({
111
- action: action,
112
- outcome: outcome,
113
- metadata: metadata || {},
114
- });
115
- } catch (_e) { /* drop-silent */ }
116
- }
107
+ var _emitAudit = audit().namespaced(null, { audit: auditOn });
117
108
 
118
- function _emitMetric(verb) {
119
- try { observability().safeEvent("auth.sdJwtVc.issuer." + verb, 1, {}); }
120
- catch (_e) { /* drop-silent */ }
121
- }
109
+ var _emitMetric = observability().namespaced("auth.sdJwtVc.issuer");
122
110
 
123
111
  async function issue(spec) {
124
112
  if (!spec || typeof spec !== "object") {