@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
@@ -172,7 +172,7 @@ function detectMixedScripts(label, allowedScripts) {
172
172
  //
173
173
  // issues.push.apply(issues,
174
174
  // codepointClass.detectCharThreats(text, opts, "html"));
175
- function detectCharThreats(text, opts, codePrefix) {
175
+ function detectCharThreats(text, opts, codePrefix, zeroWidthSeverity) {
176
176
  var issues = [];
177
177
  if (typeof text !== "string") return issues;
178
178
  if (opts && opts.bidiPolicy !== "allow") {
@@ -208,6 +208,28 @@ function detectCharThreats(text, opts, codePrefix) {
208
208
  });
209
209
  }
210
210
  }
211
+ // Zero-width / invisible-formatting chars — the fourth Trojan-source-class
212
+ // character threat, detected here alongside its siblings so no guard
213
+ // hand-rolls it. OPT-IN AND severity via zeroWidthSeverity: a caller that
214
+ // wants zero-width detection passes the context-appropriate severity
215
+ // ("high" where an invisible char spoofs an identifier / filename / line of
216
+ // text; "warn" where it is cosmetic), and omitting it skips the scan. Gated
217
+ // further on a defined non-`allow` zeroWidthPolicy — flagged under `strip`
218
+ // too (like bidi / null / control) so a zero-width-only input under `strip`
219
+ // reaches the sanitizer and is removed rather than served unchanged.
220
+ if (zeroWidthSeverity && opts && opts.zeroWidthPolicy &&
221
+ opts.zeroWidthPolicy !== "allow") {
222
+ var zwMatch = text.match(ZERO_WIDTH_RE);
223
+ if (zwMatch) {
224
+ issues.push({
225
+ kind: "zero-width", severity: zeroWidthSeverity,
226
+ ruleId: codePrefix + ".zero-width",
227
+ location: zwMatch.index,
228
+ snippet: "zero-width / invisible-formatting char U+" +
229
+ zwMatch[0].charCodeAt(0).toString(HEX_RADIX) + " at byte " + zwMatch.index,
230
+ });
231
+ }
232
+ }
211
233
  return issues;
212
234
  }
213
235
 
@@ -292,7 +314,68 @@ function isUnreserved(cc) {
292
314
  cc === 0x7e; // ~
293
315
  }
294
316
 
