@blamejs/blamejs-shop 0.4.68 → 0.4.70

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 (298) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/lib/admin.js +8 -6
  3. package/lib/asset-manifest.json +1 -1
  4. package/lib/giftcards.js +29 -5
  5. package/lib/security-middleware.js +65 -23
  6. package/lib/storefront.js +10 -7
  7. package/lib/vendor/MANIFEST.json +319 -267
  8. package/lib/vendor/blamejs/CHANGELOG.md +2 -0
  9. package/lib/vendor/blamejs/api-snapshot.json +58 -5
  10. package/lib/vendor/blamejs/examples/wiki/README.md +1 -1
  11. package/lib/vendor/blamejs/examples/wiki/docker-compose.prod.yml +9 -8
  12. package/lib/vendor/blamejs/examples/wiki/docker-compose.yml +10 -9
  13. package/lib/vendor/blamejs/examples/wiki/env-snapshot.json +4 -3
  14. package/lib/vendor/blamejs/examples/wiki/lib/build-app.js +33 -17
  15. package/lib/vendor/blamejs/examples/wiki/routes/admin.js +7 -10
  16. package/lib/vendor/blamejs/examples/wiki/server.js +5 -4
  17. package/lib/vendor/blamejs/examples/wiki/test/e2e.js +5 -0
  18. package/lib/vendor/blamejs/lib/a2a-tasks.js +38 -6
  19. package/lib/vendor/blamejs/lib/agent-event-bus.js +13 -0
  20. package/lib/vendor/blamejs/lib/agent-idempotency.js +5 -1
  21. package/lib/vendor/blamejs/lib/agent-snapshot.js +32 -2
  22. package/lib/vendor/blamejs/lib/ai-aedt-bias-audit.js +2 -1
  23. package/lib/vendor/blamejs/lib/ai-content-detect.js +1 -3
  24. package/lib/vendor/blamejs/lib/ai-frontier-protocol.js +1 -1
  25. package/lib/vendor/blamejs/lib/ai-model-manifest.js +1 -1
  26. package/lib/vendor/blamejs/lib/ai-output.js +16 -7
  27. package/lib/vendor/blamejs/lib/api-snapshot.js +4 -1
  28. package/lib/vendor/blamejs/lib/app-shutdown.js +7 -1
  29. package/lib/vendor/blamejs/lib/archive-gz.js +9 -0
  30. package/lib/vendor/blamejs/lib/archive-read.js +9 -7
  31. package/lib/vendor/blamejs/lib/archive-tar-read.js +51 -8
  32. package/lib/vendor/blamejs/lib/archive.js +4 -2
  33. package/lib/vendor/blamejs/lib/asn1-der.js +70 -22
  34. package/lib/vendor/blamejs/lib/atomic-file.js +204 -2
  35. package/lib/vendor/blamejs/lib/audit-chain.js +54 -5
  36. package/lib/vendor/blamejs/lib/audit-daily-review.js +12 -2
  37. package/lib/vendor/blamejs/lib/audit-sign.js +2 -1
  38. package/lib/vendor/blamejs/lib/audit-tools.js +108 -23
  39. package/lib/vendor/blamejs/lib/audit.js +7 -2
  40. package/lib/vendor/blamejs/lib/auth/access-lock.js +2 -2
  41. package/lib/vendor/blamejs/lib/auth/bot-challenge.js +1 -1
  42. package/lib/vendor/blamejs/lib/auth/ciba.js +43 -4
  43. package/lib/vendor/blamejs/lib/auth/dpop.js +6 -1
  44. package/lib/vendor/blamejs/lib/auth/fido-mds3.js +5 -1
  45. package/lib/vendor/blamejs/lib/auth/jwt.js +2 -2
  46. package/lib/vendor/blamejs/lib/auth/lockout.js +18 -2
  47. package/lib/vendor/blamejs/lib/auth/passkey.js +1 -1
  48. package/lib/vendor/blamejs/lib/auth/password.js +1 -1
  49. package/lib/vendor/blamejs/lib/auth/saml.js +33 -13
  50. package/lib/vendor/blamejs/lib/auth/sd-jwt-vc.js +24 -4
  51. package/lib/vendor/blamejs/lib/auth/status-list.js +14 -2
  52. package/lib/vendor/blamejs/lib/auth/step-up.js +9 -1
  53. package/lib/vendor/blamejs/lib/auth-bot-challenge.js +21 -2
  54. package/lib/vendor/blamejs/lib/backup/bundle.js +7 -2
  55. package/lib/vendor/blamejs/lib/backup/crypto.js +23 -8
  56. package/lib/vendor/blamejs/lib/backup/index.js +41 -20
  57. package/lib/vendor/blamejs/lib/backup/manifest.js +7 -1
  58. package/lib/vendor/blamejs/lib/break-glass.js +41 -22
  59. package/lib/vendor/blamejs/lib/cbor.js +34 -11
  60. package/lib/vendor/blamejs/lib/cdn-cache-control.js +7 -3
  61. package/lib/vendor/blamejs/lib/cert.js +5 -3
  62. package/lib/vendor/blamejs/lib/cli.js +5 -1
  63. package/lib/vendor/blamejs/lib/cloud-events.js +3 -2
  64. package/lib/vendor/blamejs/lib/cluster-storage.js +7 -3
  65. package/lib/vendor/blamejs/lib/codepoint-class.js +17 -0
  66. package/lib/vendor/blamejs/lib/compliance-eaa.js +1 -1
  67. package/lib/vendor/blamejs/lib/compliance-sanctions.js +9 -7
  68. package/lib/vendor/blamejs/lib/compliance.js +1 -1
  69. package/lib/vendor/blamejs/lib/config-drift.js +22 -8
  70. package/lib/vendor/blamejs/lib/content-credentials.js +11 -8
  71. package/lib/vendor/blamejs/lib/content-digest.js +11 -4
  72. package/lib/vendor/blamejs/lib/cookies.js +10 -2
  73. package/lib/vendor/blamejs/lib/cose.js +20 -0
  74. package/lib/vendor/blamejs/lib/crdt.js +2 -1
  75. package/lib/vendor/blamejs/lib/crypto-field.js +29 -23
  76. package/lib/vendor/blamejs/lib/crypto.js +18 -4
  77. package/lib/vendor/blamejs/lib/csp.js +4 -0
  78. package/lib/vendor/blamejs/lib/daemon.js +4 -1
  79. package/lib/vendor/blamejs/lib/data-act.js +27 -4
  80. package/lib/vendor/blamejs/lib/db-file-lifecycle.js +14 -6
  81. package/lib/vendor/blamejs/lib/db-query.js +69 -9
  82. package/lib/vendor/blamejs/lib/db.js +32 -15
  83. package/lib/vendor/blamejs/lib/dora.js +43 -13
  84. package/lib/vendor/blamejs/lib/dr-runbook.js +1 -1
  85. package/lib/vendor/blamejs/lib/dsa.js +2 -1
  86. package/lib/vendor/blamejs/lib/dsr.js +22 -8
  87. package/lib/vendor/blamejs/lib/early-hints.js +19 -0
  88. package/lib/vendor/blamejs/lib/eat.js +5 -1
  89. package/lib/vendor/blamejs/lib/external-db.js +60 -4
  90. package/lib/vendor/blamejs/lib/fda-21cfr11.js +30 -5
  91. package/lib/vendor/blamejs/lib/flag-providers.js +6 -2
  92. package/lib/vendor/blamejs/lib/forms.js +1 -1
  93. package/lib/vendor/blamejs/lib/gate-contract.js +46 -5
  94. package/lib/vendor/blamejs/lib/gdpr-ropa.js +18 -9
  95. package/lib/vendor/blamejs/lib/graphql-federation.js +17 -4
  96. package/lib/vendor/blamejs/lib/guard-all.js +2 -2
  97. package/lib/vendor/blamejs/lib/guard-dsn.js +1 -1
  98. package/lib/vendor/blamejs/lib/guard-envelope.js +1 -1
  99. package/lib/vendor/blamejs/lib/guard-html.js +9 -11
  100. package/lib/vendor/blamejs/lib/guard-imap-command.js +1 -1
  101. package/lib/vendor/blamejs/lib/guard-jmap.js +1 -1
  102. package/lib/vendor/blamejs/lib/guard-json.js +14 -6
  103. package/lib/vendor/blamejs/lib/guard-mail-move.js +1 -1
  104. package/lib/vendor/blamejs/lib/guard-managesieve-command.js +1 -1
  105. package/lib/vendor/blamejs/lib/guard-pop3-command.js +1 -1
  106. package/lib/vendor/blamejs/lib/guard-smtp-command.js +1 -1
  107. package/lib/vendor/blamejs/lib/guard-svg.js +8 -9
  108. package/lib/vendor/blamejs/lib/html-balance.js +7 -3
  109. package/lib/vendor/blamejs/lib/http-client-cookie-jar.js +33 -12
  110. package/lib/vendor/blamejs/lib/http-client.js +225 -53
  111. package/lib/vendor/blamejs/lib/iab-tcf.js +3 -2
  112. package/lib/vendor/blamejs/lib/importmap-integrity.js +41 -1
  113. package/lib/vendor/blamejs/lib/incident-report.js +9 -6
  114. package/lib/vendor/blamejs/lib/json-patch.js +1 -1
  115. package/lib/vendor/blamejs/lib/json-path.js +24 -3
  116. package/lib/vendor/blamejs/lib/jtd.js +2 -2
  117. package/lib/vendor/blamejs/lib/legal-hold.js +24 -8
  118. package/lib/vendor/blamejs/lib/log.js +2 -2
  119. package/lib/vendor/blamejs/lib/mail-agent.js +2 -2
  120. package/lib/vendor/blamejs/lib/mail-arf.js +1 -1
  121. package/lib/vendor/blamejs/lib/mail-auth.js +3 -3
  122. package/lib/vendor/blamejs/lib/mail-bimi.js +16 -16
  123. package/lib/vendor/blamejs/lib/mail-bounce.js +3 -3
  124. package/lib/vendor/blamejs/lib/mail-crypto-smime.js +71 -6
  125. package/lib/vendor/blamejs/lib/mail-deploy.js +9 -5
  126. package/lib/vendor/blamejs/lib/mail-greylist.js +2 -4
  127. package/lib/vendor/blamejs/lib/mail-helo.js +2 -4
  128. package/lib/vendor/blamejs/lib/mail-journal.js +11 -8
  129. package/lib/vendor/blamejs/lib/mail-mdn.js +8 -4
  130. package/lib/vendor/blamejs/lib/mail-rbl.js +2 -4
  131. package/lib/vendor/blamejs/lib/mail-scan.js +3 -5
  132. package/lib/vendor/blamejs/lib/mail-server-jmap.js +4 -1
  133. package/lib/vendor/blamejs/lib/mail-server-registry.js +1 -1
  134. package/lib/vendor/blamejs/lib/mail-server-tls.js +9 -2
  135. package/lib/vendor/blamejs/lib/mail-spam-score.js +2 -4
  136. package/lib/vendor/blamejs/lib/mail-store-fts.js +1 -1
  137. package/lib/vendor/blamejs/lib/mail.js +22 -2
  138. package/lib/vendor/blamejs/lib/markup-tokenizer.js +24 -0
  139. package/lib/vendor/blamejs/lib/mcp.js +6 -4
  140. package/lib/vendor/blamejs/lib/mdoc.js +26 -3
  141. package/lib/vendor/blamejs/lib/metrics.js +14 -3
  142. package/lib/vendor/blamejs/lib/middleware/api-encrypt.js +2 -2
  143. package/lib/vendor/blamejs/lib/middleware/body-parser.js +10 -4
  144. package/lib/vendor/blamejs/lib/middleware/bot-guard.js +26 -18
  145. package/lib/vendor/blamejs/lib/middleware/clear-site-data.js +5 -1
  146. package/lib/vendor/blamejs/lib/middleware/compression.js +9 -0
  147. package/lib/vendor/blamejs/lib/middleware/cors.js +32 -23
  148. package/lib/vendor/blamejs/lib/middleware/csrf-protect.js +60 -21
  149. package/lib/vendor/blamejs/lib/middleware/daily-byte-quota.js +6 -4
  150. package/lib/vendor/blamejs/lib/middleware/fetch-metadata.js +28 -4
  151. package/lib/vendor/blamejs/lib/middleware/network-allowlist.js +61 -30
  152. package/lib/vendor/blamejs/lib/middleware/rate-limit.js +25 -16
  153. package/lib/vendor/blamejs/lib/middleware/scim-server.js +2 -1
  154. package/lib/vendor/blamejs/lib/middleware/security-headers.js +24 -6
  155. package/lib/vendor/blamejs/lib/middleware/speculation-rules.js +6 -3
  156. package/lib/vendor/blamejs/lib/middleware/tus-upload.js +2 -2
  157. package/lib/vendor/blamejs/lib/money.js +1 -1
  158. package/lib/vendor/blamejs/lib/mtls-ca.js +10 -6
  159. package/lib/vendor/blamejs/lib/network-dns-resolver.js +1 -1
  160. package/lib/vendor/blamejs/lib/network-dns.js +9 -2
  161. package/lib/vendor/blamejs/lib/network-dnssec.js +2 -1
  162. package/lib/vendor/blamejs/lib/network-smtp-policy.js +23 -5
  163. package/lib/vendor/blamejs/lib/network-tls.js +27 -5
  164. package/lib/vendor/blamejs/lib/network-tsig.js +2 -2
  165. package/lib/vendor/blamejs/lib/nis2-report.js +1 -1
  166. package/lib/vendor/blamejs/lib/nist-crosswalk.js +1 -1
  167. package/lib/vendor/blamejs/lib/ntp-check.js +28 -0
  168. package/lib/vendor/blamejs/lib/numeric-bounds.js +9 -0
  169. package/lib/vendor/blamejs/lib/object-store/azure-blob.js +1 -2
  170. package/lib/vendor/blamejs/lib/object-store/gcs-bucket-ops.js +4 -2
  171. package/lib/vendor/blamejs/lib/object-store/gcs.js +6 -4
  172. package/lib/vendor/blamejs/lib/object-store/http-put.js +1 -2
  173. package/lib/vendor/blamejs/lib/object-store/http-request.js +30 -1
  174. package/lib/vendor/blamejs/lib/object-store/local.js +37 -17
  175. package/lib/vendor/blamejs/lib/object-store/sigv4.js +1 -2
  176. package/lib/vendor/blamejs/lib/observability-otlp-exporter.js +20 -4
  177. package/lib/vendor/blamejs/lib/outbox.js +11 -4
  178. package/lib/vendor/blamejs/lib/parsers/safe-xml.js +1 -1
  179. package/lib/vendor/blamejs/lib/parsers/safe-yaml.js +21 -3
  180. package/lib/vendor/blamejs/lib/queue-local.js +10 -3
  181. package/lib/vendor/blamejs/lib/redact.js +7 -3
  182. package/lib/vendor/blamejs/lib/request-helpers.js +201 -23
  183. package/lib/vendor/blamejs/lib/resource-access-lock.js +3 -3
  184. package/lib/vendor/blamejs/lib/restore-bundle.js +46 -18
  185. package/lib/vendor/blamejs/lib/restore-rollback.js +10 -4
  186. package/lib/vendor/blamejs/lib/restore.js +19 -0
  187. package/lib/vendor/blamejs/lib/retention.js +20 -4
  188. package/lib/vendor/blamejs/lib/router.js +17 -4
  189. package/lib/vendor/blamejs/lib/safe-ical.js +2 -2
  190. package/lib/vendor/blamejs/lib/safe-icap.js +1 -1
  191. package/lib/vendor/blamejs/lib/safe-json.js +44 -0
  192. package/lib/vendor/blamejs/lib/safe-sieve.js +1 -1
  193. package/lib/vendor/blamejs/lib/safe-vcard.js +1 -1
  194. package/lib/vendor/blamejs/lib/sandbox-worker.js +6 -0
  195. package/lib/vendor/blamejs/lib/sandbox.js +1 -1
  196. package/lib/vendor/blamejs/lib/scheduler.js +17 -1
  197. package/lib/vendor/blamejs/lib/self-update-standalone-verifier.js +16 -0
  198. package/lib/vendor/blamejs/lib/session.js +27 -3
  199. package/lib/vendor/blamejs/lib/sql.js +3 -3
  200. package/lib/vendor/blamejs/lib/static.js +65 -13
  201. package/lib/vendor/blamejs/lib/template.js +7 -5
  202. package/lib/vendor/blamejs/lib/tenant-quota.js +52 -19
  203. package/lib/vendor/blamejs/lib/tsa.js +5 -2
  204. package/lib/vendor/blamejs/lib/vault/index.js +5 -0
  205. package/lib/vendor/blamejs/lib/vault/passphrase-ops.js +22 -26
  206. package/lib/vendor/blamejs/lib/vault/passphrase-source.js +8 -3
  207. package/lib/vendor/blamejs/lib/vault/rotate.js +13 -18
  208. package/lib/vendor/blamejs/lib/vault/seal-pem-file.js +4 -1
  209. package/lib/vendor/blamejs/lib/vc.js +1 -1
  210. package/lib/vendor/blamejs/lib/vendor/MANIFEST.json +10 -10
  211. package/lib/vendor/blamejs/lib/vendor/public-suffix-list.dat +23 -10
  212. package/lib/vendor/blamejs/lib/vendor/public-suffix-list.data.js +5498 -5494
  213. package/lib/vendor/blamejs/lib/webhook.js +16 -1
  214. package/lib/vendor/blamejs/lib/websocket.js +1 -1
  215. package/lib/vendor/blamejs/lib/worm.js +1 -1
  216. package/lib/vendor/blamejs/lib/ws-client.js +57 -46
  217. package/lib/vendor/blamejs/lib/x509-chain.js +44 -0
  218. package/lib/vendor/blamejs/package.json +1 -1
  219. package/lib/vendor/blamejs/release-notes/v0.15.14.json +122 -0
  220. package/lib/vendor/blamejs/scripts/check-vendor-currency.js +119 -4
  221. package/lib/vendor/blamejs/test/00-primitives.js +5 -1
  222. package/lib/vendor/blamejs/test/integration/mail-crypto-smime.test.js +18 -4
  223. package/lib/vendor/blamejs/test/layer-0-primitives/a2a-tasks.test.js +42 -0
  224. package/lib/vendor/blamejs/test/layer-0-primitives/agent-event-bus.test.js +36 -0
  225. package/lib/vendor/blamejs/test/layer-0-primitives/agent-idempotency.test.js +0 -0
  226. package/lib/vendor/blamejs/test/layer-0-primitives/agent-snapshot.test.js +47 -0
  227. package/lib/vendor/blamejs/test/layer-0-primitives/archive-gz.test.js +24 -0
  228. package/lib/vendor/blamejs/test/layer-0-primitives/archive-tar-hardening.test.js +160 -0
  229. package/lib/vendor/blamejs/test/layer-0-primitives/asn1-der.test.js +45 -0
  230. package/lib/vendor/blamejs/test/layer-0-primitives/atomic-file-fd-read.test.js +30 -0
  231. package/lib/vendor/blamejs/test/layer-0-primitives/atomic-file-open-nofollow.test.js +79 -0
  232. package/lib/vendor/blamejs/test/layer-0-primitives/atomic-file-write-excl.test.js +132 -0
  233. package/lib/vendor/blamejs/test/layer-0-primitives/atomic-file-write-stream.test.js +181 -0
  234. package/lib/vendor/blamejs/test/layer-0-primitives/audit-chain-corrupted-anchor.test.js +65 -0
  235. package/lib/vendor/blamejs/test/layer-0-primitives/audit-chain-incremental-verify.test.js +83 -0
  236. package/lib/vendor/blamejs/test/layer-0-primitives/audit-daily-review.test.js +48 -1
  237. package/lib/vendor/blamejs/test/layer-0-primitives/audit-signing-key-rotation.test.js +13 -3
  238. package/lib/vendor/blamejs/test/layer-0-primitives/audit-verifybundle-tamper.test.js +122 -0
  239. package/lib/vendor/blamejs/test/layer-0-primitives/auth-bot-challenge.test.js +37 -0
  240. package/lib/vendor/blamejs/test/layer-0-primitives/auth-jwt-defenses.test.js +5 -3
  241. package/lib/vendor/blamejs/test/layer-0-primitives/auth-lockout.test.js +21 -0
  242. package/lib/vendor/blamejs/test/layer-0-primitives/auth-status-list.test.js +73 -0
  243. package/lib/vendor/blamejs/test/layer-0-primitives/bot-guard.test.js +17 -0
  244. package/lib/vendor/blamejs/test/layer-0-primitives/break-glass.test.js +62 -3
  245. package/lib/vendor/blamejs/test/layer-0-primitives/clear-site-data.test.js +12 -0
  246. package/lib/vendor/blamejs/test/layer-0-primitives/cluster-storage.test.js +40 -0
  247. package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +146 -5
  248. package/lib/vendor/blamejs/test/layer-0-primitives/compliance-sanctions.test.js +14 -0
  249. package/lib/vendor/blamejs/test/layer-0-primitives/compression-range.test.js +115 -0
  250. package/lib/vendor/blamejs/test/layer-0-primitives/content-credentials.test.js +17 -5
  251. package/lib/vendor/blamejs/test/layer-0-primitives/cors.test.js +27 -5
  252. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-per-row-key.test.js +83 -0
  253. package/lib/vendor/blamejs/test/layer-0-primitives/csrf-protect.test.js +58 -0
  254. package/lib/vendor/blamejs/test/layer-0-primitives/data-act.test.js +30 -0
  255. package/lib/vendor/blamejs/test/layer-0-primitives/db-raw-residency-gate.test.js +74 -0
  256. package/lib/vendor/blamejs/test/layer-0-primitives/dora.test.js +20 -6
  257. package/lib/vendor/blamejs/test/layer-0-primitives/dpop-alg-kty.test.js +64 -0
  258. package/lib/vendor/blamejs/test/layer-0-primitives/dsr.test.js +32 -0
  259. package/lib/vendor/blamejs/test/layer-0-primitives/fda-21cfr11.test.js +43 -0
  260. package/lib/vendor/blamejs/test/layer-0-primitives/fetch-metadata.test.js +30 -0
  261. package/lib/vendor/blamejs/test/layer-0-primitives/graphql-federation.test.js +33 -0
  262. package/lib/vendor/blamejs/test/layer-0-primitives/guard-html.test.js +34 -0
  263. package/lib/vendor/blamejs/test/layer-0-primitives/guard-json.test.js +12 -0
  264. package/lib/vendor/blamejs/test/layer-0-primitives/http-client-stream.test.js +72 -0
  265. package/lib/vendor/blamejs/test/layer-0-primitives/http-client-throttle-transform.test.js +88 -0
  266. package/lib/vendor/blamejs/test/layer-0-primitives/importmap-integrity.test.js +25 -0
  267. package/lib/vendor/blamejs/test/layer-0-primitives/legal-hold.test.js +28 -0
  268. package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto-smime.test.js +20 -0
  269. package/lib/vendor/blamejs/test/layer-0-primitives/mail-header-injection.test.js +58 -0
  270. package/lib/vendor/blamejs/test/layer-0-primitives/mail-journal.test.js +63 -9
  271. package/lib/vendor/blamejs/test/layer-0-primitives/mdoc.test.js +41 -14
  272. package/lib/vendor/blamejs/test/layer-0-primitives/network-allowlist.test.js +61 -0
  273. package/lib/vendor/blamejs/test/layer-0-primitives/object-store-range-header.test.js +51 -0
  274. package/lib/vendor/blamejs/test/layer-0-primitives/otlp-attr-redaction.test.js +35 -0
  275. package/lib/vendor/blamejs/test/layer-0-primitives/output-header-hardening.test.js +102 -0
  276. package/lib/vendor/blamejs/test/layer-0-primitives/parser-verify-hardening.test.js +191 -0
  277. package/lib/vendor/blamejs/test/layer-0-primitives/proto-shadow-allowlist.test.js +120 -0
  278. package/lib/vendor/blamejs/test/layer-0-primitives/rate-limit-xff-spoofing.test.js +75 -0
  279. package/lib/vendor/blamejs/test/layer-0-primitives/request-helpers.test.js +128 -0
  280. package/lib/vendor/blamejs/test/layer-0-primitives/restore-blob-remap.test.js +128 -0
  281. package/lib/vendor/blamejs/test/layer-0-primitives/retention-sweep-termination.test.js +104 -0
  282. package/lib/vendor/blamejs/test/layer-0-primitives/router-body-validation.test.js +98 -0
  283. package/lib/vendor/blamejs/test/layer-0-primitives/safe-json-stringify-for-script.test.js +43 -0
  284. package/lib/vendor/blamejs/test/layer-0-primitives/saml-subjectconfirmation-notonorafter.test.js +48 -5
  285. package/lib/vendor/blamejs/test/layer-0-primitives/sandbox.test.js +23 -0
  286. package/lib/vendor/blamejs/test/layer-0-primitives/sd-jwt-vc.test.js +54 -0
  287. package/lib/vendor/blamejs/test/layer-0-primitives/session-extensions.test.js +24 -0
  288. package/lib/vendor/blamejs/test/layer-0-primitives/static.test.js +42 -1
  289. package/lib/vendor/blamejs/test/layer-0-primitives/step-up.test.js +7 -0
  290. package/lib/vendor/blamejs/test/layer-0-primitives/template-escape-html.test.js +43 -0
  291. package/lib/vendor/blamejs/test/layer-0-primitives/tenant-quota.test.js +15 -0
  292. package/lib/vendor/blamejs/test/layer-0-primitives/vault-default-store.test.js +48 -0
  293. package/lib/vendor/blamejs/test/layer-0-primitives/vendor-currency-classify.test.js +77 -0
  294. package/lib/vendor/blamejs/test/layer-0-primitives/webhook.test.js +50 -0
  295. package/lib/vendor/blamejs/test/layer-0-primitives/ws-client.test.js +22 -0
  296. package/lib/vendor/blamejs/test/layer-0-primitives/x509-chain-ca-enforcement.test.js +149 -0
  297. package/lib/vendor/blamejs/test/layer-5-integration/external-db-residency.test.js +34 -0
  298. package/package.json +1 -1
