@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
@@ -108,6 +108,80 @@ async function run() {
108
108
  quotedWhereRefusedCode === "db-query/row-residency-local-mismatch");
109
109
  check("the quoted-WHERE update did not move the row cross-border",
110
110
  (b.db.from("residents").where({ _id: "raw-eu" }).first() || {}).dataRegion === "eu-west-1");
111
+
112
+ // RAW PATH 5: a LEADING SQL COMMENT must not hide the write keyword from the
113
+ // ^-anchored gate. A "/* hint */ INSERT ..." or "-- hint\nUPDATE ..." that
114
+ // the gate fails to recognize as a write skips residency enforcement
115
+ // entirely — a cross-border-write bypass. Strip leading comments before the
116
+ // write-detection so the gate still fires.
117
+ var blockCommentRefusedCode = codeOf(function () {
118
+ b.db.runSql(
119
+ "/* region hint */ INSERT INTO residents (_id, name, dataRegion) VALUES ('raw-bc', 'x', 'us-east-1')");
120
+ });
121
+ check("leading block-comment write is recognized + refused (no comment bypass)",
122
+ blockCommentRefusedCode === "db-query/row-residency-local-mismatch");
123
+ check("leading block-comment cross-border row did not persist",
124
+ b.db.from("residents").where({ _id: "raw-bc" }).first() === null);
125
+
126
+ var lineCommentRefusedCode = codeOf(function () {
127
+ b.db.runSql(
128
+ "-- region hint\nUPDATE residents SET dataRegion='us-east-1' WHERE _id='raw-eu'");
129
+ });
130
+ check("leading line-comment write is recognized + refused (no comment bypass)",
131
+ lineCommentRefusedCode === "db-query/row-residency-local-mismatch");
132
+ check("the leading line-comment update did not move the row cross-border",
133
+ (b.db.from("residents").where({ _id: "raw-eu" }).first() || {}).dataRegion === "eu-west-1");
134
+
135
+ // Stacked comment + whitespace before the keyword is also stripped.
136
+ var stackedRefusedCode = codeOf(function () {
137
+ b.db.runSql(
138
+ "/* a */ -- b\n /* c */ INSERT INTO residents (_id, name, dataRegion) VALUES ('raw-st', 'x', 'us-east-1')");
139
+ });
140
+ check("stacked leading comments are stripped + the write is refused",
141
+ stackedRefusedCode === "db-query/row-residency-local-mismatch");
142
+
143
+ // An in-region leading-comment write still persists (no over-rejection).
144
+ b.db.runSql(
145
+ "/* in-region */ INSERT INTO residents (_id, name, dataRegion) VALUES ('raw-bc-ok', 'z', 'eu-west-1')");
146
+ check("in-region leading-comment write still persists (gate does not over-reject)",
147
+ (b.db.from("residents").where({ _id: "raw-bc-ok" }).first() || {}).dataRegion === "eu-west-1");
148
+
149
+ // RAW PATH 6: a writable-CTE write hides its effective verb behind a `WITH`
150
+ // prefix, so the ^-anchored write-keyword detector misses it — node:sqlite
151
+ // executes `WITH c AS (...) INSERT INTO residents ...` / `WITH ... UPDATE
152
+ // residents SET ...` and would persist a cross-border row UNGATED. The gate
153
+ // now recognizes the residency-table write target behind the CTE prefix and
154
+ // FAILS CLOSED (can't parse a CTE body) — directing the operator to the
155
+ // structured builder. (Sibling of RAW PATH 5.)
156
+ var cteInsertCode = codeOf(function () {
157
+ b.db.runSql(
158
+ "WITH src AS (SELECT 'us-east-1' AS r) INSERT INTO residents (_id, name, dataRegion) SELECT 'cte-i', 'x', r FROM src");
159
+ });
160
+ check("writable-CTE INSERT to a residency table is refused (no CTE-prefix bypass)",
161
+ cteInsertCode === "db-query/row-residency-raw-unparseable");
162
+ check("writable-CTE INSERT cross-border row did not persist",
163
+ b.db.from("residents").where({ _id: "cte-i" }).first() === null);
164
+
165
+ var cteUpdateCode = codeOf(function () {
166
+ b.db.prepare(
167
+ "WITH RECURSIVE t AS (SELECT 1) UPDATE residents SET dataRegion = 'us-east-1' WHERE _id = 'raw-eu'").run();
168
+ });
169
+ check("WITH RECURSIVE UPDATE to a residency table is refused (no CTE-prefix bypass)",
170
+ cteUpdateCode === "db-query/row-residency-raw-unparseable");
171
+ check("the writable-CTE update did not move the row cross-border",
172
+ (b.db.from("residents").where({ _id: "raw-eu" }).first() || {}).dataRegion === "eu-west-1");
173
+
174
+ // A read-only CTE (WITH ... SELECT) is NOT a write → not gated (no over-reject).
175
+ var cteReadErr = codeOf(function () {
176
+ b.db.runSql("WITH c AS (SELECT _id FROM residents WHERE dataRegion = 'eu-west-1') SELECT _id FROM c");
177
+ });
178
+ check("read-only WITH...SELECT is not gated (no residency error)", cteReadErr === null);
179
+
180
+ // A writable-CTE write to a NON-residency table is not over-rejected.
181
+ b.db.runSql("CREATE TABLE IF NOT EXISTS notes (_id TEXT PRIMARY KEY, body TEXT)");
182
+ b.db.runSql("WITH s AS (SELECT 'hi' AS b) INSERT INTO notes (_id, body) SELECT 'n1', b FROM s");
183
+ check("writable-CTE write to a NON-residency table still persists (no over-reject)",
184
+ (b.db.from("notes").where({ _id: "n1" }).first() || {}).body === "hi");
111
185
  } finally {
112
186
  b.compliance.clear();
113
187
  b.cryptoField.clearResidencyForTest();
@@ -16,8 +16,17 @@ function testSurface() {
16
16
  check("dora.create is a function", typeof b.dora.create === "function");
17
17
  check("dora.MAJOR_INCIDENT_THRESHOLDS exposed",
18
18
  typeof b.dora.MAJOR_INCIDENT_THRESHOLDS === "object");
19
- check("dora.INITIAL_REPORT_DEADLINE_MS = 24h",
20
- b.dora.INITIAL_REPORT_DEADLINE_MS === b.constants.TIME.hours(24));
19
+ // RTS (EU) 2024/1772 Art. 5: initial notification within 4h of classifying
20
+ // the incident as major; the 24h-from-awareness outer bound is exposed
21
+ // separately. (Was asserted as 24h, contradicting the framework's own
22
+ // incident-report DORA regime which correctly uses 4h.)
23
+ check("dora.INITIAL_REPORT_DEADLINE_MS = 4h (operative from classification)",
24
+ b.dora.INITIAL_REPORT_DEADLINE_MS === b.constants.TIME.hours(4));
25
+ check("dora.INITIAL_REPORT_OUTER_DEADLINE_MS = 24h (outer, from awareness)",
26
+ b.dora.INITIAL_REPORT_OUTER_DEADLINE_MS === b.constants.TIME.hours(24));
27
+ check("dora agrees with incident-report REGIME_DEADLINES.dora",
28
+ b.dora.INITIAL_REPORT_DEADLINE_MS === require("../../lib/incident-report").REGIME_DEADLINES.dora.initial &&
29
+ b.dora.INTERMEDIATE_REPORT_DEADLINE_MS === require("../../lib/incident-report").REGIME_DEADLINES.dora.intermediate);
21
30
  check("frameworkError.DoraError exposed",
22
31
  typeof b.frameworkError.DoraError === "function");
23
32
  }
@@ -32,8 +41,10 @@ function testClassifyMajor() {
32
41
  });
