@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
@@ -37,6 +37,29 @@ var backupCrypto = require("../../lib/backup/crypto");
37
37
 
38
38
  var PASS = Buffer.from("operator-bundle-passphrase-not-secret");
39
39
 
40
+ // Wire-form conversions mirroring audit-tools' _wireFormToRow /
41
+ // _rowToWireForm (hex: prefix <-> Buffer) so a test can rebuild the exact
42
+ // rows JSONL the verifier re-parses.
43
+ function _wireToRow(wire) {
44
+ var out = {};
45
+ Object.keys(wire).forEach(function (k) {
46
+ var v = wire[k];
47
+ if (typeof v === "string" && v.indexOf("hex:") === 0) out[k] = Buffer.from(v.slice(4), "hex");
48
+ else out[k] = v;
49
+ });
50
+ return out;
51
+ }
52
+ function _rowToWire(row) {
53
+ var out = {};
54
+ Object.keys(row).forEach(function (k) {
55
+ var v = row[k];
56
+ if (Buffer.isBuffer(v)) out[k] = "hex:" + v.toString("hex");
57
+ else if (v === undefined) out[k] = null;
58
+ else out[k] = v;
59
+ });
60
+ return out;
61
+ }
62
+
40
63
  async function _seedAuditRows(count) {
41
64
  b.audit.registerNamespace("test");
42
65
  for (var i = 0; i < count; i++) {
@@ -191,6 +214,105 @@ async function run() {
191
214
  r3.threw === false && r3.ok === false &&
192
215
  /signature/i.test(r3.reason || ""));
193
216
 
217
+ // ---- Tamper 4: anchor-rebind — fabricate the row slice but keep the
218
+ // GENUINE signed checkpoint. The checkpoint's atRowHash anchors the real
219
+ // chain; nothing in the bundle ties it to the fabricated rows. A verifier
220
+ // that only checks (signature valid + atMonotonicCounter >= lastCounter)
221
+ // reports a wholly fabricated archive as authentic — defeating the
222
+ // documented "tampering that recomputes hashes still fails checkpoint
223
+ // verification" guarantee. The fabricated chain is internally consistent
224
+ // (recomputed via b.auditChain.computeRowHash — no key needed), so it
225
+ // passes the chain walk + manifest first/last checks; only binding the
226
+ // signed atRowHash to the slice's anchored row catches it.
227
+ var t4 = path.join(dir, "bundles", "tamper-anchor-rebind");
228
+ _copyDir(cleanDir, t4);
229
+ var m4Path = path.join(t4, "manifest.json");
230
+ var m4 = JSON.parse(fs.readFileSync(m4Path, "utf8"));
231
+ var rows4Enc = fs.readFileSync(path.join(t4, "rows.enc"));
232
+ var rows4Plain = (await backupCrypto.decryptWithPassphrase(rows4Enc, PASS, m4.salts.rows)).toString("utf8");
233
+ var genuineWire = rows4Plain.split("\n").filter(function (l) { return l.length > 0; })
234
+ .map(function (l) { return JSON.parse(l); });
235
+ // Rebuild a fabricated, internally-consistent chain off the SAME
236
+ // predecessor + counters but with phantom actions that never happened.
237
+ // The row-hash preimage excludes prevHash/rowHash/nonce/fencingToken
238
+ // (mirrors audit-tools _verifyChainSlice), so the recompute matches the
239
+ // verifier's chain walk and only the signed atRowHash bind can catch it.
240
+ var prev4 = m4.range.predecessorRowHash;
241
+ var fabricatedWire = genuineWire.map(function (w) {
242
+ var row = _wireToRow(w);
243
+ row.action = "phantom.never.happened";
244
+ row.outcome = "success";
245
+ row.prevHash = prev4;
246
+ var fields = Object.assign({}, row);
247
+ delete fields.prevHash; delete fields.rowHash; delete fields.nonce; delete fields.fencingToken;
248
+ row.rowHash = b.auditChain.computeRowHash(prev4, fields, row.nonce);
249
+ prev4 = row.rowHash;
250
+ return _rowToWire(row);
251
+ });
252
+ check("tamper-anchor: fabricated last rowHash differs from the genuine anchor",
253
+ fabricatedWire[fabricatedWire.length - 1].rowHash !== m4.checkpoint.atRowHash);
254
+ var fabJsonl = fabricatedWire.map(function (w) { return JSON.stringify(w); }).join("\n") + "\n";
255
+ var fabEnc = await backupCrypto.encryptWithFreshSalt(fabJsonl, PASS);
256
+ fs.writeFileSync(path.join(t4, "rows.enc"), fabEnc.encrypted);
257
+ m4.salts.rows = fabEnc.salt;
258
+ m4.checksum.rowsSha3_512 = backupCrypto.checksum(fabEnc.encrypted);
259
+ m4.range.firstRowHash = fabricatedWire[0].rowHash;
260
+ m4.range.lastRowHash = fabricatedWire[fabricatedWire.length - 1].rowHash;
261
+ // checkpoint.enc + manifest.checkpoint left GENUINE on purpose.
262
+ fs.writeFileSync(m4Path, JSON.stringify(m4));
263
+ var r4 = await _verify(t4);
264
+ check("tamper-anchor: fabricated archive is REJECTED (checkpoint not bound to slice)", r4.rejected === true);
265
+ check("tamper-anchor: failure points at the checkpoint anchor binding",
266
+ (!r4.threw && /anchor|atRowHash|checkpoint/i.test(r4.reason || "")) ||
267
+ (r4.threw && /anchor|atRowHash|checkpoint/i.test(r4.message || "")));
268
+
269
+ // ---- Regression: a LEGITIMATE partial archive whose covering checkpoint
270
+ // anchors a counter BEYOND the purgeable slice must still verify. The
271
+ // bundle carries the rows between the slice tip and the anchored counter
272
+ // as verification witnesses so the chain walks up to the signed atRowHash;
273
+ // purge still only touches the [first..lastCounter] slice. Driven through
274
+ // archive()'s injectable readers for determinism (no wall-clock gaps).
275
+ var fullDir = path.join(dir, "bundles", "archive-full10");
276
+ // Seed up to a tip of 10 and checkpoint THERE (the only checkpoint beyond
277
+ // counter 6), then snapshot all 10 rows + that checkpoint via a full
278
+ // archive so we can replay them through injected readers.
279
+ await _seedAuditRows(4); // counters 7..10 (6 already seeded)
280
+ var ckpt10 = await b.audit.checkpoint();
281
+ check("a checkpoint beyond the slice was anchored", Number(ckpt10.atMonotonicCounter) >= 10);
282
+ var full = await b.auditTools.archive({ before: Date.now() + 60000, out: fullDir, passphrase: PASS });
283
+ check("full archive carries >=10 rows", full.rowCount >= 10);
284
+ var fullM = JSON.parse(fs.readFileSync(path.join(fullDir, "manifest.json"), "utf8"));
285
+ var fullRowsPlain = (await backupCrypto.decryptWithPassphrase(
286
+ fs.readFileSync(path.join(fullDir, "rows.enc")), PASS, fullM.salts.rows)).toString("utf8");
287
+ var allRows = fullRowsPlain.split("\n").filter(function (l) { return l.length > 0; })
288
+ .map(function (l) { return _wireToRow(JSON.parse(l)); });
289
+ var fullCkptPlain = (await backupCrypto.decryptWithPassphrase(
290
+ fs.readFileSync(path.join(fullDir, "checkpoint.enc")), PASS, fullM.salts.checkpoint)).toString("utf8");
291
+ var genuineCkpt10 = _wireToRow(JSON.parse(fullCkptPlain));
292
+ var sliceLast = Number(allRows[5].monotonicCounter); // 6th row's counter (purge boundary)
293
+ var partialDir = path.join(dir, "bundles", "archive-partial-witness");
294
+ var partial = await b.auditTools.archive({
295
+ before: Date.now() + 60000,
296
+ out: partialDir,
297
+ passphrase: PASS,
298
+ readRows: function (criteria) {
299
+ if (criteria.firstCounter != null) {
300
+ // witness request: rows (sliceLast .. anchorCounter]
301
+ return allRows.filter(function (r) {
302
+ var c = Number(r.monotonicCounter);
303
+ return c >= criteria.firstCounter && c <= criteria.lastCounter;
304
+ });
305
+ }
306
+ return allRows.slice(0, 6); // purgeable slice = first 6 rows
307
+ },
308
+ readCoveringCheckpoint: function () { return genuineCkpt10; }, // anchors counter 10
309
+ readPredecessorRowHash: function () { return b.auditChain.ZERO_HASH; }, // slice starts at counter 1
310
+ });
311
+ check("partial archive purge range stops at the slice tip (witnesses excluded from purge range)",
312
+ Number(partial.range.lastCounter) === sliceLast);
313
+ var rp = await _verify(partialDir);
314
+ check("legit partial archive (anchor beyond slice) verifies via witness rows", rp.threw === false && rp.ok === true);
315
+
194
316
  console.log("OK — audit verifyBundle tamper-detection (" + helpers.getChecks() + " checks)");
195
317
  } finally {
196
318
  await teardownTestDb(dir);
@@ -308,10 +308,47 @@ async function testEscalationFnInvoked() {
308
308
  check("escalationFn invoked when threshold crossed", escalated === 1);
309
309
  }
310
310
 
311
+ // Concurrent failures on one key must not lose increments. A read-modify-
312
+ // write over an async store races without per-key serialization: N parallel
313
+ // recordFailure calls each read the same pre-write counter, so the failure
314
+ // count lands at 1 and the gate never crosses its thresholds. The same N
315
+ // calls run sequentially must reach the locked stage; the concurrent set
316
+ // must reach the identical stage.
317
+ async function testConcurrentFailuresDoNotLoseIncrements() {
318
+ var lockout = _fakeLockout();
319
+ var gate = b.authBotChallenge.create({
320
+ botGuard: _fakeBotGuard("pass"),
321
+ lockout: lockout,
322
+ sessionStore: _memoryStore(),
323
+ threshold: 2,
324
+ escalationThreshold: 4,
325
+ audit: _captureAudit(),
326
+ });
327
+ var key = "race@example.com";
328
+
329
+ var results = await Promise.all([
330
+ gate.recordFailure(key),
331
+ gate.recordFailure(key),
332
+ gate.recordFailure(key),
333
+ gate.recordFailure(key),
334
+ ]);
335
+ // The serialized chain produces a strictly increasing failure count.
336
+ var failures = results.map(function (r) { return r.failures; }).sort(function (a, c) { return a - c; });
337
+ check("4 concurrent failures increment to 1..4 (no lost update)",
338
+ failures[0] === 1 && failures[1] === 2 && failures[2] === 3 && failures[3] === 4);
339
+ var locked = await gate.check(key);
340
+ check("4 concurrent failures reach the same locked stage as sequential",
341
+ locked.stage === "locked");
342
+ // The lockout subscriber sees every failure, not a collapsed single call.
343
+ check("lockout subscriber receives all 4 propagated failures",
344
+ lockout.calls.fail === 4);
345
+ }
346
+
311
347
  async function run() {
312
348
  testSurface();
313
349
  testCreateRejectsBadOpts();
314
350
  await testStaircaseAdvances();
351
+ await testConcurrentFailuresDoNotLoseIncrements();
315
352
  await testMiddlewareChallengeFn();
316
353
  await testMiddlewareLockedReturns423();
317
354
  await testRecordSuccessClears();
@@ -291,7 +291,7 @@ function testPrmSignedMetadataEmits() {
291
291
 
292
292
  // -------- AUTH-22 — CIBA parseNotification lowercase only --------
293
293
 
294
- function testCibaParseNotificationLowercaseOnly() {
294
+ async function testCibaParseNotificationLowercaseOnly() {
295
295
  // Lowercase-Authorization is what node:http delivers. Capital-A
296
296
  // fallback is now structurally removed. We exercise the rejection
297
297
  // path with no header at all — the lowercase-only path is still
@@ -306,7 +306,9 @@ function testCibaParseNotificationLowercaseOnly() {
306
306
  clientNotificationToken: "abc-token-very-long-and-opaque-enough-for-ciba-minimum-entropy-guard-padding",
307
307
  });
308
308
  var threw = null;
309
- try { ciba.parseNotification({ headers: {} }, { body: {} }); }
309
+ // parseNotification is async (it verifies a pushed id_token) — the
310
+ // missing-bearer refusal surfaces as a rejection.
311
+ try { await ciba.parseNotification({ headers: {} }, { body: {} }); }
310
312
  catch (e) { threw = e; }
311
313
  check("AUTH-22 — parseNotification requires lowercase 'authorization' header",
312
314
  threw && /missing-bearer/.test(threw.code || ""));
@@ -340,7 +342,7 @@ async function run() {
340
342
  await testPasskeyExtensionsAllowlist();
341
343
  testPrmHttpsEnforced();
342
344
  testPrmSignedMetadataEmits();
343
- testCibaParseNotificationLowercaseOnly();
345
+ await testCibaParseNotificationLowercaseOnly();
344
346
  testDpopMiddlewareShutdownExposed();
345
347
  }
346
348
 
@@ -208,6 +208,26 @@ async function testRecordFailureCounter() {
208
208
  check("check: bob is unaffected", bob.locked === false && bob.attempts === 0);
209
209
  }
210
210
 
211
+ // Concurrent failures on one key must each be counted. recordFailure reads
212
+ // the counter from the async cache, increments, and writes it back; without
213
+ // per-key serialization, N parallel calls all read the same pre-write value
214
+ // and the counter lands at 1 — an attacker who fires attempts in parallel
215
+ // stays under the lockout threshold forever.
216
+ async function testConcurrentFailuresAllCounted() {
217
+ var lockout = b.auth.lockout.create({
218
+ cache: _newCache("ns-race"), namespace: "login", maxAttempts: 10,
219
+ });
220
+ await Promise.all([
221
+ lockout.recordFailure("eve"),
222
+ lockout.recordFailure("eve"),
223
+ lockout.recordFailure("eve"),
224
+ lockout.recordFailure("eve"),
225
+ lockout.recordFailure("eve"),
226
+ ]);
227
+ var state = await lockout.check("eve");
228
+ check("5 concurrent failures all counted (no lost update)", state.attempts === 5);
229
+ }
230
+
211
231
  async function testNonMutatingCheck() {
212
232
  var lockout = b.auth.lockout.create({
213
233
  cache: _newCache("ns-nonmutating"), namespace: "login", maxAttempts: 5,
@@ -525,6 +545,7 @@ async function run() {
525
545
  testCreateRejectsBadOpts();
526
546
  await testKeyValidation();
527
547
  await testRecordFailureCounter();
548
+ await testConcurrentFailuresAllCounted();
528
549
  await testNonMutatingCheck();
529
550
  await testRecordSuccessClears();
530
551
  await testExponentialLadder();
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ // b.auth.statusList — IETF Token Status List (draft) revocation bitstring.
3
+ //
4
+ // B11: the relying-party read path (fromJwt().list.get) had NO bounds check —
5
+ // _getAt over-reads the inflated buffer and returns 0 for an out-of-range index,
6
+ // and status 0 = VALID. So a credential whose status_list index points PAST the
7
+ // decoded list read as "not revoked": a fail-OPEN revocation bypass. create().get
8
+ // already throws on a bad index; fromJwt().get must match (fail closed).
9
+ //
10
+ // RED on the buggy tree: fromJwt(...).list.get(size + N) returns 0 (valid).
11
+ // GREEN after the fix: it throws status-list/bad-index.
12
+ //
13
+ // Also the first dedicated behavioral coverage for the primitive (was only an
14
+ // export-presence check), driving the real create -> toJwt -> fromJwt path.
15
+
16
+ var nodeCrypto = require("node:crypto");
17
+ var helpers = require("../helpers");
18
+ var b = helpers.b;
19
+ var check = helpers.check;
20
+
21
+ async function run() {
22
+ check("b.auth.statusList.create is a function", typeof b.auth.statusList.create === "function");
23
+ check("b.auth.statusList.fromJwt is a function", typeof b.auth.statusList.fromJwt === "function");
24
+
25
+ // ML-DSA-87 is available on the Node 24 CI floor (SLH-DSA keygen is Node-26+).
26
+ var kp = nodeCrypto.generateKeyPairSync("ml-dsa-87");
27
+
28
+ var list = b.auth.statusList.create({ size: 64, bits: 1 });
29
+ list.set(5, 1); // revoke index 5
30
+
31
+ // create()/get already fail closed on a bad index (control for the contract).
32
+ var createThrew = null;
33
+ try { list.get(64); } catch (e) { createThrew = e.code; }
34
+ check("create().get throws on an out-of-range index (fail closed)",
35
+ createThrew === "status-list/bad-index");
36
+
37
+ var token = await list.toJwt({
38
+ issuer: "https://issuer.example", subject: "https://issuer.example/sl/1",
39
+ privateKey: kp.privateKey, algorithm: "ML-DSA-87",
40
+ });
41
+ var parsed = await b.auth.statusList.fromJwt(token, {
42
+ publicKey: kp.publicKey, algorithms: ["ML-DSA-87"],
43
+ expectedIssuer: "https://issuer.example",
44
+ });
45
+
46
+ check("fromJwt round-trips the decoded list size", parsed.list.size === 64);
47
+ check("fromJwt: revoked index reads as revoked (status 1)", parsed.list.get(5) === 1);
48
+ check("fromJwt: an unset index reads as valid (status 0)", parsed.list.get(0) === 0);
49
+
50
+ // B11: the out-of-range read MUST fail closed, not read 0/valid.
51
+ var oobThrew = null, oobValue;
52
+ try { oobValue = parsed.list.get(parsed.list.size + 10); }
53
+ catch (e) { oobThrew = e.code; }
54
+ check("fromJwt().get throws on an out-of-range index (no fail-open revocation bypass)",
55
+ oobThrew === "status-list/bad-index");
56
+ check("fromJwt().get did NOT return a status for the out-of-range index",
57
+ oobValue === undefined);
58
+ // Negative + non-integer indices fail closed too.
59
+ var negThrew = null;
60
+ try { parsed.list.get(-1); } catch (e) { negThrew = e.code; }
61
+ check("fromJwt().get fails closed on a negative index", negThrew === "status-list/bad-index");
62
+ var fracThrew = null;
63
+ try { parsed.list.get(3.5); } catch (e) { fracThrew = e.code; }
64
+ check("fromJwt().get fails closed on a non-integer index", fracThrew === "status-list/bad-index");
65
+
66
+ console.log("OK — auth status-list (" + helpers.getChecks() + " checks)");
67
+ }
68
+
69
+ module.exports = { run: run };
70
+ if (require.main === module) {
71
+ run().then(function () { process.exit(0); })
72
+ .catch(function (err) { process.exitCode = 1; throw err; });
73
+ }
@@ -91,12 +91,29 @@ function testOverridesAndSkips() {
91
91
  check("string blockedAgents pattern is refused", threw === "bot-guard/bad-pattern");
92
92
  }
93
93
 
94
+ function testPeerGatedAuditIp() {
95
+ // Audit-attribution IP can be peer-gated so a forged X-Forwarded-For from a
96
+ // direct caller can't pollute it. Construction wiring + validation.
97
+ var okTrusted = true;
98
+ try { b.middleware.botGuard({ trustedProxies: ["10.0.0.0/8"] }); } catch (_e) { okTrusted = false; }
99
+ check("botGuard: trustedProxies accepted", okTrusted === true);
100
+
101
+ var okResolver = true;
102
+ try { b.middleware.botGuard({ clientIpResolver: function () { return "1.2.3.4"; } }); } catch (_e) { okResolver = false; }
103
+ check("botGuard: clientIpResolver accepted", okResolver === true);
104
+
105
+ var threwCidr = null;
106
+ try { b.middleware.botGuard({ trustedProxies: ["nope"] }); } catch (e) { threwCidr = e.code; }
107
+ check("botGuard: malformed trustedProxies CIDR refused", threwCidr === "bot-guard/bad-opt");
108
+ }
109
+
94
110
  async function run() {
95
111
  testSurface();
96
112
  testSecFetchNeverBlocks();
97
113
  testBotsStillBlocked();
98
114
  testTagModeAdvisory();
99
115
  testOverridesAndSkips();
116
+ testPeerGatedAuditIp();
100
117
  }
101
118
  module.exports = { run: run };
102
119
  if (require.main === module) { run().then(function () { console.log("[bot-guard] OK — " + helpers.getChecks() + " checks passed"); }, function (e) { console.error("FAIL: " + helpers.formatErr(e)); process.exit(1); }); }
@@ -74,12 +74,26 @@ function testInitOptsValidation() {
74
74
  try { b.breakGlass.init({ now: 123 }); } catch (_e) { threwNow = true; }
75
75
  check("init: removed `now` opt throws", threwNow);
76
76
 
77
- // trustProxy is still honored default behavior unchanged.
77
+ // A bare trustProxy is refusedthe grant IP pin would bind to a forgeable
78
+ // X-Forwarded-For. Operators declare peer-gating instead.
78
79
  var threwTrustProxy = false;
79
80
  try { b.breakGlass.init({ trustProxy: true }); } catch (_e) { threwTrustProxy = true; }
80
- check("init: trustProxy still accepted", !threwTrustProxy);
81
+ check("init: bare trustProxy refused (spoofable IP pin)", threwTrustProxy);
81
82
 
82
- // bare init() with no opts still works.
83
+ var threwTrusted = false;
84
+ try { b.breakGlass.init({ trustedProxies: ["10.0.0.0/8"] }); } catch (_e) { threwTrusted = true; }
85
+ check("init: trustedProxies accepted", !threwTrusted);
86
+
87
+ var threwResolver = false;
88
+ try { b.breakGlass.init({ clientIpResolver: function (rq) { return rq && rq.headers && rq.headers["true-client-ip"]; } }); }
89
+ catch (_e) { threwResolver = true; }
90
+ check("init: clientIpResolver accepted", !threwResolver);
91
+
92
+ var threwBadCidr = false;
93
+ try { b.breakGlass.init({ trustedProxies: ["nope"] }); } catch (_e) { threwBadCidr = true; }
94
+ check("init: malformed trustedProxies CIDR refused", threwBadCidr);
95
+
96
+ // bare init() with no opts still works (resolves the socket address).
83
97
  var threwBare = false;
84
98
  try { b.breakGlass.init(); } catch (_e) { threwBare = true; }
85
99
  check("init: no-opts init still works", !threwBare);
@@ -526,6 +540,50 @@ async function testIpPinEnforcement() {
526
540
  }
527
541
  }
528
542
 
543
+ async function testIpPinPeerGated() {
544
+ // With trustedProxies, the grant IP pins to the real client behind the
545
+ // proxy — and a direct attacker forging X-Forwarded-For cannot satisfy it.
546
+ var tmpDir = _tmp();
547
+ await setupTestDb(tmpDir);
548
+ try {
549
+ b.breakGlass.init({ trustedProxies: ["10.0.0.0/8"] });
550
+ b.queue.init({ backends: { primary: { protocol: "local" } } });
551
+ var jid = await b.queue.enqueue("ip-pin-pg-q", { secret: "row-ip-pin-pg" });
552
+ await b.breakGlass.policy.set("_blamejs_jobs", {
553
+ columns: ["payload"], factors: ["totp"], maxRowsPerGrant: 5,
554
+ pinIp: true, sessionPin: false,
555
+ });
556
+ var totp = _validTotp();
557
+ // Mint behind the trusted proxy: peer 10.0.0.9 trusted → pin to the
558
+ // forwarded client 203.0.113.7, NOT the proxy address.
559
+ var grant = await b.breakGlass.grant({
560
+ req: _fakeReq({ socket: { remoteAddress: "10.0.0.9" }, headers: { "x-forwarded-for": "203.0.113.7" } }),
561
+ table: "_blamejs_jobs",
562
+ reason: "ip-pin peer-gated: minting behind trusted proxy for the real client",
563
+ factor: { type: "totp", code: totp.code, secret: totp.secret },
564
+ });
565
+
566
+ // Direct attacker forging the client IP via XFF (untrusted peer) → refused.
567
+ var threwSpoof = null;
568
+ try {
569
+ await b.breakGlass.unsealRow(grant, "_blamejs_jobs", jid.jobId,
570
+ { req: _fakeReq({ socket: { remoteAddress: "198.51.100.66" }, headers: { "x-forwarded-for": "203.0.113.7" } }) });
571
+ } catch (e) { threwSpoof = e; }
572
+ check("ip-pin peer-gated: forged XFF redeem refused",
573
+ threwSpoof && /breakglass\/grant-ip-mismatch/.test(threwSpoof.code));
574
+
575
+ // Legitimate redeem through the proxy → succeeds (resolves to 203.0.113.7).
576
+ var ok = await b.breakGlass.unsealRow(grant, "_blamejs_jobs", jid.jobId,
577
+ { req: _fakeReq({ socket: { remoteAddress: "10.0.0.9" }, headers: { "x-forwarded-for": "203.0.113.7" } }) });
578
+ check("ip-pin peer-gated: proxied redeem of real client succeeds",
579
+ ok && ok.payload && ok.payload.indexOf("row-ip-pin-pg") !== -1);
580
+
581
+ try { await b.queue.shutdown({ timeoutMs: 200 }); } catch (_e) {}
582
+ } finally {
583
+ await teardownTestDb(tmpDir);
584
+ }
585
+ }
586
+
529
587
  async function testSessionPinEnforcement() {
530
588
  var tmpDir = _tmp();
531
589
  await setupTestDb(tmpDir);
@@ -1079,6 +1137,7 @@ async function run() {
1079
1137
  await testSweepExpiredGrants();
1080
1138
  // grant binding enforcement (IP / session pin + fail-closed) + TOTP replay
1081
1139
  await testIpPinEnforcement();
1140
+ await testIpPinPeerGated();
1082
1141
  await testSessionPinEnforcement();
1083
1142
  await testIpPinFailClosedOnNullBinding();
1084
1143
  await testTotpReplayDefense();
@@ -61,6 +61,18 @@ function testUnknownTokenRefused() {
61
61
  try { b.middleware.clearSiteData({ types: ["bogus"] }); }
62
62
  catch (e) { threw = e instanceof TypeError && /unknown type/.test(e.message); }
63
63
  check("clearSiteData: unknown token refused at config-time", threw);
64
+
65
+ // B13: an Object.prototype member name must NOT pass the allowlist. A bracket
66
+ // lookup on the plain-object KNOWN_TYPES resolved inherited functions to
67
+ // truthy, so "toString" / "constructor" / "hasOwnProperty" slipped through and
68
+ // were emitted as bogus directives (prototype shadowing).
69
+ var csd = b.middleware._modules.clearSiteData;
70
+ ["toString", "constructor", "hasOwnProperty", "__proto__", "valueOf"].forEach(function (proto) {
71
+ var t2 = false;
72
+ try { csd.headerValue([proto]); }
73
+ catch (e) { t2 = e instanceof TypeError && /unknown type/.test(e.message); }
74
+ check("clearSiteData: prototype member '" + proto + "' refused (no proto-shadow)", t2);
75
+ });
64
76
  }
65
77
 
66
78
  function testEmptyArrayRefused() {
@@ -102,12 +102,52 @@ async function testTransactionRejectsBadArg() {
102
102
  check("tx: non-function arg rejected", threw && threw.code === "cluster-storage/bad-arg");
103
103
  }
104
104
 
105
+ // A WITH (CTE) read must reach the caller's row set. The local-exec method
106
+ // choice keyed on a leading "SELECT" mis-routed "WITH c AS (...) SELECT ..." to
107
+ // .run(), which on node:sqlite returns only a changes count and SILENTLY DROPS
108
+ // the rows — the caller saw an empty result. The classifier now resolves the
109
+ // CTE's effective verb so the read uses .all().
110
+ async function testCteReadReturnsRows() {
111
+ var tmp = fs.mkdtempSync(path.join(os.tmpdir(), "cs-cte-read-"));
112
+ try {
113
+ await setupTestDb(tmp, SCHEMA);
114
+ var cs = b.clusterStorage;
115
+ await cs.execute("INSERT INTO cs_tx_t (k, v) VALUES (?, ?)", ["a", 1]);
116
+ await cs.execute("INSERT INTO cs_tx_t (k, v) VALUES (?, ?)", ["b", 2]);
117
+
118
+ // Plain SELECT control — already worked.
119
+ var plain = await cs.execute("SELECT k, v FROM cs_tx_t ORDER BY k");
120
+ check("plain SELECT returns both rows", plain.rows.length === 2);
121
+
122
+ // CTE read — the regression. Was: rows.length === 0 (rows dropped by .run()).
123
+ var cte = await cs.execute(
124
+ "WITH picked AS (SELECT k, v FROM cs_tx_t WHERE v >= ?) SELECT k, v FROM picked ORDER BY k", [1]);
125
+ check("WITH (CTE) read returns rows (not silently dropped)", cte.rows.length === 2);
126
+ check("WITH (CTE) read returns the actual data", cte.rows[0].k === "a" && cte.rows[1].k === "b");
127
+
128
+ // executeOne over a CTE read resolves the single row too.
129
+ var one = await cs.executeOne(
130
+ "WITH t AS (SELECT COUNT(*) AS n FROM cs_tx_t) SELECT n FROM t");
131
+ check("executeOne over a CTE read resolves the row", one && one.n === 2);
132
+
133
+ // A WITH (CTE) write still applies its changes (not mis-read as a query).
134
+ await cs.execute(
135
+ "WITH src AS (SELECT 'c' AS k, 3 AS v) INSERT INTO cs_tx_t (k, v) SELECT k, v FROM src");
136
+ var after = await cs.executeOne("SELECT COUNT(*) AS n FROM cs_tx_t");
137
+ check("WITH (CTE) write persisted its row", after.n === 3);
138
+ } finally {
139
+ b.db._resetForTest();
140
+ await teardownTestDb(tmp);
141
+ }
142
+ }
143
+
105
144
  async function run() {
106
145
  testSurface();
107
146
  await testTransactionCommits();
108
147
  await testTransactionRollsBackOnThrow();
109
148
  await testTransactionSerializesExecute();
110
149
  await testTransactionRejectsBadArg();
150
+ await testCteReadReturnsRows();
111
151
  }
112
152
 
113
153
  module.exports = { run: run };