@@ -1647,12 +1647,16 @@ function testFormatValidatorLengthCap() {
1647
1647
  var line = lines[li];
1648
1648
  if (/^\s*(\/\/|\*|\/\*)/.test(line)) continue;
1649
1649
  if (!/\b[A-Z_]+_RE\.test\(/.test(line)) continue;
1650
- // Look for `length` mention in surrounding 5 lines (any
1651
- // comparison: <, >, =, !=, !==, including the explicit-length
1652
- // shape `s.length !== expectedLength`).
1650
+ // Look for a length bound in the surrounding 5 lines (any comparison:
1651
+ // <, >, =, !=, !==, including the explicit-length shape `s.length !==
1652
+ // expectedLength`). A byte-length bound (Buffer.byteLength /
1653
+ // safeBuffer.byteLengthOf) counts too — the byte-cap-vs-char-length
1654
+ // hardening replaces `.length` caps with byte-length on multibyte input,
1655
+ // and `byteLength(x) > MAX || RE.test(x)` short-circuits the regex on
1656
+ // anything over the cap just as `.length` does.
1653
1657
  var window = (lines[li-2] || "") + (lines[li-1] || "") +
1654
1658
  line + (lines[li+1] || "") + (lines[li+2] || "");
1655
- if (/\.length\s*[><=!]/.test(window)) continue;
1659
+ if (/\.length\s*[><=!]/.test(window) || /byteLength/.test(window)) continue;
1656
1660
  bad.push({
1657
1661
  file: _relPath(files[fi]),
1658
1662
  line: li + 1,
@@ -3850,13 +3854,24 @@ async function testNoDuplicateCodeBlocks() {
3850
3854
  { mode: "family-subset", files: ["lib/ai-dp.js:mechanism", "lib/dora.js:_validateReportInput", "lib/guard-snapshot-envelope.js:validate"] }, // fp:b0dceb3bd2fe
3851
3855
  { mode: "family-subset", files: ["lib/auth/sd-jwt-vc-issuer.js:create", "lib/fsm.js:define", "lib/mail.js:_validateMessage"] }, // fp:dff4eab2b4e0
3852
3856
  { mode: "family-subset", files: ["lib/csp.js:_parsePermissionsPolicyString", "lib/http-message-signature.js:_parseSignatureInput", "lib/network-tls.js:_parseSanString"] }, // fp:c998d327f9c0
3853
- { mode: "family-subset", files: ["lib/middleware/idempotency-key.js:resealMigrate", "lib/object-store/http-request.js:notModifiedGetResult", "lib/validate-opts.js:observabilityShape"] }, // fp:711a58281dd7
3857
+ { mode: "family-subset", files: ["lib/middleware/idempotency-key.js:resealMigrate", "lib/object-store/http-request.js:promiseToStream", "lib/validate-opts.js:observabilityShape"] }, // fp:824f10cf9f10 (was notModifiedGetResult/fp:711a58281dd7 before promiseToStream landed adjacent — coincidental object-literal/return shingle across 3 unrelated fns)
3854
3858
  { mode: "family-subset", files: ["lib/auth/sd-jwt-vc-issuer.js:create", "lib/guard-saga-config.js:validate", "lib/network-heartbeat.js:_validateTarget"] }, // fp:c8f43d4d1941
3855
3859
  { mode: "family-subset", files: ["lib/auth/oauth.js:deviceAuthorization", "lib/auth/oauth.js:parseCallback", "lib/ddl-change-control.js:_hashSql", "lib/mail-rbl.js:query"] }, // fp:882fd32d8e11
3856
3860
  { mode: "family-subset", files: ["lib/auth/oid4vp.js:matchDcql", "lib/gate-contract.js:_ctxValueForKind", "lib/http-message-signature.js:_parseUrl"] }, // fp:726ed545b065
3857
3861
  { mode: "family-subset", files: ["lib/archive-adapters.js:close", "lib/crypto-field.js:listPerRowResidency", "lib/tracing.js:spanSync"] }, // fp:c4617588ed77
3858
3862
  { mode: "family-subset", files: ["lib/backup/bundle.js:create", "lib/mail-greylist.js:check", "lib/safe-buffer.js:makeByteCoercer"] }, // fp:d4c06878dcda
3859
3863
  { mode: "family-subset", files: ["lib/breach-deadline.js:trackReport", "lib/guard-snapshot-envelope.js:validate", "lib/incident-report.js:open"] }, // fp:d7d8a89732c8
3864
+ // Proto-shadow hardening converged these allowlist gates onto the framework's
3865
+ // canonical `!Object.prototype.hasOwnProperty.call(MAP, key)` membership idiom
3866
+ // (the 312-use convention), which pushed the shared validation preamble past
3867
+ // the 50-token shingle. Shape-only: the maps, error classes, codes, and
3868
+ // messages diverge per domain (mail-spam-score lists the valid profiles in
3869
+ // its bad-profile message; guard-envelope does not — a deliberate per-caller
3870
+ // choice, see gate-contract.resolveProfileName which owns only the resolution
3871
+ // expression and leaves each caller its bespoke typed throw).
3872
+ { mode: "family-subset", files: ["lib/guard-envelope.js:check", "lib/mail-greylist.js:create", "lib/mail-helo.js:evaluate", "lib/mail-rbl.js:create", "lib/mail-scan.js:create", "lib/mail-spam-score.js:create"] }, // fp:958143c0319d (was fp:59fd2e7ac678 before mail-scan's profile gate joined)
3873
+ { mode: "family-subset", files: ["lib/mail-journal.js:_validateRegimes", "lib/middleware/tus-upload.js:create", "lib/safe-sieve.js:_parseCommand"] }, // fp:2524f7ddf5a1
3874
+ { mode: "family-subset", files: ["lib/breach-deadline.js:open", "lib/breach-deadline.js:trackReport", "lib/cms-codec.js:encodeEnvelopedData", "lib/cms-codec.js:encodeSignedData", "lib/mail-deploy.js:mtaStsPublish"] }, // fp:32fba36fb64e
3860
3875
  {
3861
3876
  // presigned-policy expiry-range validation + structure — shape-only. `var
3862
3877
  // ttl = opts.expiresIn != null ? opts.expiresIn : <default>; if (typeof ttl
@@ -5092,6 +5107,89 @@ var KNOWN_ANTIPATTERNS = [
5092
5107
  allowlist: ["lib/guard-auth.js"],
5093
5108
  reason: "v0.15.0 #103 — the guard sanitize/parse refuse-on-critical|high throw (err(issue.ruleId || '<x>.refused', 'guard<Name>.<op>: ' + issue.snippet)) is owned by gateContract.throwOnRefusalSeverity; 18 guards reuse it (this was the failing STRONG-DUP fp:f349a8d1f51b before extraction). A hand-rolled `issues[i].ruleId || '<x>.refused'` throw re-implements it. lib/guard-auth.js is the one genuine holdout (its message embeds issues[i].source: 'guardAuth.sanitize [<source>]:') pending task #104; the primitive itself uses a `fallback` variable (no .refused literal) so it does not match. Any other lib file with this shape must call gateContract.throwOnRefusalSeverity (the severities / op options cover the critical-only + parse variants).",
5094
5109
  },
5110
+ {
5111
+ id: "html-comment-scan-must-use-htmlCommentEnd",
5112
+ primitive: "b.markupTokenizer.htmlCommentEnd",
5113
+ scanScope: "lib",
5114
+ skipCommentLines: true,
5115
+ // The WHATWG HTML tokenizer closes a comment at "--!>" and ABRUPTLY at
5116
+ // "<!-->" / "<!--->", not only "-->". An HTML/SVG-in-HTML scanner that
5117
+ // searches indexOf("-->") disagrees with the browser on the comment
5118
+ // boundary, so an element after an early terminator is swallowed as inert
5119
+ // comment by the sanitizer but parsed LIVE by the browser (mXSS, the
5120
+ // comment-parser differential). markupTokenizer.htmlCommentEnd owns the
5121
+ // correct scan; guard-html / guard-svg / mail-bimi / html-balance route
5122
+ // through it. XML comment scanners (safe-xml, xml-c14n) legitimately use
5123
+ // "-->" because XML has neither "--!>" nor abrupt-close forms.
5124
+ regex: /\.indexOf\("-->"/,
5125
+ allowlist: ["lib/markup-tokenizer.js", "lib/parsers/safe-xml.js", "lib/xml-c14n.js"],
5126
+ reason: "HTML/SVG-in-HTML comment scanning must use markupTokenizer.htmlCommentEnd (covers --!> + abrupt <!-->/<!--->), not a bare indexOf('-->'). XML scanners are exempt (XML has no such forms); markup-tokenizer.js is the primitive's home.",
5127
+ },
5128
+ {
5129
+ id: "x509-issuer-check-must-enforce-ca",
5130
+ primitive: "b.x509Chain.issuerValidlyIssued",
5131
+ scanScope: "lib",
5132
+ skipCommentLines: true,
5133
+ // node:crypto X509Certificate.checkIssued() does NOT enforce
5134
+ // basicConstraints cA:TRUE, so `subject.checkIssued(issuer)` used to
5135
+ // accept a leaf / end-entity cert (cA:FALSE, no keyUsage) as a signing
5136
+ // CA — the basicConstraints bypass (CVE-2002-0862 class) that hit
5137
+ // tsa.js / mail-bimi.js / mail-crypto-smime.js. Every chain walker now
5138
+ // routes its issuer test through x509Chain.issuerValidlyIssued (which
5139
+ // adds the cA check). A hand-rolled `receiver.checkIssued(otherArg)`
5140
+ // re-introduces the gap. The negative backref skips the legitimate
5141
+ // self-signed-root probe `current.checkIssued(current)` (same receiver
5142
+ // + arg), which is not an issuer-acceptance use.
5143
+ regex: /\b(\w+)\.checkIssued\s*\(\s*(?!\1[\s,)])\w/,
5144
+ allowlist: ["lib/x509-chain.js"],
5145
+ reason: "basicConstraints cA:TRUE enforcement is owned by x509Chain.issuerValidlyIssued / x509Chain.isCaCert; tsa/mail-bimi/mail-crypto-smime route through it. Any lib file calling X.checkIssued(Y) (Y!=X) directly bypasses the cA check and must use x509Chain instead. lib/x509-chain.js is the home of the primitive.",
5146
+ },
5147
+ {
5148
+ id: "trusted-proxy-cidr-must-canonicalize-peer",
5149
+ primitive: "b.requestHelpers.trustedClientIp",
5150
+ scanScope: "lib",
5151
+ skipCommentLines: true,
5152
+ // The peer-gating trustedProxies predicate matches the immediate peer
5153
+ // address against operator CIDRs. cidrContains refuses a cross-family
5154
+ // compare, so an IPv4-mapped IPv6 peer (::ffff:a.b.c.d from a dual-stack
5155
+ // listener) silently fails an IPv4 CIDR and the proxy reads as untrusted —
5156
+ // X-Forwarded-* is then ignored and the gate keys on the proxy. The peer
5157
+ // MUST be folded through ssrfGuard.canonicalizeHost (var canon) first; a
5158
+ // raw `cidrContains(trustedProxies[i], addr)` re-introduces the bypass.
5159
+ regex: /cidrContains\(trustedProxies\[\w+\],\s*(?!canon\b)\w/,
5160
+ allowlist: [],
5161
+ reason: "Match the immediate peer against trustedProxies only after folding IPv4-mapped IPv6 via ssrfGuard.canonicalizeHost (the `canon` local) — cidrContains rejects a cross-family compare, so a raw peer addr lets a dual-stack proxy read as untrusted. request-helpers.js's _trustedProxyPredicate owns this.",
5162
+ },
5163
+ {
5164
+ id: "http-upload-content-length-must-guard-transform",
5165
+ primitive: "b.httpClient.request",
5166
+ scanScope: "lib",
5167
+ skipCommentLines: true,
5168
+ // A Content-Length set from a Buffer body's length is wrong once a
5169
+ // size-changing uploadTransform (gzip / encrypt / frame) is interposed —
5170
+ // the server truncates extra bytes or waits for bytes that never arrive.
5171
+ // The assignment must be guarded by `uploadTransforms.length === 0` so a
5172
+ // transformed body is framed (chunked / DATA frames) on its real size.
5173
+ regex: /Buffer\.isBuffer\(opts\.body\)(?:(?!uploadTransforms)[\s\S]){0,400}?content-length"\]\s*=\s*(?:String\()?opts\.body\.length/i,
5174
+ allowlist: [],
5175
+ reason: "Set Content-Length from opts.body.length only when uploadTransforms.length === 0 — a size-changing uploadTransform makes the original length wrong (truncation / hang). Omit it (chunked / H2 DATA frames) when a transform is present. lib/http-client.js owns both legs.",
5176
+ },
5177
+ {
5178
+ id: "http-response-collect-must-use-download-pipeline",
5179
+ primitive: "b.httpClient.request",
5180
+ scanScope: "lib",
5181
+ skipCommentLines: true,
5182
+ // The buffered (non-stream) response collector must read from the
5183
+ // _buildDownloadStream pipeline tail, not the raw socket stream — reading
5184
+ // `res`/`stream` directly bypasses maxBytesPerSec + downloadTransform in
5185
+ // buffer / always-resolve mode (the documented default), so a configured
5186
+ // throttle / transform silently no-ops. Anchored on the full-response
5187
+ // collector (maxResponseBytes) so the bounded error-prefix reader is not
5188
+ // matched.
5189
+ regex: /boundedChunkCollector\(\{ maxBytes: maxResponseBytes \}\)(?:(?!_buildDownloadStream)[\s\S]){0,300}?\b(?:res|stream)\.on\("data"/,
5190
+ allowlist: [],
5191
+ reason: "Feed the full-response collector from _buildDownloadStream(...) (the dlSource / dlH2 tail), never raw res/stream .on('data') — otherwise maxBytesPerSec / downloadTransform apply only in stream mode. lib/http-client.js owns both H1 + H2 paths.",
5192
+ },
5095
5193
  {
5096
5194
  // A hard quota / rate / budget ceiling must be enforced with an
5097
5195
  // atomic conditional reserve — the limit test and the charge are
@@ -8390,6 +8488,49 @@ var KNOWN_ANTIPATTERNS = [
8390
8488
  allowlist: ["lib/atomic-file.js"],
8391
8489
  reason: "A TOCTOU-safe synchronous file read (open fd → fstat → read-fully loop, binding size/content/integrity to the fd's inode against a swap, CWE-367) is owned by b.atomicFile.fdSafeReadSync — with optional guards (maxBytes cap, refuseSymlink + inodeCheck, expectedHash, encoding, allowShortRead) and a per-caller errorFor(kind, detail) so each domain keeps its typed error. The §1 sweep routed the four hand-rolled read-fully loops: atomic-file._readSyncCore (the home; now the wrapper that supplies AtomicFileError messages), network-tls._readPathFile (utf8 + slice-on-short-read + raw ENOENT), vault/seal-pem-file._resealNow (the strongest posture: refuseSymlink + inodeCheck + maxSourceBytes, SealPemFileError codes wrapped by its outer audit/onError catch), backup/bundle.create (BackupBundleError short-read with per-entry relativePath). Allowlist is lib/atomic-file.js (fdSafeReadSync's own loop is the single home); a re-introduced `readSync(fd, buf, read, size - read, null)` read-loop anywhere else trips this — call b.atomicFile.fdSafeReadSync instead.",
8392
8490
  },
8491
+ {
8492
+ id: "raw-fs-write-without-exclusive-nofollow",
8493
+ primitive: "b.atomicFile.writeSync / writeStream / writeExclSync — atomic, O_EXCL|O_NOFOLLOW write-replace",
8494
+ // The write-side sibling of fd-read-loop-hand-rolled. A bare
8495
+ // nodeFs.writeFileSync(path, ...) / nodeFs.createWriteStream(path) follows
8496
+ // a symlink an attacker pre-planted at `path` (CWE-59 arbitrary write) and
8497
+ // leaves a torn file at the canonical name on a crash. Every framework
8498
+ // write-replace goes through an atomic-file primitive that stages into a
8499
+ // CSPRNG temp opened O_EXCL|O_NOFOLLOW and renames: writeSync (buffer),
8500
+ // writeStream (streaming source), writeExclSync (staged write→verify→rename
8501
+ // for the vault seal/unseal/rotate round-trip).
8502
+ scanScope: "lib",
8503
+ skipCommentLines: true,
8504
+ regex: /\bnodeFs\.(?:writeFileSync|createWriteStream)\s*\(/,
8505
+ allowlist: [
8506
+ "lib/atomic-file.js", // primitives' home: createWriteStream(null, { fd }) over an _openExclTemp fd (the fd already carries O_EXCL|O_NOFOLLOW)
8507
+ "lib/backup/index.js", // fsAdapter.writeFile uses writeFileSync(..., { flag: "wx" }) — O_CREAT|O_EXCL refuses (EEXIST) a pre-planted file/symlink
8508
+ "lib/http-client.js", // downloadStream: createWriteStream(tmpPath, { flags: O_WRONLY|O_CREAT|O_EXCL|O_NOFOLLOW }) — explicit exclusive no-follow create
8509
+ ],
8510
+ reason: "Symlink-follow / torn-write class (CWE-59 / CWE-377). A raw nodeFs.writeFileSync / nodeFs.createWriteStream to a destination path follows a symlink an attacker pre-planted there and can leave a half-written file on a crash. The §1 sweep routed every hand-rolled write through an atomic-file primitive: writeSync (buffer payloads — cookie-jar flush, archive/tar extract, backup payloads, p12 export, api-snapshot, sealed-pem marker), writeStream (object-store streaming put — staged into a no-follow exclusive temp + atomic rename, capped by maxBytes), and writeExclSync (the vault seal/unseal/rotate staged write that must re-read + verify the bytes before the rename — clears any stale entry then O_EXCL|O_NOFOLLOW creates so a re-planted symlink fails closed). The three allowlisted files are the genuinely-safe forms: atomic-file.js owns the primitives (its createWriteStream binds an _openExclTemp fd); backup/index.js writeFileSync passes { flag: 'wx' } (O_EXCL refuses a pre-planted target); http-client.js opens its download temp with explicit O_WRONLY|O_CREAT|O_EXCL|O_NOFOLLOW flags. Any new raw nodeFs.writeFileSync/createWriteStream elsewhere trips this — call the matching atomic-file primitive instead.",
8511
+ },
8512
+ {
8513
+ id: "allowlist-map-indexed-by-untrusted-key-without-hasown",
8514
+ primitive: "Object.prototype.hasOwnProperty.call(MAP, key) — proto-shadow-safe allowlist membership",
8515
+ // Prototype-pollution / proto-shadow class. An object-literal allowlist
8516
+ // (`var MAP = { a: 1 }`) inherits Object.prototype, so MAP["constructor"],
8517
+ // MAP["__proto__"], MAP["toString"], MAP["valueOf"], MAP["hasOwnProperty"]
8518
+ // are all TRUTHY (and !== undefined) even though never added. A membership
8519
+ // gate `if (!MAP[key])` (reject-if-absent) or `if (MAP[key] === undefined)`
8520
+ // is therefore BYPASSED by a proto-named key when `key` is attacker- or
8521
+ // caller-controlled, and `var v = MAP[key]` then hands back a Function /
8522
+ // prototype member. The framework's 312-use idiom is
8523
+ // `Object.prototype.hasOwnProperty.call(MAP, key)` (own-key only). This
8524
+ // detector forbids the bare negated-bracket and `=== undefined` membership
8525
+ // shapes against a SCREAMING_CASE allowlist; reject-if-PRESENT gates
8526
+ // (`if (DROP[tag]) drop`) are a DIFFERENT, fail-safe polarity and use the
8527
+ // positive `if (MAP[key])` form this regex does not match.
8528
+ scanScope: "lib",
8529
+ skipCommentLines: true,
8530
+ regex: /!\s*(?:[a-zA-Z_$][\w$]*\.)?[A-Z][A-Z0-9_]{2,}\[\s*[a-z]|[A-Z][A-Z0-9_]{2,}\[[a-z][\w.]*\]\s*===\s*undefined/,
8531
+ allowlist: [],
8532
+ reason: "Proto-shadow allowlist-bypass class (CWE-1321 prototype pollution / unsafe reflection). A reject-if-absent membership check on an object-literal allowlist — `if (!MAP[key])` or `if (MAP[key] === undefined)` — passes for any Object.prototype member name (constructor / __proto__ / toString / valueOf / hasOwnProperty) when `key` is attacker- or caller-supplied, bypassing the allowlist and (for value-lookup callers) handing a Function downstream. The v0.15.14 sweep converted every such gate across lib/ to the framework's canonical `Object.prototype.hasOwnProperty.call(MAP, key)` membership idiom (already 312 uses). Zero allowlist: a re-introduced `!SCREAMING_MAP[lowercaseKey]` or `SCREAMING_MAP[key] === undefined` membership gate anywhere in lib/ trips this — use hasOwnProperty.call instead. Reject-if-PRESENT gates (`if (DANGEROUS[scheme]) refuse`) are the opposite, fail-safe polarity and are written with the positive `if (MAP[key])` form, which this detector deliberately does not match (adding hasOwnProperty there would weaken them).",
8533
+ },
8393
8534
  {
8394
8535
  id: "inline-optional-non-empty-string-array-validation",
8395
8536
  primitive: "validateOpts.optionalNonEmptyStringArray(value, label, ErrorClass, code?)",
@@ -231,6 +231,20 @@ function testScreenTypeFilter() {
231
231
  var r = screener.screen({ name: "Acme Corporation", type: "individual" });
232
232
  check("screen type filter: entity matches still allowed",
233
233
  r.match === true && r.hits[0].type === "entity");
234
+
235
+ // A sanctions screen MUST over-match on the NAME: a sanctioned INDIVIDUAL
236
+ // screened with a DIFFERING operator-asserted type (the counterparty's
237
+ // unverified self-classification) must STILL match — previously the type
238
+ // filter `continue`d past it, an under-match / fail-open false negative.
239
+ var rv = screener.screen({ name: "JOHN ALEXANDER SMITH", type: "vessel" });
240
+ check("screen: sanctioned individual still matches under a differing type",
241
+ rv.match === true && rv.hits.length >= 1 && rv.hits[0].type === "individual");
242
+ check("screen: type mismatch surfaced as non-dispositive typeMatch:false",
243
+ rv.hits[0].typeMatch === false);
244
+ var rmatch = screener.screen({ name: "JOHN ALEXANDER SMITH", type: "individual" });
245
+ check("screen: typeMatch true when asserted type matches", rmatch.hits[0].typeMatch === true);
246
+ var rnotype = screener.screen({ name: "JOHN ALEXANDER SMITH" });
247
+ check("screen: typeMatch null when no type asserted", rnotype.hits[0].typeMatch === null);
234
248
  }
235
249
 
236
250
  function testScreenCorrectAlgorithmEcho() {
@@ -0,0 +1,115 @@
1
+ "use strict";
2
+ /**
3
+ * b.middleware.compression — range/partial-content pass-through.
4
+ *
5
+ * A 206 Partial Content or any Content-Range response must NOT be
6
+ * transformed (RFC 7233 §4.1): compressing it drops Content-Length but
7
+ * leaves Content-Range advertising an uncompressed byte interval over a
8
+ * now-compressed body, corrupting range-assembling clients.
9
+ */
10
+
11
+ var helpers = require("../helpers");
12
+ var b = helpers.b;
13
+ var check = helpers.check;
14
+
15
+ function _fakeRes() {
16
+ var headers = {};
17
+ var chunks = [];
18
+ var ended = false;
19
+ return {
20
+ statusCode: 200,
21
+ writeHead: function (status, a, c) {
22
+ this.statusCode = status;
23
+ var hdrs = (c && typeof c === "object") ? c
24
+ : (a && typeof a === "object" && !Array.isArray(a)) ? a : null;
25
+ if (hdrs) {
26
+ var keys = Object.keys(hdrs);
27
+ for (var i = 0; i < keys.length; i++) headers[keys[i].toLowerCase()] = hdrs[keys[i]];
28
+ }
29
+ return this;
30
+ },
31
+ write: function (chunk) {
32
+ if (chunk != null) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
33
+ return true;
34
+ },
35
+ end: function (chunk) {
36
+ if (chunk != null) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
37
+ ended = true;
38
+ return this;
39
+ },
40
+ setHeader: function (k, v) { headers[k.toLowerCase()] = v; },
41
+ getHeader: function (k) { return headers[k.toLowerCase()]; },
42
+ removeHeader: function (k) { delete headers[k.toLowerCase()]; },
43
+ emit: function () {},
44
+ _headers: headers,
45
+ _body: function () { return Buffer.concat(chunks); },
46
+ _ended: function () { return ended; },
47
+ };
48
+ }
49
+
50
+ function testPartialContent206NotCompressed() {
51
+ var mw = b.middleware.compression({ threshold: 0 });
52
+ var req = { headers: { "accept-encoding": "gzip" } };
53
+ var res = _fakeRes();
54
+ var body = Buffer.from("X".repeat(4096));
55
+ mw(req, res, function () {});
56
+ res.writeHead(206, {
57
+ "Content-Type": "text/plain",
58
+ "Content-Range": "bytes 0-4095/100000",
59
+ "Content-Length": String(body.length),
60
+ });
61
+ res.end(body);
62
+ check("206 response carries no Content-Encoding", !res.getHeader("content-encoding"));
63
+ check("206 body passes through verbatim (uncompressed)", res._body().equals(body));
64
+ }
65
+
66
+ function testContentRange200NotCompressed() {
67
+ // A 200 that still carries a Content-Range header (multi-range / explicit
68
+ // single range over 200) must also pass through untransformed.
69
+ var mw = b.middleware.compression({ threshold: 0 });
70
+ var req = { headers: { "accept-encoding": "gzip" } };
71
+ var res = _fakeRes();
72
+ var body = Buffer.from("Y".repeat(4096));
73
+ mw(req, res, function () {});
74
+ res.writeHead(200, {
75
+ "Content-Type": "text/plain",
76
+ "Content-Range": "bytes 0-4095/8192",
77
+ });
78
+ res.end(body);
79
+ check("Content-Range 200 carries no Content-Encoding", !res.getHeader("content-encoding"));
80
+ check("Content-Range 200 body passes through verbatim", res._body().equals(body));
81
+ }
82
+
83
+ async function testPlain200StillCompresses() {
84
+ // Control: a normal compressible 200 still gets compressed, so the
85
+ // range guard isn't disabling compression wholesale.
86
+ var mw = b.middleware.compression({ threshold: 0 });
87
+ var req = { headers: { "accept-encoding": "gzip" } };
88
+ var res = _fakeRes();
89
+ var body = Buffer.from("Z".repeat(4096));
90
+ mw(req, res, function () {});
91
+ res.writeHead(200, { "Content-Type": "text/plain" });
92
+ res.end(body);
93
+ check("plain compressible 200 sets Content-Encoding: gzip",
94
+ res.getHeader("content-encoding") === "gzip");
95
+ // zlib flushes asynchronously; wait for the wrapped end before asserting.
96
+ await helpers.waitUntil(function () { return res._ended(); }, {
97
+ timeoutMs: 5000,
98
+ label: "compression-range: compressed response flushed",
99
+ });
100
+ check("plain compressible 200 body is transformed (smaller than source)",
101
+ res._body().length > 0 && res._body().length < body.length);
102
+ }
103
+
104
+ async function run() {
105
+ testPartialContent206NotCompressed();
106
+ testContentRange200NotCompressed();
107
+ await testPlain200StillCompresses();
108
+ }
109
+
110
+ module.exports = { run: run };
111
+
112
+ if (require.main === module) {
113
+ run().then(function () { console.log("OK compression-range — " + helpers.getChecks() + " checks"); })
114
+ .catch(function (e) { console.error("FAIL:", e && e.stack || e); process.exit(1); });
115
+ }
@@ -51,7 +51,7 @@ function _makeTsaCert() {
51
51
  // self-signed (issuer null). Used to assemble a real [leaf, intermediate,
52
52
  // root] chain so the identity-assertion chain walk is exercised through
53
53
  // an intermediate CA, not only a direct-root / self-signed leaf.
54
- function _makeCert(cn, issuer) {
54
+ function _makeCert(cn, issuer, isCa) {
55
55
  var kp = nodeCrypto.generateKeyPairSync("rsa", { modulusLength: 2048 });
56
56
  var spki = kp.publicKey.export({ type: "spki", format: "der" });
57
57
  var subjName = _certName(cn);
@@ -62,7 +62,19 @@ function _makeCert(cn, issuer) {
62
62
  var serial = asn1.writeInteger(Buffer.from([0x2b]));
63
63
  var now = Date.now();
64
64
  var validity = asn1.writeSequence([_utcTime(new Date(now - 86400000)), _utcTime(new Date(now + 86400000 * 3650))]);
65
- var tbs = asn1.writeSequence([version, serial, sigAlgId, issuerName, validity, subjName, spki]);
65
+ var tbsChildren = [version, serial, sigAlgId, issuerName, validity, subjName, spki];
66
+ // Issuer (root / intermediate) certs carry basicConstraints cA:TRUE so the
67
+ // chain walk's cA enforcement (x509Chain.issuerValidlyIssued) accepts them;
68
+ // a leaf omits it (cA:FALSE) as a real end-entity cert does.
69
+ if (isCa) {
70
+ var bcExt = asn1.writeSequence([
71
+ asn1.writeOid("2.5.29.19"), // basicConstraints
72
+ asn1.writeBoolean(true), // critical
73
+ asn1.writeOctetString(asn1.writeSequence([asn1.writeBoolean(true)])), // { cA TRUE }
74
+ ]);
75
+ tbsChildren.push(asn1.writeContextExplicit(3, asn1.writeSequence([bcExt])));
76
+ }
77
+ var tbs = asn1.writeSequence(tbsChildren);
66
78
  var tbsSig = nodeCrypto.sign("sha256", tbs, issuerKey);
67
79
  var certDer = asn1.writeSequence([tbs, sigAlgId, asn1.writeBitString(tbsSig, 0)]);
68
80
  return { name: subjName, key: kp.privateKey, pem: new nodeCrypto.X509Certificate(certDer).toString() };
@@ -321,8 +333,8 @@ async function run() {
321
333
  // x509 chain through an INTERMEDIATE CA: chain [leaf, intermediate]
322
334
  // with the root as the trust anchor must verify — the chain walk uses
323
335
  // chain[1..], not only a direct leaf-vs-anchor test.
324
- var caRoot = _makeCert("CAWG Test Root", null);
325
- var caInter = _makeCert("CAWG Test Intermediate", { name: caRoot.name, key: caRoot.key });
336
+ var caRoot = _makeCert("CAWG Test Root", null, true);
337
+ var caInter = _makeCert("CAWG Test Intermediate", { name: caRoot.name, key: caRoot.key }, true);
326
338
  var caLeaf = _makeCert("Acme Newsroom Leaf", { name: caInter.name, key: caInter.key });
327
339
  var iaChain = b.contentCredentials.verifyIdentityAssertion(ia, orgPair.publicKey, {
328
340
  referencedAssertions: refs,
@@ -332,7 +344,7 @@ async function run() {
332
344
  check("verifyIdentityAssertion: [leaf,intermediate]→root anchor verified (walks intermediates)",
333
345
  iaChain.valid === true && iaChain.verified === true);
334
346
  // The same chain against an unrelated anchor must NOT verify.
335
- var caRogue = _makeCert("Rogue Root", null);
347
+ var caRogue = _makeCert("Rogue Root", null, true);
336
348
  var iaWrongAnchor = b.contentCredentials.verifyIdentityAssertion(ia, orgPair.publicKey, {
337
349
  referencedAssertions: refs,
338
350
  identityCertChainPem: [caLeaf.pem, caInter.pem],
@@ -112,10 +112,11 @@ async function testCorsExplicitSiteOriginRejectsInferredOrigin() {
112
112
  }
113
113
 
114
114
  async function testCorsXForwardedProtoRespected() {
115
- // Behind a TLS terminator: socket is HTTP but Origin claims https.
116
- // With `trustProxy: true`, X-Forwarded-Proto: https flips the inferred
117
- // scheme so same-origin detection matches.
118
- var mw = b.middleware.cors({ origins: [], refuseUnknown: true, trustProxy: true });
115
+ // Behind a TLS terminator: socket is HTTP but Origin claims https. With
116
+ // trustedProxies covering the proxy peer, X-Forwarded-Proto: https flips the
117
+ // inferred scheme so same-origin detection matches — peer-gated, so only a
118
+ // request arriving via the trusted proxy gets the header honored.
119
+ var mw = b.middleware.cors({ origins: [], refuseUnknown: true, trustedProxies: ["127.0.0.0/8"] });
119
120
  var req = _req({
120
121
  method: "POST",
121
122
  headers: {
@@ -126,8 +127,29 @@ async function testCorsXForwardedProtoRespected() {
126
127
  socket: { remoteAddress: "127.0.0.1", encrypted: false },
127
128
  });
128
129
  var out = await _drive(mw, req);
129
- check("X-Forwarded-Proto: https + trustProxy → same-origin pass-through",
130
+ check("X-Forwarded-Proto: https via trusted proxy → same-origin pass-through",
130
131
  out.nextCalled === true);
132
+
133
+ // A bare trustProxy is refused at construction (spoofable).
134
+ var threwBare = false;
135
+ try { b.middleware.cors({ origins: [], refuseUnknown: true, trustProxy: true }); }
136
+ catch (_e) { threwBare = true; }
137
+ check("cors: bare trustProxy refused (spoofable)", threwBare === true);
138
+
139
+ // Forged X-Forwarded-Proto from an UNTRUSTED peer is ignored → inferred
140
+ // origin stays http://… so the https Origin is cross-origin → blocked.
141
+ var forged = _req({
142
+ method: "POST",
143
+ headers: {
144
+ host: "wiki.example.com",
145
+ origin: "https://wiki.example.com",
146
+ "x-forwarded-proto": "https",
147
+ },
148
+ socket: { remoteAddress: "198.51.100.66", encrypted: false },
149
+ });
150
+ var outForged = await _drive(mw, forged);
151
+ check("forged X-Forwarded-Proto from untrusted peer ignored → blocked",
152
+ outForged.nextCalled === false);
131
153
  }
132
154
 
133
155
  async function testCorsXForwardedProtoIgnoredWithoutTrustProxy() {
@@ -169,6 +169,41 @@ async function run() {
169
169
  var shredded = b.db.from("pr_keyed").where({ _id: "row-3" }).first();
170
170
  check("shredded cell reads as absent (null), no crash", shredded.ssn === null && shredded.note === null);
171
171
 
172
+ // ---- B3a: a SHREDDED read must NOT trip the unseal-failure rate cap ----
173
+ // A crypto-shredded key is an EXPECTED absence (the wrap is gone — no oracle
174
+ // to brute-force), not a forged-ciphertext attack. Bulk-reading erased rows
175
+ // (GDPR eraseHard) must not accrue rate-cap failures, else the cap trips and
176
+ // DoS's the live rows (self-DoS). Configure a low threshold and read the
177
+ // shredded row many times for one (actor, table, column): none must throw
178
+ // crypto-field/unseal-rate-exceeded.
179
+ var rateAudits = [];
180
+ b.cryptoField.configureUnsealRateCap({
181
+ threshold: 2, windowMs: 60000, cooldownMs: 300000,
182
+ onAudit: function (ev) { rateAudits.push(ev); },
183
+ });
184
+ var shredTrip = null;
185
+ for (var sr = 0; sr < 8; sr++) {
186
+ try {
187
+ var srRow = b.db.prepare('SELECT * FROM "pr_keyed" WHERE _id = ?').get("row-3");
188
+ b.cryptoField.unsealRow("pr_keyed", srRow, "shred-reader");
189
+ } catch (e) { if (e.code === "crypto-field/unseal-rate-exceeded") shredTrip = e; }
190
+ }
191
+ check("8 shredded reads do NOT trip the rate cap (shred is exempt)", shredTrip === null);
192
+ check("shredded reads emit no rate-exceeded audit",
193
+ rateAudits.every(function (a) { return a.action !== "system.crypto.unseal_rate_exceeded"; }));
194
+ // CONTROL: a genuine forged-ciphertext read on the same tuple still trips —
195
+ // the exemption is specific to the shredded-key case, not a cap bypass.
196
+ var FORGED_ROW = "vault.aad:Zm9yZ2VkLWdhcmJhZ2U=";
197
+ var forgedTrip = null;
198
+ for (var fr = 0; fr < 8; fr++) {
199
+ try {
200
+ b.cryptoField.unsealRow("pr_keyed", { _id: "row-3", ssn: FORGED_ROW }, "forge-reader");
201
+ } catch (e) { if (e.code === "crypto-field/unseal-rate-exceeded") forgedTrip = e; }
202
+ }
203
+ check("forged-ciphertext reads STILL trip the rate cap (exemption is shred-specific)",
204
+ forgedTrip !== null);
205
+ b.cryptoField.clearRateCapForTest();
206
+
172
207
  // ---- FORENSIC RESIDUAL PROOF (the crypto-shred guarantee) ----
173
208
  // Advertised (crypto-field.js destroyPerRowKey): destroying the wrapped
174
209
  // row-secret makes residual ciphertext (WAL / replica / backup)
@@ -243,6 +278,9 @@ async function run() {
243
278
 
244
279
  // ---- ROTATION ROUND-TRIP ----
245
280
  await _rotationRoundTrip();
281
+
282
+ // ---- B3b: crypto-shred under a CUSTOM tablePrefix ----
283
+ await _customPrefixShredLifecycle();
246
284
  } finally {
247
285
  try { b.cryptoField.clearResidencyForTest(); } catch (_e) {}
248
286
  try { fs.rmSync(dir, { recursive: true, force: true }); } catch (_e) {}
@@ -250,6 +288,51 @@ async function run() {
250
288
  console.log("OK — crypto-field per-row-key tests");
251
289
  }
252
290
 
291
+ // The per-row-key registry is ALWAYS local (the crypto-shred substrate lives in
292
+ // the local sqlite even in cluster mode). A custom tablePrefix rewrites only the
293
+ // cluster-mode EXTERNAL names; the local table stays raw (_blamejs_per_row_keys).
294
+ // Regression: crypto-field resolved the registry name through the cluster-prefix
295
+ // resolver, so under a custom prefix it targeted a nonexistent <prefix>per_row_keys
296
+ // table — destroyPerRowKey deleted 0 rows and sealed cells stayed decryptable
297
+ // after eraseHard (a SILENT crypto-shred failure). Prove the full lifecycle
298
+ // (seal -> read -> shred -> read-absent) holds under a custom prefix.
299
+ async function _customPrefixShredLifecycle() {
300
+ var dir = fs.mkdtempSync(path.join(os.tmpdir(), "prk-prefix-"));
301
+ try {
302
+ process.env.BLAMEJS_SKIP_NTP_CHECK = "1";
303
+ b.cluster._resetForTest(); b.audit._resetForTest(); b.vault._resetForTest(); b.db._resetForTest();
304
+ helpers.setTestPassphraseEnv ? helpers.setTestPassphraseEnv() : (function () {
305
+ process.env.BLAMEJS_VAULT_PASSPHRASE = "x".repeat(40);
306
+ process.env.BLAMEJS_AUDIT_SIGNING_PASSPHRASE = "x".repeat(40);
307
+ })();
308
+ await b.vault.init({ dataDir: dir });
309
+ await b.db.init({
310
+ dataDir: dir, tmpDir: path.join(dir, "tmpfs"), allowNonTmpfsTmpDir: true,
311
+ tablePrefix: "acme_", // <-- CUSTOM PREFIX
312
+ schema: KEYED_SCHEMA,
313
+ });
314
+ b.cryptoField.clearResidencyForTest();
315
+ b.cryptoField.declarePerRowKey("pr_keyed", { keySize: 32 });
316
+ b.cryptoField.declarePerRowResidency("pr_keyed", {
317
+ residencyColumn: "dataRegion", allowedTags: ["eu", "us", "global"],
318
+ });
319
+ b.db.from("pr_keyed").insertOne({
320
+ _id: "p1", subjectId: "subj-P", dataRegion: "eu", ssn: "123-45-6789", note: "n",
321
+ });
322
+ check("custom-prefix: keyed read round-trips before shred",
323
+ b.db.from("pr_keyed").where({ _id: "p1" }).first().ssn === "123-45-6789");
324
+ var res = b.cryptoField.destroyPerRowKey("pr_keyed", "p1", b.db);
325
+ check("custom-prefix: destroyPerRowKey deletes the wrap (destroyed === 1)", res.destroyed === 1);
326
+ check("custom-prefix: sealed cell is UNREADABLE after shred (crypto-shred holds)",
327
+ b.db.from("pr_keyed").where({ _id: "p1" }).first().ssn === null);
328
+ b.db.close();
329
+ } finally {
330
+ try { b.cryptoField.clearResidencyForTest(); } catch (_e) {}
331
+ try { b.db._resetForTest(); } catch (_e) {}
332
+ try { fs.rmSync(dir, { recursive: true, force: true }); } catch (_e) {}
333
+ }
334
+ }
335
+
253
336
  // A vault keypair rotation must reseal the wrapped row-secret in
254
337
  // _blamejs_per_row_keys old-root -> new-root (registerTable({aad:true})
255
338
  // wires rotate._rotateColumn). After the swap, the wrap unwraps under the
@@ -137,11 +137,69 @@ async function testFirstOccurrenceWinsForDuplicateCookie() {
137
137
  r2.outcome === "denied" && r2.status === 403);
138
138
  }
139
139
 
140
+ function testMethodsEmptyThrows() {
141
+ // An empty methods array would silently disable the primary CSRF method-gate
142
+ // for all state-changing requests — refuse it at config time.
143
+ var threw = null;
144
+ try { b.middleware.csrfProtect({ cookie: true, methods: [] }); } catch (e) { threw = e; }
145
+ check("csrfProtect({ methods: [] }) refused at config time (no silent CSRF disable)",
146
+ threw && /non-empty array/.test(threw.message || ""));
147
+ }
148
+
149
+ // Two distinct csrf instances on the same response (createApp wires csrf
150
+ // globally AND an operator re-mounts it at the route level) must issue a
151
+ // SINGLE Set-Cookie for the same cookie name — cookie issuance is a
152
+ // response-level resource, deduped by name. Enforcement stays per instance
153
+ // (the next test).
154
+ async function testRedundantMountIssuesSingleCookie() {
155
+ var req = _mockReq({ method: "GET", url: "/", headers: { host: "example.com" } });
156
+ var res = _mockRes();
157
+ var mw1 = b.middleware.csrfProtect({ cookie: true });
158
+ var mw2 = b.middleware.csrfProtect({ cookie: true });
159
+ await new Promise(function (r) { mw1(req, res, r); });
160
+ await new Promise(function (r) { mw2(req, res, r); });
161
+ var sc = res.getHeader("set-cookie");
162
+ var arr = Array.isArray(sc) ? sc : (sc ? [sc] : []);
163
+ var csrfCookies = arr.filter(function (c) { return /^csrf=/.test(c); });
164
+ check("csrf: redundant same-name mount issues a single Set-Cookie",
165
+ csrfCookies.length === 1);
166
+ check("csrf: both instances expose the same req.csrfToken",
167
+ typeof req.csrfToken === "string" && req.csrfToken.length === 64);
168
+ }
169
+
170
+ // The cookie-issuance dedup must NOT disable per-instance enforcement: two
171
+ // distinct instances each validate the double-submit token on a POST. A bad
172
+ // token reaches a deny regardless of which instance runs first — a shared
173
+ // "already handled" flag (the bug the per-instance gate fixed) would let the
174
+ // first instance mark the request handled and skip the second's check.
175
+ async function testDistinctInstancesBothEnforce() {
176
+ var good = b.forms.generateCsrfToken();
177
+ var req = _mockReq({
178
+ method: "POST", url: "/submit",
179
+ headers: { host: "example.com", cookie: "csrf=" + good, "x-csrf-token": "deadbeef" },
180
+ });
181
+ var res = _mockRes();
182
+ var denied = false;
183
+ var origEnd = res.end;
184
+ res.end = function () { denied = true; return origEnd.apply(res, arguments); };
185
+ var mw = b.middleware.csrfProtect({ cookie: true });
186
+ var nextCalled = false;
187
+ await new Promise(function (r) { mw(req, res, function () { nextCalled = true; r(); }); if (denied) r(); });
188
+ // The instance denies the mismatched token (cookie != header), proving
189
+ // enforcement runs per instance and the cookie-dedup did not mark the
190
+ // request "already handled".
191
+ check("csrf: distinct instance enforces double-submit (bad token denied)",
192
+ denied === true && nextCalled === false);
193
+ }
194
+
140
195
  async function run() {
141
196
  await testSuccessPathDoubleSubmit();
142
197
  await testMismatchDenied();
143
198
  await testPoisonedCookieNamesDoNotPollute();
144
199
  await testFirstOccurrenceWinsForDuplicateCookie();
200
+ await testRedundantMountIssuesSingleCookie();
201
+ await testDistinctInstancesBothEnforce();
202
+ testMethodsEmptyThrows();
145
203
  }
146
204
 
147
205
  module.exports = { run: run };
@@ -41,6 +41,36 @@ function run() {
41
41
  } catch (e) { threw = /gatekeeper-refused/.test(e.code); }
42
42
  check("dataAct: designated gatekeeper share refused (Art 32 §1)", threw);
43
43
 
44
+ // B10: a gatekeeper as the FINAL domain label must still be refused. The old
45
+ // substring `indexOf(g + ".")` test missed "blog.google" / "data.amazon" (the
46
+ // gatekeeper token is the last label, e.g. the real ".google" TLD), letting an
47
+ // Art 32 §1 share through. Label matching closes the bypass.
48
+ ["blog.google", "drive.google", "data.amazon", "play.google.com",
49
+ "https://meta.com/share", "user@booking.com"].forEach(function (recip) {
50
+ var bypassed = false, refused = false;
51
+ try {
52
+ b.dataAct.shareWithThirdParty({
53
+ productId: "thermo-v3", userId: "user-1", recipient: recip, scope: "x",
54
+ });
55
+ bypassed = true; // no throw → the gate let a gatekeeper through
56
+ } catch (e) { refused = /gatekeeper-refused/.test(e.code); }
57
+ check("B10: gatekeeper recipient '" + recip + "' refused (no final-label bypass)",
58
+ refused && !bypassed);
59
+ });
60
+
61
+ // B10: a NON-gatekeeper whose name merely CONTAINS a gatekeeper token must NOT
62
+ // be over-refused (the substring test false-flagged "notgoogle.com").
63
+ ["notgoogle.com", "evilgoogle.com", "google-partners.example.com"].forEach(function (recip) {
64
+ var ok = false;
65
+ try {
66
+ b.dataAct.shareWithThirdParty({
67
+ productId: "thermo-v3", userId: "user-1", recipient: recip, scope: "x",
68
+ });
69
+ ok = true;
70
+ } catch (_e) { ok = false; }
71
+ check("B10: non-gatekeeper '" + recip + "' is NOT over-refused", ok);
72
+ });
73
+
44
74
  // Override accepted with audited reason.
45
75
  b.dataAct.shareWithThirdParty({
46
76
  productId: "thermo-v3", userId: "user-1",