33
42
  check("classify with critical severity → major",
34
43
  rv.classification === "major" && rv.mustReport === true);
35
- check("major incident → 24h initial-report deadline",
36
- rv.mustReportInitialByMs === b.constants.TIME.hours(24));
44
+ check("major incident → 4h initial-report deadline (from classification)",
45
+ rv.mustReportInitialByMs === b.constants.TIME.hours(4));
46
+ check("major incident → 24h outer initial deadline (from awareness)",
47
+ rv.mustReportInitialOuterByMs === b.constants.TIME.hours(24));
37
48
  }
38
49
 
39
50
  function testClassifySignificant() {
@@ -91,8 +102,11 @@ function testReportInitial() {
91
102
  check("report initial: returns RTS-shaped record with reportedAt set",
92
103
  record && record.incidentId === "INC-2026-0001" &&
93
104
  typeof record.reportedAt === "number");
94
- check("report initial: nextStageDueAt = detectedAt + 72h",
95
- record.nextStageDueAt === detectedAt + b.constants.TIME.hours(72));
105
+ // The intermediate deadline anchors on the INITIAL report's SUBMISSION time
106
+ // (record.reportedAt), not detectedAt a report filed late must not get a
107
+ // deadline measured from detection (RTS 2024/1772 reporting timeline).
108
+ check("report initial: nextStageDueAt = reportedAt + 72h (submission-anchored)",
109
+ record.nextStageDueAt === record.reportedAt + b.constants.TIME.hours(72));
96
110
  }
97
111
 
98
112
  function testReportFinal() {
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ /**
3
+ * b.auth.dpop.verify — alg/kty cross-check (alg-confusion family,
4
+ * CVE-2026-22817 class).
5
+ *
6
+ * The DPoP proof embeds its own public key (header.jwk) and declares the
7
+ * signing alg (header.alg). Every other JWS verifier in the framework calls
8
+ * jwtExternal._assertAlgKtyMatch(alg, jwk) before handing the self-asserted
9
+ * key to node:crypto; the DPoP verifier omitted it. A proof declaring
10
+ * alg:"ES256" while embedding an RSA jwk must be refused with a clean
11
+ * alg/kty mismatch — not run through node:crypto with mismatched params.
12
+ */
13
+
14
+ var helpers = require("../helpers");
15
+ var b = helpers.b;
16
+ var check = helpers.check;
17
+ var nodeCrypto = require("crypto");
18
+
19
+ function _b64url(buf) {
20
+ return Buffer.from(buf).toString("base64")
21
+ .replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
22
+ }
23
+
24
+ async function run() {
25
+ var rsa = nodeCrypto.generateKeyPairSync("rsa", { modulusLength: 2048 });
26
+ var rsaJwk = rsa.publicKey.export({ format: "jwk" }); // { kty:"RSA", n, e }
27
+
28
+ // Hand-craft a proof: header.alg="ES256" but header.jwk is the RSA key,
29
+ // signed (RS256) with the matching RSA private key. The embedded jwk and
30
+ // the declared alg disagree on key type.
31
+ var header = { typ: "dpop+jwt", alg: "ES256", jwk: rsaJwk };
32
+ var payload = { htm: "POST", htu: "https://api.example.com/r",
33
+ jti: "jti-" + rsaJwk.n.slice(0, 8), iat: Math.floor(Date.now() / 1000) };
34
+ var signingInput = _b64url(JSON.stringify(header)) + "." + _b64url(JSON.stringify(payload));
35
+ var sig = nodeCrypto.sign("sha256", Buffer.from(signingInput, "ascii"), rsa.privateKey);
36
+ var proof = signingInput + "." + _b64url(sig);
37
+
38
+ var threw = null;
39
+ try {
40
+ await b.auth.dpop.verify(proof, { htm: "POST", htu: "https://api.example.com/r" });
41
+ } catch (e) { threw = e; }
42
+ check("dpop.verify: alg/kty mismatch (ES256 alg, RSA jwk) is refused with alg-kty-mismatch",
43
+ threw && (threw.code === "auth-jwt-external/alg-kty-mismatch" ||
44
+ threw.code === "auth-jwt-external/alg-crv-mismatch"));
45
+ check("dpop.verify: it is NOT mis-reported as a plain invalid-signature",
46
+ threw && threw.code !== "auth-dpop/invalid-signature");
47
+
48
+ // Control: a well-formed ES256 proof (alg matches the EC jwk) still verifies.
49
+ var ec = nodeCrypto.generateKeyPairSync("ec", { namedCurve: "prime256v1" });
50
+ var goodProof = await b.auth.dpop.buildProof({
51
+ htm: "POST", htu: "https://api.example.com/r", privateKey: ec.privateKey,
52
+ });
53
+ var rv = await b.auth.dpop.verify(goodProof, { htm: "POST", htu: "https://api.example.com/r" });
54
+ check("dpop.verify: matching ES256/EC proof still verifies", rv && rv.header.jwk.kty === "EC");
55
+
56
+ console.log("OK — dpop alg/kty cross-check (" + helpers.getChecks() + " checks)");
57
+ }
58
+
59
+ module.exports = { run: run };
60
+
61
+ if (require.main === module) {
62
+ run().then(function () { process.exit(0); })
63
+ .catch(function (err) { process.exitCode = 1; throw err; });
64
+ }
@@ -923,6 +923,37 @@ async function testDbStoreUpgradePath() {
923
923
  }
924
924
  }
925
925
 
926
+ async function testDbStoreFindsLegacyKeyedMacRows() {
927
+ // A ticket written BEFORE the keyed-MAC default flip stored the LEGACY
928
+ // salted-sha3 digest in subject_email_hash (non-NULL, so the NULL-only
929
+ // backfill never migrates it). list-by-subject must still FIND it via the
930
+ // dual-read (keyed-MAC + legacy candidates) — otherwise an Art.17 erasure
931
+ // purge (list → delete) skips the subject's prior PII-bearing tickets.
932
+ var cryptoField = require("../../lib/crypto-field");
933
+ var tmpDir = _tmp();
934
+ await setupTestDb(tmpDir);
935
+ try {
936
+ var h = _dbDsr();
937
+ var t = await h.dsr.submit({
938
+ type: "access", subject: { email: "alice@example.com" }, reason: "pre-flip ticket",
939
+ });
940
+ // Simulate a pre-flip row: overwrite the keyed-MAC hash with the legacy
941
+ // salted-sha3 digest the old default would have stored.
942
+ var d = cryptoField.computeDerived("dsr_tickets", "subject_email", "alice@example.com");
943
+ check("dsr legacy: a legacy digest distinct from the keyed-MAC exists",
944
+ d && d.legacyValue && d.legacyValue !== d.value);
945
+ b.db.prepare("UPDATE dsr_tickets SET subject_email_hash = $h WHERE id = $id")
946
+ .run({ $h: d.legacyValue, $id: t.id });
947
+ // RED before the dual-read fix: the keyed-MAC single-value equality never
948
+ // matches the legacy digest, so this returns 0 rows.
949
+ var found = await h.store.list({ subject: { email: "alice@example.com" } });
950
+ check("dsr legacy: list-by-subject finds the pre-flip legacy-hashed ticket",
951
+ found.some(function (x) { return x.id === t.id; }));
952
+ } finally {
953
+ await teardownTestDb(tmpDir);
954
+ }
955
+ }
956
+
926
957
  // ---- AAD_ROTATION descriptor + reseal ----
927
958
 
928
959
  function testAadRotationDescriptor() {
@@ -1027,6 +1058,7 @@ async function testResealValidationAndStore() {
1027
1058
  await testDbStoreSealsAtRest();
1028
1059
  await testDbStoreErasurePurgesPriorTickets();
1029
1060
  await testDbStoreUpgradePath();
1061
+ await testDbStoreFindsLegacyKeyedMacRows();
1030
1062
  // AAD_ROTATION descriptor + reseal
1031
1063
  testAadRotationDescriptor();
1032
1064
  await testResealValidationAndStore();
@@ -136,16 +136,59 @@ function testNonModificationBypassesShape() {
136
136
  check("read-shape audit bypasses before/after requirement", ok === true);
137
137
  }
138
138
 
139
+ function testOffListModificationVerbsRequireShape() {
140
+ // §11.10(e) must fail closed: a modifying verb NOT on the legacy denylist
141
+ // (anonymize / revoke / overwrite / merge / withdraw_consent / restrict)
142
+ // must still require before/after — previously it bypassed the check.
143
+ var mods = ["subject.anonymize", "consent.revoke", "subject.overwrite",
144
+ "db.merge", "subject.withdraw_consent", "subject.restrict"];
145
+ for (var i = 0; i < mods.length; i += 1) {
146
+ var r = b.fda21cfr11.checkGxpAudit({
147
+ action: mods[i], recordedAt: Date.now(), actorUserId: "u",
148
+ });
149
+ check("checkGxpAudit requires §11.10(e) shape for " + mods[i], r.ok === false);
150
+ }
151
+ // A genuinely complete modification row (before/after/reason) passes.
152
+ var full = b.fda21cfr11.checkGxpAudit({
153
+ action: "subject.anonymize", recordedAt: Date.now(), actorUserId: "u",
154
+ reason: "GDPR Art.17", metadata: { before: { name: "Alice" }, after: { name: null } },
155
+ });
156
+ check("checkGxpAudit accepts a complete anonymize row", full.ok === true);
157
+ }
158
+
159
+ function testSignatureStrippedRefusedWhenVerifierWired() {
160
+ // #B0 — with verifyWith wired, a record whose signature is null/empty must
161
+ // NOT verify on recordHash alone (recordHash is self-consistency, not
162
+ // authentication). Accepting it is alg:none-style signature stripping.
163
+ var nc = require("node:crypto");
164
+ var key = nc.randomBytes(32);
165
+ var sign = function (buf) { return nc.createHmac("sha256", key).update(buf).digest(); };
166
+ var verify = function (buf, sig) { try { return nc.timingSafeEqual(sign(buf), sig); } catch (_e) { return false; } };
167
+ var fda = b.fda21cfr11.posture({ audit: _fakeAudit(), interceptAudit: false, signWith: sign, verifyWith: verify });
168
+ var rec = fda.electronicSignature.create({
169
+ printedName: "Jane Doe, M.D.", signatureMeaning: "approval",
170
+ predicateRule: "21 CFR 312.62", boundRecord: Buffer.from("trial-data"),
171
+ });
172
+ check("FDA properly-signed record verifies",
173
+ fda.electronicSignature.verify(rec, Buffer.from("trial-data")).ok === true);
174
+ var stripped = Object.assign({}, rec, { signature: null });
175
+ var v = fda.electronicSignature.verify(stripped, Buffer.from("trial-data"));
176
+ check("FDA signature-stripped record refused when verifier wired",
177
+ v.ok === false && v.reason === "signature-required");
178
+ }
179
+
139
180
  async function run() {
140
181
  testSurface();
141
182
  testSignatureCreate();
142
183
  testSignatureBadMeaning();
143
184
  testSignatureMissingPredicate();
185
+ testSignatureStrippedRefusedWhenVerifierWired();
144
186
  testAssertGxpAuditOk();
145
187
  testAssertGxpAuditMissingBefore();
146
188
  testAssertGxpAuditMetadataAsString();
147
189
  testCheckGxpAuditMissingActor();
148
190
  testNonModificationBypassesShape();
191
+ testOffListModificationVerbsRequireShape();
149
192
  }
150
193
 
151
194
  module.exports = { run: run };
@@ -185,12 +185,42 @@ async function testExactMembershipNoBypass() {
185
185
  plain.next === true);
186
186
  }
187
187
 
188
+ function testMethodsEmptyThrows() {
189
+ var threw = null;
190
+ try { b.middleware.fetchMetadata({ methods: [] }); } catch (e) { threw = e; }
191
+ check("fetchMetadata({ methods: [] }) refused at config time (no silent pass-through)",
192
+ threw && /non-empty array/.test(threw.message || ""));
193
+ }
194
+
195
+ async function testLayeredStricterGateRuns() {
196
+ // A lenient app-level mount must NOT silently disable a STRICTER sub-route
197
+ // mount sharing the request — each instance gates independently.
198
+ var lenient = b.middleware.fetchMetadata({}); // allowSameSite default true
199
+ var strict = b.middleware.fetchMetadata({ allowSameSite: false });
200
+ var req = _post({ "sec-fetch-site": "same-site", "sec-fetch-mode": "cors", "sec-fetch-dest": "empty" });
201
+ var r1 = await _run(lenient, req, _bodyRes());
202
+ check("lenient app-level gate passes the same-site request", r1.next === true);
203
+ // The SAME req now carries the lenient gate's flag; the stricter instance
204
+ // must still evaluate (previously a shared boolean made it a no-op).
205
+ var r2 = await _run(strict, req, _bodyRes());
206
+ check("stricter sub-route gate still refuses (not disabled by the earlier mount)",
207
+ r2.next === false && r2.status === 403);
208
+ // The SAME stricter instance run twice on a request IS idempotent.
209
+ var req2 = _post({ "sec-fetch-site": "same-site", "sec-fetch-mode": "cors", "sec-fetch-dest": "empty" });
210
+ var s1 = await _run(strict, req2, _bodyRes());
211
+ var s2 = await _run(strict, req2, _bodyRes());
212
+ check("same instance is idempotent (first refuses, second no-ops to next)",
213
+ s1.next === false && s2.next === true);
214
+ }
215
+
188
216
  async function run() {
189
217
  await testDefaultUnchanged();
190
218
  await testDeniedDestWebIdentity();
191
219
  await testStorageAccessGate();
192
220
  testStrictDestThrows();
193
221
  await testExactMembershipNoBypass();
222
+ testMethodsEmptyThrows();
223
+ await testLayeredStricterGateRuns();
194
224
  }
195
225
 
196
226
  module.exports = { run: run };
@@ -16,6 +16,14 @@ async function run() {
16
16
  check("probes _entities", b.graphqlFederation.queryProbesSdl("{ _entities(representations:[...]) { ... } }") === true);
17
17
  check("clean query", b.graphqlFederation.queryProbesSdl("{ user { name } }") === false);
18
18
  check("non-string query", b.graphqlFederation.queryProbesSdl(null) === false);
19
+ // Aliased _service with NO leading whitespace must still probe true — the
20
+ // prefix-char-class form let `query{x:_service{sdl}}` slip the SDL gate.
21
+ check("probes aliased _service (no space)", b.graphqlFederation.queryProbesSdl("query{x:_service{sdl}}") === true);
22
+ check("probes single-char-alias _service", b.graphqlFederation.queryProbesSdl("{s:_service{sdl}}") === true);
23
+ check("probes aliased _entities (no space)", b.graphqlFederation.queryProbesSdl("{a:_entities(r:[]){__typename}}") === true);
24
+ // \b still excludes substring field names.
25
+ check("does NOT probe my_service substring", b.graphqlFederation.queryProbesSdl("query{my_service{x}}") === false);
26
+ check("does NOT probe userservice substring", b.graphqlFederation.queryProbesSdl("query{userservice{x}}") === false);
19
27
 
20
28
  // ---- guardSdl opts validation ----
21
29
  var threw = null;
@@ -37,6 +45,31 @@ async function run() {
37
45
  nonceHeader: "x-my-nonce",
38
46
  });
39
47
  check("guardSdl: accepts custom nonceHeader", typeof withNonce === "function");
48
+
49
+ // ---- guardSdl drives: a batched ARRAY body whose SECOND operation probes
50
+ // _service must be refused (401) without a router token, not forwarded. ----
51
+ function _drive(mw, body) {
52
+ return new Promise(function (resolve) {
53
+ var out = { status: null, nextCalled: false };
54
+ var res = {
55
+ statusCode: 200,
56
+ setHeader: function () {}, getHeader: function () { return undefined; },
57
+ writeHead: function (s) { out.status = s; },
58
+ end: function () { if (out.status === null) out.status = res.statusCode; resolve(out); },
59
+ };
60
+ mw({ method: "POST", headers: {}, body: body, on: function () {} }, res,
61
+ function () { out.nextCalled = true; resolve(out); });
62
+ });
63
+ }
64
+ var guard = b.graphqlFederation.guardSdl({ routerToken: "a".repeat(64) });
65
+ var batched = await _drive(guard, [{ query: "{ ok }" }, { query: "{ _service { sdl } }" }]);
66
+ check("guardSdl: batched-array _service probe is refused (401), not forwarded",
67
+ batched.status === 401 && batched.nextCalled === false);
68
+ var aliased = await _drive(guard, { query: "query{x:_service{sdl}}" });
69
+ check("guardSdl: aliased _service probe is refused (401)",
70
+ aliased.status === 401 && aliased.nextCalled === false);
71
+ var clean = await _drive(guard, { query: "{ user { name } }" });
72
+ check("guardSdl: a clean query is forwarded (next)", clean.nextCalled === true);
40
73
  }
41
74
 
42
75
  module.exports = { run: run };
@@ -124,6 +124,14 @@ function testGuardHtmlUrlSchemes() {
124
124
  check("entity-encoded javascript: detected after entity decode",
125
125
  rvEnc.issues.some(function (issue) { return issue.kind === "dangerous-url-scheme"; }));
126
126
 
127
+ // #B7 — NO-semicolon decimal entity `&#106avascript:` (106='j', terminates at
128
+ // the non-digit 'a') is browser-decoded to javascript:. A semicolon-required
129
+ // decoder let this bypass the scheme allowlist as clean.
130
+ var rvNoSemi = b.guardHtml.validate(
131
+ '<a href="&#106avascript:alert(1)">x</a>', { profile: "balanced" });
132
+ check("no-semicolon entity javascript: detected",
133
+ rvNoSemi.issues.some(function (issue) { return issue.kind === "dangerous-url-scheme"; }));
134
+
127
135
  // Image-context data URL allowed under balanced when allowImageData true.
128
136
  var rvImg = b.guardHtml.validate(
129
137
  '<img src="data:image/png;base64,iVBORw0KG" alt="x">',
@@ -281,6 +289,31 @@ function testGuardHtmlByteCaps() {
281
289
 
282
290
  // ---- Sanitize round-trip ----
283
291
 
292
+ function testGuardHtmlCommentEndDifferential() {
293
+ // mXSS comment-parser differential: the WHATWG HTML parser closes a comment
294
+ // at "--!>" (comment-end-bang) and ABRUPTLY at "<!-->" / "<!--->". A
295
+ // tokenizer honouring only "-->" treats the trailing <img onerror> as part
296
+ // of an inert comment; the browser parses it as a LIVE element. With the
297
+ // comment boundary fixed, the <img> is a real token the sanitizer disarms
298
+ // and validate flags — even in the permissive (allowComments) profile.
299
+ var payloads = [
300
+ "<!-- a --!><img src=x onerror=alert(1)>",
301
+ "<!--><img src=x onerror=alert(1)>-->",
302
+ "<!---><img src=x onerror=alert(1)>",
303
+ ];
304
+ for (var i = 0; i < payloads.length; i += 1) {
305
+ var p = payloads[i];
306
+ var s = b.guardHtml.sanitize(p, { profile: "permissive" });
307
+ check("comment-differential: sanitize strips smuggled onerror (" + i + ")",
308
+ s.indexOf("onerror=alert") === -1);
309
+ check("comment-differential: sanitize is not a verbatim pass-through (" + i + ")",
310
+ s !== p);
311
+ var v = b.guardHtml.validate(p, { profile: "permissive" });
312
+ check("comment-differential: validate flags the smuggled element (" + i + ")",
313
+ v.ok === false);
314
+ }
315
+ }
316
+
284
317
  function testGuardHtmlSanitizeBasic() {
285
318
  var clean = b.guardHtml.sanitize("<p>hello <b>world</b></p>",
286
319
  { profile: "strict" });
@@ -404,6 +437,7 @@ async function run() {
404
437
  testGuardHtmlControlChar();
405
438
  testGuardHtmlSizeCaps();
406
439
  testGuardHtmlByteCaps();
440
+ testGuardHtmlCommentEndDifferential();
407
441
  testGuardHtmlSanitizeBasic();
408
442
  testGuardHtmlEscape();
409
443
  testGuardHtmlBadProfile();
@@ -203,6 +203,17 @@ function testGuardJsonStringLengthCap() {
203
203
  rv.issues.some(function (i) { return i.kind === "string-too-long"; }));
204
204
  }
205
205
 
206
+ function testGuardJsonStringLengthByteCap() {
207
+ // maxStringLength is a per-string BYTE cap (strict 8 KiB). A multibyte string
208
+ // whose UTF-16 code-unit count is UNDER the cap but whose UTF-8 byte length
209
+ // EXCEEDS it must still be refused — value.length (code units) under-enforces.
210
+ // 4100 'é' = 4100 code units (< 8192) but 8200 UTF-8 bytes (> 8192).
211
+ var multibyte = "é".repeat(4100);
212
+ var rv = b.guardJson.validate('{"k":"' + multibyte + '"}', { profile: "strict" });
213
+ check("per-string maxStringLength measured in UTF-8 bytes (multibyte not under-enforced)",
214
+ rv.issues.some(function (i) { return i.kind === "string-too-long"; }));
215
+ }
216
+
206
217
  function testGuardJsonByteCap() {
207
218
  // maxBytes is a BYTE cap — multibyte input must be measured by UTF-8
208
219
  // byte length, not UTF-16 code-unit count (.length). "é" is one code
@@ -356,6 +367,7 @@ async function run() {
356
367
  testGuardJsonKeyCountCap();
357
368
  testGuardJsonArrayLengthCap();
358
369
  testGuardJsonStringLengthCap();
370
+ testGuardJsonStringLengthByteCap();
359
371
  testGuardJsonByteCap();
360
372
  testGuardJsonNumericPrecision();
361
373
  testGuardJsonTopLevelKeyAllowlist();
@@ -396,8 +396,80 @@ function testSurface() {
396
396
  b.httpClient.ALLOWED_DOWNLOAD_HASH_ALGS.indexOf("sha3-512") !== -1);
397
397
  }
398
398
 
399
+ async function _drainStream(s) {
400
+ return await new Promise(function (resolve, reject) {
401
+ var chunks = [];
402
+ s.on("data", function (c) { chunks.push(c); });
403
+ s.on("end", function () { resolve(Buffer.concat(chunks)); });
404
+ s.on("error", reject);
405
+ });
406
+ }
407
+
408
+ async function testBandwidthThrottle() {
409
+ // #342: maxBytesPerSec paces a download with real backpressure. The token
410
+ // bucket starts full (a 1s burst = RATE bytes), so a transfer LARGER than the
411
+ // burst is paced: SIZE 20000 @ RATE 10000 → ~10000 free + ~10000 at 10KB/s ≈ 1s.
412
+ var SIZE = 20000, RATE = 10000;
413
+ var payload = Buffer.alloc(SIZE, 0x61);
414
+ await _withServer(function (req, res) {
415
+ res.writeHead(200, { "Content-Type": "application/octet-stream", "Content-Length": String(SIZE) });
416
+ res.end(payload);
417
+ }, async function (baseUrl) {
418
+ var started = Date.now();
419
+ var resp = await b.httpClient.request({
420
+ url: baseUrl + "/big", method: "GET", responseMode: "stream",
421
+ maxBytesPerSec: RATE, allowedProtocols: b.safeUrl.ALLOW_HTTP_ALL, allowInternal: true,
422
+ });
423
+ var got = await _drainStream(resp.body);
424
+ var elapsed = Date.now() - started;
425
+ check("throttle: received all bytes intact", got.length === SIZE && got.equals(payload));
426
+ check("throttle: paced (elapsed >= 700ms for 20KB @ 10KB/s)", elapsed >= 700);
427
+ });
428
+ }
429
+
430
+ async function testDownloadTransformInterpose() {
431
+ // #342: downloadTransform (factory form) interposes on the response stream.
432
+ var nodeStream = require("stream");
433
+ var payload = Buffer.from("transform me", "utf8");
434
+ await _withServer(function (req, res) {
435
+ res.writeHead(200, { "Content-Length": String(payload.length) });
436
+ res.end(payload);
437
+ }, async function (baseUrl) {
438
+ var resp = await b.httpClient.request({
439
+ url: baseUrl + "/x", method: "GET", responseMode: "stream",
440
+ downloadTransform: function () {
441
+ return new nodeStream.Transform({
442
+ transform: function (chunk, _e, cb) { cb(null, Buffer.from(chunk.toString("utf8").toUpperCase())); },
443
+ });
444
+ },
445
+ allowedProtocols: b.safeUrl.ALLOW_HTTP_ALL, allowInternal: true,
446
+ });
447
+ var got = await _drainStream(resp.body);
448
+ check("downloadTransform: response is transformed (uppercased)", got.toString("utf8") === "TRANSFORM ME");
449
+ });
450
+ }
451
+
452
+ async function testThrottleTransformValidation() {
453
+ function _rejects(label, opts) {
454
+ return b.httpClient.request(opts).then(
455
+ function () { check(label + " (did not reject)", false); },
456
+ function (e) { check(label, e && /maxBytesPerSec|Transform/i.test(e.message || "")); });
457
+ }
458
+ await _rejects("maxBytesPerSec: non-number refused",
459
+ { url: "https://x.example/", maxBytesPerSec: "fast" });
460
+ await _rejects("maxBytesPerSec: <= 0 refused",
461
+ { url: "https://x.example/", maxBytesPerSec: 0 });
462
+ await _rejects("downloadTransform: non-Transform refused",
463
+ { url: "https://x.example/", downloadTransform: 123 });
464
+ await _rejects("uploadTransform: non-Transform refused",
465
+ { url: "https://x.example/", uploadTransform: { not: "a stream" } });
466
+ }
467
+
399
468
  async function run() {
400
469
  testSurface();
470
+ await testBandwidthThrottle();
471
+ await testDownloadTransformInterpose();
472
+ await testThrottleTransformValidation();
401
473
  await testDownloadHappyPath();
402
474
  await testDownloadExpectedHashMatch();
403
475
  await testDownloadHashMismatch();
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ /**
3
+ * b.httpClient — download/upload transform correctness outside stream mode.
4
+ *
5
+ * - A downloadTransform must apply in the default "buffer" responseMode, not
6
+ * only when responseMode is "stream": the buffered body is the transformed
7
+ * bytes.
8
+ * - A size-changing uploadTransform on a Buffer body must NOT leave a stale
9
+ * Content-Length: the request is framed (chunked) on the transformed bytes
10
+ * so the server receives the whole body, not a truncated prefix.
11
+ *
12
+ * No live network — a local http.Server on a random port.
13
+ */
14
+
15
+ var http = require("http");
16
+ var nodeStream = require("stream");
17
+
18
+ var helpers = require("../helpers");
19
+ var b = helpers.b;
20
+ var check = helpers.check;
21
+
22
+ async function _withServer(handler, fn) {
23
+ var server = http.createServer(handler);
24
+ var port = await b.testing.listenOnRandomPort(server, "127.0.0.1");
25
+ try { return await fn("http://127.0.0.1:" + port); }
26
+ finally { await new Promise(function (resolve) { server.close(function () { resolve(); }); }); }
27
+ }
28
+
29
+ function _upperTransform() {
30
+ return new nodeStream.Transform({
31
+ transform: function (chunk, _enc, cb) { cb(null, Buffer.from(chunk.toString("utf8").toUpperCase())); },
32
+ });
33
+ }
34
+
35
+ // Appends a fixed suffix on flush — the transformed body is strictly larger
36
+ // than the source, so a stale Content-Length would truncate it.
37
+ function _appendTransform() {
38
+ return new nodeStream.Transform({
39
+ transform: function (chunk, _enc, cb) { cb(null, chunk); },
40
+ flush: function (cb) { this.push(Buffer.from("-XFORM")); cb(); },
41
+ });
42
+ }
43
+
44
+ async function testDownloadTransformAppliesInBufferMode() {
45
+ await _withServer(function (req, res) {
46
+ res.writeHead(200, { "Content-Type": "text/plain" });
47
+ res.end("hello world");
48
+ }, async function (base) {
49
+ var r = await b.httpClient.request({
50
+ url: base + "/", allowedProtocols: b.safeUrl.ALLOW_HTTP_ALL, allowInternal: true,
51
+ downloadTransform: _upperTransform, // default responseMode = "buffer"
52
+ });
53
+ check("buffer-mode downloadTransform applied to the buffered body",
54
+ r.body.toString("utf8") === "HELLO WORLD");
55
+ });
56
+ }
57
+
58
+ async function testUploadTransformSizeChangeNotTruncated() {
59
+ var received = null;
60
+ await _withServer(function (req, res) {
61
+ var chunks = [];
62
+ req.on("data", function (c) { chunks.push(c); });
63
+ req.on("end", function () {
64
+ received = Buffer.concat(chunks).toString("utf8");
65
+ res.writeHead(200); res.end("ok");
66
+ });
67
+ }, async function (base) {
68
+ await b.httpClient.request({
69
+ url: base + "/", method: "POST", body: Buffer.from("payload"),
70
+ allowedProtocols: b.safeUrl.ALLOW_HTTP_ALL, allowInternal: true,
71
+ uploadTransform: _appendTransform, // grows the body past the Buffer length
72
+ });
73
+ check("size-changing uploadTransform delivers the whole transformed body",
74
+ received === "payload-XFORM");
75
+ });
76
+ }
77
+
78
+ async function run() {
79
+ await testDownloadTransformAppliesInBufferMode();
80
+ await testUploadTransformSizeChangeNotTruncated();
81
+ }
82
+
83
+ module.exports = { run: run };
84
+
85
+ if (require.main === module) {
86
+ run().then(function () { console.log("OK http-client-throttle-transform — " + helpers.getChecks() + " checks"); })
87
+ .catch(function (e) { console.error("FAIL:", e && e.stack || e); process.exit(1); });
88
+ }