317
+ // isForbiddenControlChar — the header-injection / RFC 5322 control-byte
318
+ // predicate every "refuse control bytes in a header / line / value" loop
319
+ // shares across the mail guards (dsn / imap / list-id / list-unsubscribe /
320
+ // mail-compose / mail-sieve / managesieve) and the safe-* text parsers
321
+ // (ical / mime / vcard / toml). TRUE for DEL (0x7f) and any C0 control
322
+ // (< 0x20) other than TAB (0x09); LF (0x0a) and CR (0x0d) are refused by
323
+ // default but can be permitted per call (a line reader that has already
324
+ // split on CRLF, or a grammar that allows folding). Distinct from the
325
+ // C0_CTRL_RANGES regex above, which is the stripping/scanning table that
326
+ // always exempts LF/CR and never matches DEL.
327
+ //
328
+ // opts.forbidTab — TAB (0x09) is normally permitted (it is folding
329
+ // whitespace in RFC 5322 header values). The stricter identifier / key /
330
+ // name / single-line-value contexts (idempotency keys, step-up challenge
331
+ // values, mailbox / folder / message-id names, assembly ids, DNSBL-free
332
+ // single-line values) forbid EVERY C0 control plus DEL — there is no
333
+ // folding to honour — so they pass `{ forbidTab: true }`, making the
334
+ // predicate exactly `code < 0x20 || code === 0x7f`.
335
+ function isForbiddenControlChar(code, opts) {
336
+ if (code === 0x7f) return true; // DEL
337
+ if (code >= 0x20) return false;
338
+ if (code === 0x09 && (!opts || !opts.forbidTab)) return false; // TAB — permitted unless forbidTab
339
+ if (opts) {
340
+ if (opts.allowLf && code === 0x0a) return false;
341
+ if (opts.allowCr && code === 0x0d) return false;
342
+ }
343
+ return true;
344
+ }
345
+
346
+ // firstControlCharOffset — index of the first isForbiddenControlChar in `s`
347
+ // (under the same opts), or -1 when none. Callers wrap it as a boolean
348
+ // (`offset !== -1`), throw with the offending code (`s.charCodeAt(offset)`),
349
+ // or derive a byte offset from the index — replacing the open-coded
350
+ // `for (i) { c = charCodeAt(i); if (c === 0 || c === 0x7f || (c < 0x20 &&
351
+ // c !== 0x09)) … }` scan each parser previously rolled by hand.
352
+ function firstControlCharOffset(s, opts) {
353
+ for (var i = 0; i < s.length; i += 1) {
354
+ if (isForbiddenControlChar(s.charCodeAt(i), opts)) return i;
355
+ }
356
+ return -1;
357
+ }
358
+
359
+ // Decode HTML numeric character references (hex &#x..; and decimal &#..;) just
360
+ // enough to expose a scheme hidden behind entity-encoding. The trailing
361
+ // semicolon is OPTIONAL — a browser decodes `&#106avascript:` (no semicolon)
362
+ // the same as `&#106;avascript:`, so a semicolon-required decoder lets the
363
+ // no-semicolon form bypass a scheme allowlist. Shared so guard-html / guard-svg
364
+ // / guard-markdown cannot drift on this (the bug class that shipped one buggy
365
+ // + one correct copy).
366
+ var NUMERIC_ENTITY_RE_G = /&#(?:x([0-9a-f]+)|(\d+));?/gi;
367
+ function decodeNumericEntities(s) {
368
+ return String(s == null ? "" : s).replace(NUMERIC_ENTITY_RE_G, function (m, hex, dec) {
369
+ var cp = hex !== undefined ? parseInt(hex, 16) : parseInt(dec, 10);
370
+ if (!isFinite(cp) || cp < 0 || cp > 0x10FFFF) return m;
371
+ try { return String.fromCodePoint(cp); } catch (_e) { return m; }
372
+ });
373
+ }
374
+
295
375
  module.exports = {
376
+ isForbiddenControlChar: isForbiddenControlChar,
377
+ firstControlCharOffset: firstControlCharOffset,
378
+ decodeNumericEntities: decodeNumericEntities,
296
379
  isAsciiAlnum: isAsciiAlnum,
297
380
  isUnreserved: isUnreserved,
298
381
  hex4: hex4,
@@ -131,12 +131,7 @@ function emit(event) {
131
131
  // namespace uses underscores, so the kind is rewritten before emit.
132
132
  try {
133
133
  var kindCanonical = String(event.kind || "log").replace(/-/g, "_");
134
- audit().safeEmit({
135
- action: "compliance.aiact." + kindCanonical,
136
- outcome: event.outcome || "success",
137
- actor: event.actor || null,
138
- metadata: event,
139
- });
134
+ audit().namespaced("compliance.aiact")(kindCanonical, event.outcome || "success", event, { actor: event.actor || null });
140
135
  } catch (_e) { /* drop-silent */ }
141
136
  return event;
142
137
  }
@@ -39,6 +39,7 @@
39
39
  */
40
40
 
41
41
  var validateOpts = require("./validate-opts");
42
+ var markupEscape = require("./markup-escape").markupEscape;
42
43
  var { ComplianceError } = require("./framework-error");
43
44
 
44
45
  var BANNER_KINDS = Object.freeze([
@@ -118,10 +119,7 @@ function htmlBanner(opts) {
118
119
 
119
120
  function _escapeHtml(s) {
120
121
  if (typeof s !== "string") return "";
121
- return s.replace(/&/g, "&amp;")
122
- .replace(/</g, "&lt;")
123
- .replace(/>/g, "&gt;")
124
- .replace(/"/g, "&quot;");
122
+ return markupEscape(s);
125
123
  }
126
124
 
127
125
  // Watermark builder for Art. 50(2). Operators integrate with C2PA /
@@ -66,22 +66,12 @@ function create(opts) {
66
66
  var standards = opts.standards.slice();
67
67
  var contact = opts.contact || null;
68
68
  var supervisoryAuthority = opts.supervisoryAuthority || null;
69
- var auditOn = opts.audit !== false;
70
69
  var now = typeof opts.now === "function" ? opts.now : function () { return Date.now(); };
71
70
 
72
71
  var criteria = new Map();
73
72
  var nonConformances = [];
74
73
 
75
- function _emitAudit(action, outcome, metadata) {
76
- if (!auditOn) return;
77
- try {
78
- audit().safeEmit({
79
- action: "compliance.eaa." + action,
80
- outcome: outcome,
81
- metadata: metadata || {},
82
- });
83
- } catch (_e) { /* drop-silent */ }
84
- }
74
+ var _emitAudit = audit().namespaced("compliance.eaa", opts.audit);
85
75
 
86
76
  function declareCriterion(id, decl) {
87
77
  if (typeof id !== "string" || id.length === 0) {
@@ -92,7 +82,7 @@ function create(opts) {
92
82
  throw new ComplianceEaaError("compliance-eaa/bad-decl",
93
83
  "compliance.eaa.declareCriterion: decl must be an object with { conformance, note? }");
94
84
  }
95
- if (!VALID_CONFORMANCE[decl.conformance]) {
85
+ if (!Object.prototype.hasOwnProperty.call(VALID_CONFORMANCE, decl.conformance)) {
96
86
  throw new ComplianceEaaError("compliance-eaa/bad-conformance",
97
87
  "compliance.eaa.declareCriterion: conformance must be one of " + Object.keys(VALID_CONFORMANCE).join(", "));
98
88
  }
@@ -65,20 +65,25 @@ function create(opts) {
65
65
  "fetchOnStart", "audit",
66
66
  ], "compliance.sanctions.fetcher.create");
67
67
 
68
- if (!opts.screener || typeof opts.screener.reload !== "function") {
69
- throw new SanctionsFetcherError("sanctions-fetcher/bad-screener",
70
- "fetcher.create: screener must be a sanctions.create() instance");
71
- }
72
- if (typeof opts.fetch !== "function") {
73
- throw new SanctionsFetcherError("sanctions-fetcher/bad-fetch",
74
- "fetcher.create: fetch must be an async function returning entry[]");
75
- }
76
- validateOpts.optionalPositiveFinite(opts.intervalMs,
77
- "fetcher.create: intervalMs", SanctionsFetcherError, "sanctions-fetcher/bad-opts");
78
- validateOpts.optionalFunction(opts.onRefreshed,
79
- "fetcher.create: onRefreshed", SanctionsFetcherError, "sanctions-fetcher/bad-opts");
80
- validateOpts.optionalFunction(opts.onError,
81
- "fetcher.create: onError", SanctionsFetcherError, "sanctions-fetcher/bad-opts");
68
+ validateOpts.shape(opts, {
69
+ screener: function (value) {
70
+ if (!value || typeof value.reload !== "function") {
71
+ throw new SanctionsFetcherError("sanctions-fetcher/bad-screener",
72
+ "fetcher.create: screener must be a sanctions.create() instance");
73
+ }
74
+ },
75
+ fetch: function (value) {
76
+ if (typeof value !== "function") {
77
+ throw new SanctionsFetcherError("sanctions-fetcher/bad-fetch",
78
+ "fetcher.create: fetch must be an async function returning entry[]");
79
+ }
80
+ },
81
+ intervalMs: "optional-positive-finite",
82
+ onRefreshed: "optional-function",
83
+ onError: "optional-function",
84
+ fetchOnStart: "optional-boolean",
85
+ audit: "optional-boolean",
86
+ }, "fetcher.create", SanctionsFetcherError, "sanctions-fetcher/bad-opts");
82
87
 
83
88
  var intervalMs = opts.intervalMs || C.TIME.hours(24);
84
89
  var fetchOnStart = opts.fetchOnStart !== false;
@@ -93,21 +98,9 @@ function create(opts) {
93
98
  var refreshCount = 0;
94
99
  var failureCount = 0;
95
100
 
96
- function _emitAudit(action, outcome, metadata) {
97
- if (!auditOn) return;
98
- try {
99
- audit().safeEmit({
100
- action: action,
101
- outcome: outcome,
102
- metadata: metadata || {},
103
- });
104
- } catch (_e) { /* drop-silent */ }
105
- }
101
+ var _emitAudit = audit().namespaced(null, { audit: auditOn });
106
102
 
107
- function _emitMetric(verb, n) {
108
- try { observability().safeEvent("compliance.sanctions.fetcher." + verb, n || 1, {}); }
109
- catch (_e) { /* drop-silent */ }
110
- }
103
+ var _emitMetric = observability().namespaced("compliance.sanctions.fetcher");
111
104
 
112
105
  async function _tick() {
113
106
  if (stopping) return;
@@ -284,21 +284,9 @@ function create(opts) {
284
284
  // strings.
285
285
  var index = opts.entries.map(_normalizeEntry);
286
286
 
287
- function _emitAudit(action, outcome, metadata) {
288
- if (!auditOn) return;
289
- try {
290
- audit().safeEmit({
291
- action: action,
292
- outcome: outcome,
293
- metadata: metadata || {},
294
- });
295
- } catch (_e) { /* drop-silent — audit sink */ }
296
- }
287
+ var _emitAudit = audit().namespaced(null, { audit: auditOn });
297
288
 
298
- function _emitMetric(verb, n, labels) {
299
- try { observability().safeEvent("compliance.sanctions." + verb, n || 1, labels || {}); }
300
- catch (_e) { /* drop-silent */ }
301
- }
289
+ var _emitMetric = observability().namespaced("compliance.sanctions");
302
290
 
303
291
  function _exactMatch(qNorm, candidate) {
304
292
  for (var i = 0; i < candidate._allNamesNormalized.length; i++) {
@@ -380,13 +368,14 @@ function create(opts) {
380
368
 
381
369
  for (var c = 0; c < index.length; c++) {
382
370
  var candidate = index[c];
383
- // Type filter: when input.type is set, skip candidates of
384
- // the wrong type unless candidate is an entity (entities can
385
- // be matched regardless to catch operator-side type errors).
386
- if (input.type && candidate.type !== input.type &&
387
- candidate.type !== "entity") {
388
- continue;
389
- }
371
+ // A sanctions screen MUST over-match on the legally-operative NAME. The
372
+ // operator-asserted input.type is an unverified counterparty
373
+ // self-classification, NOT data-driven non-match confidence using it to
374
+ // EXCLUDE a name hit inverts the screen to under-match (a false negative
375
+ // that processes a payment which should have been blocked). So never skip
376
+ // a candidate on type; the name match runs against every record and the
377
+ // type mismatch is surfaced as a non-dispositive `typeMatch` signal on
378
+ // each hit for operator triage.
390
379
 
391
380
  var bestForCandidate = { score: 0, name: "" };
392
381
  for (var qi = 0; qi < queryNames.length; qi++) {
@@ -416,6 +405,7 @@ function create(opts) {
416
405
  listed: candidate.listedAt,
417
406
  programs: candidate.programs,
418
407
  type: candidate.type,
408
+ typeMatch: input.type ? candidate.type === input.type : null,
419
409
  country: candidate.country,
420
410
  });
421
411
  }
package/lib/compliance.js CHANGED
@@ -47,6 +47,7 @@ var aiAct = require("./compliance-ai-act");
47
47
  var { ComplianceError } = require("./framework-error");
48
48
 
49
49
  var audit = lazyRequire(function () { return require("./audit"); });
50
+ var auditEmit = require("./audit-emit");
50
51
  var retentionMod = lazyRequire(function () { return require("./retention"); });
51
52
  var db = lazyRequire(function () { return require("./db"); });
52
53
  var cryptoField = lazyRequire(function () { return require("./crypto-field"); });
@@ -316,15 +317,7 @@ var ARTIFACT_STANDARDS = Object.freeze([
316
317
 
317
318
  var STATE = { posture: null, setAt: null, fipsMode: false };
318
319
 
319
- function _emitAudit(action, metadata, outcome) {
320
- try {
321
- audit().safeEmit({
322
- action: action,
323
- outcome: outcome || "success",
324
- metadata: metadata,
325
- });
326
- } catch (_e) { /* audit best-effort */ }
327
- }
320
+ var _emitAudit = auditEmit.emit;
328
321
 
329
322
  /**
330
323
  * @primitive b.compliance.set
@@ -960,7 +953,7 @@ var REGIME_MAP = Object.freeze({
960
953
  * b.compliance.describe("not-a-real-posture"); // → null
961
954
  */
962
955
  function describe(posture) {
963
- return REGIME_MAP[posture] || null;
956
+ return Object.prototype.hasOwnProperty.call(REGIME_MAP, posture) ? REGIME_MAP[posture] : null;
964
957
  }
965
958
 
966
959
  // POSTURE_DEFAULTS — per-posture configuration knobs that primitives
@@ -35,7 +35,6 @@
35
35
  * @card
36
36
  * Monitor + alert when runtime config diverges from a declared baseline.
37
37
  */
38
- var nodeFs = require("node:fs");
39
38
  var nodePath = require("node:path");
40
39
  var auditSign = require("./audit-sign");
41
40
  var canonicalJson = require("./canonical-json");
@@ -45,8 +44,10 @@ var safeJson = require("./safe-json");
45
44
  var validateOpts = require("./validate-opts");
46
45
  var { defineClass } = require("./framework-error");
47
46
  var atomicFile = require("./atomic-file");
47
+ var C = require("./constants");
48
48
 
49
49
  var audit = lazyRequire(function () { return require("./audit"); });
50
+ var auditEmit = require("./audit-emit");
50
51
 
51
52
  var ConfigDriftError = defineClass("ConfigDriftError", { alwaysPermanent: true });
52
53
  var _err = ConfigDriftError.factory;
@@ -159,23 +160,16 @@ function create(opts) {
159
160
  var sidecarPath = nodePath.join(dataDir,
160
161
  baselineName === "default" ? SIDECAR_NAME : ("config-baseline-" + baselineName + ".sig"));
161
162
 
162
- function _emit(action, info, outcome) {
163
- if (!auditOn) return;
164
- var sink = auditInstance || audit();
165
- try {
166
- sink.safeEmit({
167
- action: action,
168
- outcome: outcome,
169
- metadata: info || {},
170
- reason: info && info.reason ? info.reason : null,
171
- });
172
- } catch (_e) { /* audit best-effort */ }
173
- }
163
+ var _emit = auditEmit.gatedReasonEmitter({ audit: auditOn, sink: auditInstance });
174
164
 
175
165
  function _readSidecar() {
176
- if (!nodeFs.existsSync(sidecarPath)) return null;
166
+ // Capped fd-bound read (no existsSync check-then-read window): the signed
167
+ // config-baseline sidecar is parsed + verified, so a tampered multi-GB file
168
+ // would OOM the reader before signature verify. refuseSymlink stays OFF: the
169
+ // data dir may be a symlink-mounted volume (k8s PVC). Any read failure
170
+ // (missing / too-large) → null, the existing "no baseline yet" behavior.
177
171
  var raw;
178
- try { raw = nodeFs.readFileSync(sidecarPath, "utf8"); }
172
+ try { raw = atomicFile.fdSafeReadSync(sidecarPath, { maxBytes: C.BYTES.mib(1), encoding: "utf8" }); }
179
173
  catch (_e) { return null; }
180
174
  var parsed;
181
175
  try { parsed = safeJson.parse(raw); }
@@ -200,9 +194,12 @@ function create(opts) {
200
194
  publicKeyPem: auditSign.getPublicKey(),
201
195
  snapshot: snapshot,
202
196
  };
203
- var tmp = sidecarPath + ".tmp";
204
- nodeFs.writeFileSync(tmp, JSON.stringify(payload, null, 2));
205
- atomicFile.renameWithRetry(tmp, sidecarPath);
197
+ // Atomic, symlink-refusing write. The previous hand-rolled form staged
198
+ // into a PREDICTABLE temp name (`sidecarPath + ".tmp"`) via a plain
199
+ // writeFileSync, so an attacker could pre-plant a symlink at that exact
200
+ // path and have the signed sidecar written through it (CWE-59 / CWE-377).
201
+ // writeSync uses a CSPRNG temp name opened O_EXCL | O_NOFOLLOW.
202
+ atomicFile.writeSync(sidecarPath, JSON.stringify(payload, null, 2), { fileMode: 0o600 });
206
203
  }
207
204
 
208
205
  function _verifySidecar(parsed) {
@@ -377,7 +374,10 @@ function verifyVendorIntegrity(opts) {
377
374
  var libVendorDir = opts.libVendorDir || nodePath.join(__dirname, "vendor");
378
375
  var manifestPath = opts.manifestPath || nodePath.join(libVendorDir, "MANIFEST.json");
379
376
  var raw;
380
- try { raw = nodeFs.readFileSync(manifestPath, "utf8"); }
377
+ // Capped fd-bound read of the vendor MANIFEST.json (operator-bundled, install-
378
+ // time). refuseSymlink OFF — the vendored tree ships read-only and an install
379
+ // may symlink lib/. The cap precedes the alloc.
380
+ try { raw = atomicFile.fdSafeReadSync(manifestPath, { maxBytes: C.BYTES.mib(4), encoding: "utf8" }); }
381
381
  catch (_e) {
382
382
  throw _err("VENDOR_MANIFEST_MISSING",
383
383
  "vendor MANIFEST.json missing at " + manifestPath, true);
@@ -407,7 +407,11 @@ function verifyVendorIntegrity(opts) {
407
407
  var abs = nodePath.isAbsolute(rel) ? rel : nodePath.join(libVendorDir, relInVendor);
408
408
  var actual;
409
409
  try {
410
- var bytes = nodeFs.readFileSync(abs);
410
+ // Capped fd-bound read (raw bytes hashing, no encoding). Sanity ceiling
411
+ // so a corrupted/huge vendored file is a read-failed mismatch (caught
412
+ // below) rather than an OOM in the boot integrity loop. NO refuseSymlink
413
+ // (vendored tree ships read-only; installs may symlink lib/).
414
+ var bytes = atomicFile.fdSafeReadSync(abs, { maxBytes: C.BYTES.mib(64) });
411
415
  actual = "sha256:" + require("node:crypto")
412
416
  .createHash("sha256").update(bytes).digest("hex");
413
417
  } catch (_e) {
@@ -31,6 +31,7 @@
31
31
  var nodeCrypto = require("node:crypto");
32
32
  var C = require("./constants");
33
33
  var bCrypto = require("./crypto");
34
+ var x509Chain = require("./x509-chain");
34
35
  var canonicalJson = require("./canonical-json");
35
36
  var safeJson = require("./safe-json");
36
37
  var validateOpts = require("./validate-opts");
@@ -1103,7 +1104,7 @@ function attachIdentityAssertion(opts) {
1103
1104
  validateOpts.requireObject(opts, "contentCredentials.attachIdentityAssertion", ContentCredentialsError);
1104
1105
  validateOpts(opts, ["binding", "subject", "referencedAssertions", "privateKeyPem", "audit"],
1105
1106
  "contentCredentials.attachIdentityAssertion");
1106
- if (typeof opts.binding !== "string" || !IDENTITY_BINDINGS[opts.binding]) {
1107
+ if (typeof opts.binding !== "string" || !Object.prototype.hasOwnProperty.call(IDENTITY_BINDINGS, opts.binding)) {
1107
1108
  throw ContentCredentialsError.factory("content-credentials/bad-identity-binding",
1108
1109
  "attachIdentityAssertion: binding must be one of " + Object.keys(IDENTITY_BINDINGS).join(" / "));
1109
1110
  }
@@ -1217,7 +1218,7 @@ function verifyIdentityAssertion(assertion, publicKeyPem, opts) {
1217
1218
  return _fail("public-key-required");
1218
1219
  }
1219
1220
  var sp = assertion.signer_payload;
1220
- if (!sp || typeof sp !== "object" || typeof sp.binding !== "string" || !IDENTITY_BINDINGS[sp.binding] ||
1221
+ if (!sp || typeof sp !== "object" || typeof sp.binding !== "string" || !Object.prototype.hasOwnProperty.call(IDENTITY_BINDINGS, sp.binding) ||
1221
1222
  !Array.isArray(sp.referenced_assertions)) {
1222
1223
  return _fail("signer-payload-shape");
1223
1224
  }
@@ -1334,9 +1335,10 @@ function _verifyIdentityX509Chain(certChainPem, trustAnchorsPem) {
1334
1335
  // only direct-root / self-signed leaves.
1335
1336
  for (var li = 0; li < certs.length - 1; li += 1) {
1336
1337
  var child = certs[li], parent = certs[li + 1];
1337
- var linked = false;
1338
- try { linked = child.checkIssued(parent) && child.verify(parent.publicKey); }
1339
- catch (_eLink) { linked = false; }
1338
+ // issuerValidlyIssued enforces basicConstraints cA:TRUE on the parent in
1339
+ // addition to the issuance + signature linkage — a non-CA cert cannot be
1340
+ // an intermediate issuer (basicConstraints bypass, CVE-2002-0862 class).
1341
+ var linked = x509Chain.issuerValidlyIssued(parent, child);
1340
1342
  if (!linked) { return { ok: false, reason: "broken-chain" }; }
1341
1343
  }
1342
1344
  // The top of the presented chain must chain to (or BE) a trust anchor.
@@ -1347,8 +1349,9 @@ function _verifyIdentityX509Chain(certChainPem, trustAnchorsPem) {
1347
1349
  if (top.fingerprint256 === anchor.fingerprint256) {
1348
1350
  chained = true; // top of the chain IS the anchor (root-in-chain or self-signed leaf == anchor)
1349
1351
  } else {
1350
- try { chained = top.checkIssued(anchor) && top.verify(anchor.publicKey); }
1351
- catch (_eChk) { chained = false; }
1352
+ // issuerValidlyIssued enforces basicConstraints cA:TRUE on the anchor
1353
+ // in addition to issuance + signature (basicConstraints bypass class).
1354
+ chained = x509Chain.issuerValidlyIssued(anchor, top);
1352
1355
  }
1353
1356
  if (chained) {
1354
1357
  if (now < anchor.validFromDate.getTime() || now > anchor.validToDate.getTime()) {
@@ -1427,7 +1430,7 @@ function cacImplicitLabel(opts) {
1427
1430
  throw new ContentCredentialsError("cac-implicit-label/bad-content-id",
1428
1431
  "cacImplicitLabel: contentId must match [A-Za-z0-9._:/-]");
1429
1432
  }
1430
- if (typeof opts.contentKind !== "string" || !CAC_KIND_ENUM[opts.contentKind]) {
1433
+ if (typeof opts.contentKind !== "string" || !Object.prototype.hasOwnProperty.call(CAC_KIND_ENUM, opts.contentKind)) {
1431
1434
  throw new ContentCredentialsError("cac-implicit-label/bad-content-kind",
1432
1435
  "cacImplicitLabel: contentKind must be one of " +
1433
1436
  Object.keys(CAC_KIND_ENUM).join("/"));
@@ -29,6 +29,7 @@
29
29
 
30
30
  var nodeCrypto = require("node:crypto");
31
31
  var bCrypto = require("./crypto");
32
+ var safeBuffer = require("./safe-buffer");
32
33
  var structuredFields = require("./structured-fields");
33
34
  var validateOpts = require("./validate-opts");
34
35
  var { defineClass } = require("./framework-error");
@@ -59,12 +60,12 @@ function _strictBase64(s, what) {
59
60
  return buf;
60
61
  }
61
62
 
62
- function _bodyBytes(body, what) {
63
- if (Buffer.isBuffer(body)) return body;
64
- if (body instanceof Uint8Array) return Buffer.from(body);
65
- if (typeof body === "string") return Buffer.from(body, "utf8");
66
- throw new ContentDigestError("content-digest/bad-body", "contentDigest: " + what + " must be a Buffer / Uint8Array / string");
67
- }
63
+ var _bodyBytes = safeBuffer.makeByteCoercer({
64
+ errorClass: ContentDigestError,
65
+ typeCode: "content-digest/bad-body",
66
+ messagePrefix: "contentDigest: ",
67
+ messageSuffix: " must be a Buffer / Uint8Array / string",
68
+ });
68
69
 
69
70
  /**
70
71
  * @primitive b.contentDigest.create
@@ -98,9 +99,12 @@ function create(body, opts) {
98
99
  if (!Array.isArray(algos) || algos.length === 0) throw new ContentDigestError("content-digest/bad-arg", "contentDigest.create: opts.algorithms must be a non-empty array");
99
100
  var members = algos.map(function (a) {
100
101
  var name = String(a).toLowerCase();
101
- var nodeAlg = ACTIVE[name];
102
+ // hasOwnProperty: the algorithm name is operator/caller input; a bracket
103
+ // lookup lets "constructor"/"toString" inherit a truthy value off the
104
+ // prototype and pass the support check (proto shadowing).
105
+ var nodeAlg = Object.prototype.hasOwnProperty.call(ACTIVE, name) ? ACTIVE[name] : undefined;
102
106
  if (!nodeAlg) {
103
- if (DEPRECATED[name]) throw new ContentDigestError("content-digest/insecure-algorithm", "contentDigest.create: '" + name + "' is a deprecated/insecure digest algorithm (RFC 9530 §6); use sha-256 or sha-512");
107
+ if (Object.prototype.hasOwnProperty.call(DEPRECATED, name)) throw new ContentDigestError("content-digest/insecure-algorithm", "contentDigest.create: '" + name + "' is a deprecated/insecure digest algorithm (RFC 9530 §6); use sha-256 or sha-512");
104
108
  throw new ContentDigestError("content-digest/unsupported-algorithm", "contentDigest.create: unsupported digest algorithm '" + name + "'");
105
109
  }
106
110
  var digest = nodeCrypto.createHash(nodeAlg).update(bytes).digest("base64");
@@ -150,10 +154,15 @@ function verify(fieldValue, body, opts) {
150
154
  if (m === "") continue;
151
155
  var eq = m.indexOf("=");
152
156
  if (eq < 1) throw new ContentDigestError("content-digest/bad-field", "contentDigest.verify: malformed dictionary member");
153
- var name = m.slice(0, eq).trim().toLowerCase();
154
- var raw = m.slice(eq + 1).trim();
157
+ var kvp = structuredFields.parseKeyValuePiece(m);
158
+ var name = kvp.key;
159
+ var raw = kvp.value.trim();
160
+ // hasOwnProperty: `name` is the algorithm token from the untrusted inbound
161
+ // Content-Digest header; a bracket lookup lets "constructor"/"__proto__"
162
+ // inherit a truthy value off the prototype and reach createHash (proto
163
+ // shadowing).
164
+ if (!Object.prototype.hasOwnProperty.call(ACTIVE, name)) continue; // ignore legacy / unknown entries
155
165
  var nodeAlg = ACTIVE[name];
156
- if (!nodeAlg) continue; // ignore legacy / unknown entries
157
166
  if (raw.length < 2 || raw.charAt(0) !== ":" || raw.charAt(raw.length - 1) !== ":") {
158
167
  throw new ContentDigestError("content-digest/bad-field", "contentDigest.verify: '" + name + "' value is not an RFC 8941 byte sequence (:base64:)");
159
168
  }
@@ -170,7 +179,7 @@ function verify(fieldValue, body, opts) {
170
179
  if (!Array.isArray(opts.required)) throw new ContentDigestError("content-digest/bad-arg", "contentDigest.verify: opts.required must be an array");
171
180
  for (var r = 0; r < opts.required.length; r++) {
172
181
  var req = String(opts.required[r]).toLowerCase();
173
- if (!ACTIVE[req]) throw new ContentDigestError("content-digest/unsupported-algorithm", "contentDigest.verify: required algorithm '" + req + "' is not a modern digest");
182
+ if (!Object.prototype.hasOwnProperty.call(ACTIVE, req)) throw new ContentDigestError("content-digest/unsupported-algorithm", "contentDigest.verify: required algorithm '" + req + "' is not a modern digest");
174
183
  if (!seen[req]) throw new ContentDigestError("content-digest/missing-algorithm", "contentDigest.verify: required digest '" + req + "' is not present");
175
184
  }
176
185
  }
package/lib/cookies.js CHANGED
@@ -48,6 +48,7 @@
48
48
  */
49
49
 
50
50
  var C = require("./constants");
51
+ var structuredFields = require("./structured-fields");
51
52
  var validateOpts = require("./validate-opts");
52
53
  var { FrameworkError } = require("./framework-error");
53
54
 
@@ -95,7 +96,11 @@ function _validateValue(value) {
95
96
  // Length cap before the regex test (defense in depth — the regex
96
97
  // here is a simple character class, but the same discipline that
97
98
  // bounds longer regexes elsewhere applies).
98
- if (value.length > MAX_VALUE_LENGTH || FORBIDDEN_VALUE_RE.test(value)) {
99
+ // Byte length, not value.length (UTF-16 code units): the cap bounds the
100
+ // on-the-wire Set-Cookie size, and a multibyte value is 2-4x larger in bytes
101
+ // than chars, so a char-count check under-enforces and a 4096-char value can
102
+ // emit a ~12 KiB+ header.
103
+ if (Buffer.byteLength(value, "utf8") > MAX_VALUE_LENGTH || FORBIDDEN_VALUE_RE.test(value)) {
99
104
  throw new CookieError("cookies/invalid-value",
100
105
  "cookie value is too long or contains forbidden control character (CRLF/NUL/;/,)");
101
106
  }
@@ -107,7 +112,11 @@ function _validateValue(value) {
107
112
  // header — never trust unscrubbed values reach the wire.
108
113
  function _scrubAttr(s) {
109
114
  if (typeof s !== "string") return s;
110
- return s.replace(/[\r\n\0]/g, ""); // allow:duplicate-regex CR/LF/NUL header-injection rejection appears in cookies / mail / security-headers; each is the boundary primitive for its domain
115
+ // `;` joins Set-Cookie attributes, so an unscrubbed `;` in Path/SameSite is
116
+ // attribute injection (e.g. Path=/x; HttpOnly). Domain is regex-validated
117
+ // upstream; Path/SameSite only flow through here, so strip `;` alongside the
118
+ // CR/LF/NUL header-injection set.
119
+ return s.replace(/[\r\n\0;]/g, ""); // allow:duplicate-regex — CR/LF/NUL(+;) header-injection rejection appears in cookies / mail / security-headers; each is the boundary primitive for its domain
111
120
 
112
121
  }
113
122
 
@@ -143,9 +152,7 @@ function parse(cookieHeader) {
143
152
  if (!k) continue;
144
153
  var v = pair.slice(eq + 1).trim();
145
154
  // Strip surrounding double-quotes per RFC 6265 §5.2.
146
- if (v.length >= 2 && v.charAt(0) === '"' && v.charAt(v.length - 1) === '"') {
147
- v = v.slice(1, -1);
148
- }
155
+ v = structuredFields.stripDoubleQuotes(v);
149
156
  try { v = decodeURIComponent(v); }
150
157
  catch (_e) { /* malformed encoding — keep raw */ }
151
158
  // Last write wins per RFC; matches every browser's behavior.
@@ -386,10 +393,8 @@ function create(opts) {
386
393
  }
387
394
 
388
395
  function _requireVault() {
389
- if (!vault || typeof vault.seal !== "function" || typeof vault.unseal !== "function") {
390
- throw new CookieError("cookies/no-vault",
391
- "sealed cookies require opts.vault (a value with .seal/.unseal)");
392
- }
396
+ validateOpts.requireMethods(vault, ["seal", "unseal"],
397
+ "sealed cookies: opts.vault", CookieError, "cookies/no-vault");
393
398
  }
394
399
 
395
400
  // Vault.seal returns "vault:<base64>". We strip the constant prefix
@@ -515,10 +520,7 @@ function parseSafe(cookieHeader, opts) {
515
520
  });
516
521
  continue;
517
522
  }
518
- var v = pair.slice(eq + 1).trim();
519
- if (v.length >= 2 && v.charAt(0) === '"' && v.charAt(v.length - 1) === '"') {
520
- v = v.slice(1, -1);
521
- }
523
+ var v = structuredFields.stripDoubleQuotes(pair.slice(eq + 1).trim());
522
524
  try { v = decodeURIComponent(v); }
523
525
  catch (_e) { /* malformed encoding — keep raw */ }
524
526