@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
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ /**
3
+ * Response-output, header, and compliance hardening — each test drives the
4
+ * shipped consumer path with the input that triggers the failure.
5
+ *
6
+ * - ai.output.sanitize: a markdown image/link whose alt text equals its URL
7
+ * no longer leaves the real exfiltration target intact (EchoLeak / CWE-918).
8
+ * - dora.classify: evaluates the percentage-of-client-base criterion (not just
9
+ * absolute counts); dora.report anchors the next-stage deadline on the
10
+ * report's submission time, not detection.
11
+ * - cookies.serialize: value cap is byte-length (multibyte overflow refused);
12
+ * a ';' in Path is scrubbed (no attribute injection).
13
+ * - csp.mergeDirectives: merging a real source into a 'none' directive drops
14
+ * 'none' (no malformed "'none' host").
15
+ * - cdnCacheControl.parse: delta-seconds accepts only decimal digits (no
16
+ * hex / exponential / whitespace forms).
17
+ */
18
+
19
+ var helpers = require("../helpers");
20
+ var b = helpers.b;
21
+ var check = helpers.check;
22
+
23
+ function _throws(fn) { try { fn(); return null; } catch (e) { return (e && e.code) || e.message || "threw"; } }
24
+
25
+ function testAiOutputEchoLeak() {
26
+ var meta = "https://169.254.169.254/latest/meta-data";
27
+ var out = b.ai.output.sanitize("![" + meta + "](" + meta + ")", { audit: false });
28
+ var text = out.text || out;
29
+ check("ai.output.sanitize neutralizes the real URL when alt === url (EchoLeak)",
30
+ text.indexOf("(" + meta + ")") === -1 && /about:blank#blocked/.test(text));
31
+ // A normal link is still neutralized.
32
+ var out2 = b.ai.output.sanitize("[x](" + meta + ")", { audit: false });
33
+ check("ai.output.sanitize still neutralizes a normal metadata link",
34
+ /about:blank#blocked/.test(out2.text || out2));
35
+ }
36
+
37
+ function testDoraClientBasePercentile() {
38
+ var d = b.dora.create({ audit: false });
39
+ // 8000 of 50000 = 16% > 10% major threshold, but 8000 < 100000 absolute.
40
+ var major = d.classify({ affectedClients: 8000, clientBase: 50000 });
41
+ check("dora.classify: 16% of client base → major (percentile criterion)",
42
+ major.classification === "major");
43
+ // Absolute-only behavior unchanged when clientBase omitted.
44
+ var minor = d.classify({ affectedClients: 8000 });
45
+ check("dora.classify: 8000 with no clientBase → not major (absolute-only)",
46
+ minor.classification !== "major");
47
+ }
48
+
49
+ function testDoraDeadlineAnchor() {
50
+ var d = b.dora.create({ audit: false });
51
+ var detected = Date.now() - 5 * 60 * 60 * 1000; // detected 5h ago
52
+ var rep = d.report({
53
+ incidentId: "inc-1", classification: "major", stage: "initial",
54
+ detectedAt: detected, description: "x",
55
+ });
56
+ // nextStageDueAt must anchor on submission (~now), not detectedAt — so it is
57
+ // well past detectedAt + 72h would have been if anchored on detection.
58
+ check("dora.report: nextStageDueAt anchored on submission, not detectedAt",
59
+ rep.nextStageDueAt > detected + 72 * 60 * 60 * 1000 - 60000);
60
+ }
61
+
62
+ function testCookieByteCapAndPath() {
63
+ var bigMultibyte = "€".repeat(4096); // 4096 chars = 12288 UTF-8 bytes
64
+ check("cookies.serialize refuses an over-byte-cap multibyte value",
65
+ _throws(function () { b.cookies.serialize("c", bigMultibyte, { secure: true }); }) === "cookies/invalid-value");
66
+ // A ';' in Path must not survive into the Set-Cookie attribute list.
67
+ var sc = b.cookies.serialize("c", "v", { path: "/a;HttpOnly=evil", secure: true });
68
+ check("cookies.serialize scrubs ';' from Path (no attribute injection)",
69
+ sc.indexOf("/aHttpOnly=evil") !== -1 && sc.indexOf("/a;HttpOnly=evil") === -1);
70
+ }
71
+
72
+ function testCspMergeNone() {
73
+ var merged = b.csp.mergeDirectives("script-src 'none'", { "script-src": ["https://cdn.example"] });
74
+ check("csp.mergeDirectives drops 'none' when a real source is merged in",
75
+ merged.indexOf("'none'") === -1 && merged.indexOf("https://cdn.example") !== -1);
76
+ }
77
+
78
+ function testCdnDeltaSeconds() {
79
+ var ok = b.cdnCacheControl.parse("max-age=3600");
80
+ check("cdnCacheControl.parse accepts decimal delta-seconds", ok.maxAge === 3600);
81
+ // hex / exponential / whitespace are not RFC 9111 delta-seconds.
82
+ var hex = b.cdnCacheControl.parse("max-age=0x10");
83
+ check("cdnCacheControl.parse rejects hex delta-seconds", hex.maxAge === undefined);
84
+ var exp = b.cdnCacheControl.parse("max-age=1e3");
85
+ check("cdnCacheControl.parse rejects exponential delta-seconds", exp.maxAge === undefined);
86
+ }
87
+
88
+ async function run() {
89
+ testAiOutputEchoLeak();
90
+ testDoraClientBasePercentile();
91
+ testDoraDeadlineAnchor();
92
+ testCookieByteCapAndPath();
93
+ testCspMergeNone();
94
+ testCdnDeltaSeconds();
95
+ }
96
+
97
+ module.exports = { run: run };
98
+
99
+ if (require.main === module) {
100
+ run().then(function () { console.log("OK"); })
101
+ .catch(function (e) { console.error(e.stack || e); process.exit(1); });
102
+ }
@@ -0,0 +1,191 @@
1
+ "use strict";
2
+ /**
3
+ * Pre-auth parse/verify hardening — each test drives the shipped consumer
4
+ * path with the adversarial input that triggers the failure.
5
+ *
6
+ * - cbor: O(1) duplicate-key detection (was O(n²) — pre-auth DoS on every
7
+ * COSE/CWT/EAT/mdoc verify); duplicates still refused.
8
+ * - crypto.decrypt: a truncated envelope throws the documented envelope Error,
9
+ * not a raw RangeError.
10
+ * - mdoc.verifyIssuerSigned: issuer trust is enforced by default (the x5chain
11
+ * signer cert is attacker-controlled) — refuses without trustAnchorsPem
12
+ * unless allowUntrustedIssuer is set.
13
+ * - cose: RFC 9053 alg↔curve binding — refuses ES512 over a P-256 key.
14
+ * - jsonPath: a deeply-nested filter throws JsonPathError, not a raw
15
+ * stack-overflow RangeError.
16
+ * - bodyParser.raw(): the default match-all wildcard contentTypes accepts a
17
+ * real Content-Type (was 415 on every request).
18
+ */
19
+
20
+ var nodeCrypto = require("node:crypto");
21
+ var EventEmitter = require("events").EventEmitter;
22
+ var helpers = require("../helpers");
23
+ var b = helpers.b;
24
+ var check = helpers.check;
25
+ var _bodyRes = helpers._bodyRes;
26
+
27
+ function _codeOf(fn) {
28
+ try { fn(); return null; } catch (e) { return (e && e.code) || (e && e.message) || "threw"; }
29
+ }
30
+
31
+ async function _codeOfAsync(p) {
32
+ try { await p; return null; } catch (e) { return (e && e.code) || (e && e.message) || "threw"; }
33
+ }
34
+
35
+ function testCborDuplicateKeyAndLinearSpeed() {
36
+ // Duplicates still refused.
37
+ var dup = Buffer.from([0xa2, 0x01, 0x01, 0x01, 0x02]); // {1:1, 1:2}
38
+ check("cbor.decode refuses a duplicate map key",
39
+ _codeOf(function () { b.cbor.decode(dup); }) === "cbor/duplicate-key");
40
+
41
+ // A large distinct-key map decodes in linear time. The old O(n²) per-key
42
+ // Buffer.compare scan over a 20k-key map is ~4e8 comparisons (many seconds);
43
+ // the Set-based guard is milliseconds.
44
+ var m = new Map();
45
+ for (var i = 0; i < 20000; i++) m.set(i, i);
46
+ var encoded = b.cbor.encode(m);
47
+ var t0 = Date.now();
48
+ var decoded = b.cbor.decode(encoded, { maxBytes: 4 * 1024 * 1024 });
49
+ var ms = Date.now() - t0;
50
+ check("cbor.decode of a 20k distinct-key map is correct", decoded instanceof Map && decoded.size === 20000);
51
+ check("cbor.decode of a 20k-key map is linear-fast (<3s), not O(n²)", ms < 3000);
52
+ }
53
+
54
+ function testCryptoDecryptTruncatedEnvelope() {
55
+ // A truncated envelope must surface the documented "Invalid envelope" Error,
56
+ // never a raw Node RangeError from an out-of-bounds readUInt16BE.
57
+ [Buffer.from([0xe2, 1]), Buffer.from([0xe2, 1, 1, 1])].forEach(function (buf, idx) {
58
+ var msg = null;
59
+ try { b.crypto.decrypt(buf, "no-key"); } catch (e) { msg = e.message || ""; }
60
+ check("crypto.decrypt truncated envelope #" + idx + " → documented envelope Error",
61
+ msg !== null && msg.indexOf("Invalid envelope") === 0);
62
+ });
63
+ }
64
+
65
+ async function testMdocRequiresTrustAnchorByDefault() {
66
+ // Issuer authentication is fail-closed by default: without trustAnchorsPem
67
+ // (and without the explicit opt-out) the call is refused BEFORE trusting the
68
+ // attacker-supplied x5chain signer.
69
+ var code = await _codeOfAsync(
70
+ b.mdoc.verifyIssuerSigned(Buffer.from([0xa0]), { algorithms: ["ES256"] }));
71
+ check("mdoc.verifyIssuerSigned refuses without a trust anchor",
72
+ code === "mdoc/trust-anchors-required");
73
+
74
+ // The explicit opt-out gets past the trust gate (it then fails later on the
75
+ // garbage payload — a DIFFERENT, non-trust error), proving the gate is the
76
+ // only thing the default adds.
77
+ var code2 = await _codeOfAsync(
78
+ b.mdoc.verifyIssuerSigned(Buffer.from([0xa0]), { algorithms: ["ES256"], allowUntrustedIssuer: true }));
79
+ check("mdoc allowUntrustedIssuer opt-out bypasses the trust gate (fails later, not on trust)",
80
+ code2 !== "mdoc/trust-anchors-required" && code2 !== null);
81
+ }
82
+
83
+ async function testCoseAlgCurveBinding() {
84
+ var p256 = nodeCrypto.generateKeyPairSync("ec", { namedCurve: "prime256v1" });
85
+ // ES256 over a P-256 key is the correct binding — succeeds.
86
+ var signedOk = null;
87
+ try { await b.cose.sign(Buffer.from("hi"), { alg: "ES256", privateKey: p256.privateKey }); signedOk = true; }
88
+ catch (_e) { signedOk = false; }
89
+ check("cose.sign ES256 over a P-256 key succeeds (correct binding)", signedOk === true);
90
+
91
+ // ES512 over a P-256 key violates RFC 9053 §2 — refused.
92
+ var mismatch = null;
93
+ try { await b.cose.sign(Buffer.from("hi"), { alg: "ES512", privateKey: p256.privateKey }); }
94
+ catch (e) { mismatch = e && e.code; }
95
+ check("cose.sign ES512 over a P-256 key is refused (alg/curve binding)",
96
+ mismatch === "cose/alg-curve-mismatch");
97
+ }
98
+
99
+ function testJsonPathFilterDepthCap() {
100
+ // A pathologically nested filter must throw a typed JsonPathError, never a
101
+ // raw V8 RangeError ("Maximum call stack size exceeded").
102
+ var deep = "$[?(" + "(".repeat(500) + "@.a==1" + ")".repeat(500) + ")]";
103
+ var err = null;
104
+ try { b.jsonPath.query({ a: 1 }, deep); } catch (e) { err = e; }
105
+ check("jsonPath deeply-nested filter throws JsonPathError, not a raw RangeError",
106
+ err !== null && err.name !== "RangeError" &&
107
+ ((err.code || "").indexOf("json-path/") === 0));
108
+ }
109
+
110
+ function testBodyParserRawWildcardMatchesRealType() {
111
+ // bodyParser.raw() defaults contentTypes to ["*/*"]; that wildcard must match
112
+ // a real Content-Type (it never did → 415 on every request).
113
+ var bp = b.middleware.bodyParser.raw();
114
+ var req = new EventEmitter();
115
+ req.method = "POST"; req.url = "/hooks";
116
+ req.headers = { "content-type": "application/json", "content-length": "2" };
117
+ req.socket = { remoteAddress: "127.0.0.1" };
118
+ var res = _bodyRes();
119
+ return new Promise(function (resolve) {
120
+ var settled = false;
121
+ bp(req, res, function () {
122
+ if (settled) return; settled = true;
123
+ check("bodyParser.raw() */* accepts application/json → next() runs", true);
124
+ check("bodyParser.raw() exposes the raw body as a Buffer", Buffer.isBuffer(req.body));
125
+ resolve();
126
+ });
127
+ res.on("finish", function () {
128
+ if (settled) return; settled = true;
129
+ check("bodyParser.raw() */* did NOT 415 a real Content-Type (status=" + res._endedStatus + ")",
130
+ res._endedStatus !== 415);
131
+ resolve();
132
+ });
133
+ setImmediate(function () { req.emit("data", Buffer.from("{}")); req.emit("end"); });
134
+ setTimeout(function () { if (!settled) { settled = true; check("bodyParser.raw() settled", false); resolve(); } }, 1500);
135
+ });
136
+ }
137
+
138
+ async function testPromiseToStreamWrapsPromise() {
139
+ // The object-store remote backends' getStream() wraps a Promise<Buffer> via
140
+ // sharedRequest.promiseToStream. A bare Readable.from(promise) throws
141
+ // ERR_INVALID_ARG_TYPE — the helper must return a Readable that yields the
142
+ // resolved bytes and surfaces a rejection as a stream 'error'.
143
+ var sharedRequest = require("../../lib/object-store/http-request");
144
+ var s = sharedRequest.promiseToStream(Promise.resolve(Buffer.from("hello-stream")));
145
+ var chunks = [];
146
+ await new Promise(function (resolve, reject) {
147
+ s.on("data", function (c) { chunks.push(c); });
148
+ s.on("end", resolve); s.on("error", reject);
149
+ });
150
+ check("promiseToStream yields the resolved buffer", Buffer.concat(chunks).toString() === "hello-stream");
151
+
152
+ var s2 = sharedRequest.promiseToStream(Promise.reject(new Error("upstream boom")));
153
+ var errored = false;
154
+ await new Promise(function (resolve) {
155
+ s2.on("error", function () { errored = true; resolve(); });
156
+ s2.on("end", resolve);
157
+ s2.resume();
158
+ });
159
+ check("promiseToStream surfaces a rejected promise as a stream 'error'", errored === true);
160
+ }
161
+
162
+ async function testSchedulerFarFutureNoImmediateFire() {
163
+ // A task whose first fire is ~40 days out (> the 2147483647 ms setTimeout
164
+ // ceiling) must NOT fire now. Pre-fix, Node coerced the over-max delay to 1ms
165
+ // and the task fired immediately (and re-armed into a tight loop).
166
+ var sched = b.scheduler.create({ audit: false });
167
+ var fired = 0;
168
+ sched.schedule({ name: "far", every: 40 * 24 * 60 * 60 * 1000, run: function () { fired += 1; } });
169
+ await sched.start();
170
+ await helpers.passiveObserve(600, "scheduler far-future: no immediate fire");
171
+ check("scheduler far-future task does not fire immediately (setTimeout overflow clamped)", fired === 0);
172
+ await sched.stop();
173
+ }
174
+
175
+ async function run() {
176
+ testCborDuplicateKeyAndLinearSpeed();
177
+ await testPromiseToStreamWrapsPromise();
178
+ await testSchedulerFarFutureNoImmediateFire();
179
+ testCryptoDecryptTruncatedEnvelope();
180
+ await testMdocRequiresTrustAnchorByDefault();
181
+ await testCoseAlgCurveBinding();
182
+ testJsonPathFilterDepthCap();
183
+ await testBodyParserRawWildcardMatchesRealType();
184
+ }
185
+
186
+ module.exports = { run: run };
187
+
188
+ if (require.main === module) {
189
+ run().then(function () { console.log("OK"); })
190
+ .catch(function (e) { console.error(e.stack || e); process.exit(1); });
191
+ }
@@ -0,0 +1,120 @@
1
+ "use strict";
2
+ /**
3
+ * Prototype-shadow / proto-pollution allowlist-bypass class (CWE-1321).
4
+ *
5
+ * An object-literal allowlist (`var MAP = { a: 1 }`) inherits Object.prototype,
6
+ * so MAP["constructor"], MAP["__proto__"], MAP["toString"], MAP["valueOf"],
7
+ * MAP["hasOwnProperty"] are all TRUTHY (and !== undefined) even though never
8
+ * added. A membership gate `if (!MAP[key])` / `if (MAP[key] === undefined)` is
9
+ * therefore BYPASSED by a prototype-named key when `key` is caller- or
10
+ * attacker-controlled, and `var v = MAP[key]` hands back a Function. The fix is
11
+ * `Object.prototype.hasOwnProperty.call(MAP, key)` (own-key only).
12
+ *
13
+ * This drives the SHIPPED public consumer paths with prototype-named inputs and
14
+ * asserts each is refused / resolves to its safe default — never a bypass and
15
+ * never a Function reaching downstream. RED before the v0.15.14 sweep (the
16
+ * bracket lookup is truthy for a proto key → no throw, or a Function returned).
17
+ */
18
+
19
+ var helpers = require("../helpers");
20
+ var b = helpers.b;
21
+ var check = helpers.check;
22
+
23
+ var PROTO_KEYS = ["constructor", "__proto__", "toString", "valueOf", "hasOwnProperty"];
24
+
25
+ function _throws(fn) {
26
+ try { fn(); return null; } catch (e) { return e; }
27
+ }
28
+
29
+ function testContentDigestCreateRefusesProtoAlg() {
30
+ // ACTIVE = { "sha-256": ..., "sha-512": ... }. A proto-named algorithm must
31
+ // be refused as unsupported, NOT silently accepted (which previously reached
32
+ // nodeCrypto.createHash(<Function>) and threw an opaque type error).
33
+ PROTO_KEYS.forEach(function (k) {
34
+ var e = _throws(function () { b.contentDigest.create("payload", { algorithms: [k] }); });
35
+ check("contentDigest.create refuses proto-named algorithm '" + k + "'",
36
+ e !== null && /unsupported-algorithm|insecure-algorithm/.test(String(e.code || e.message)));
37
+ });
38
+ // A real algorithm still works (no over-rejection).
39
+ check("contentDigest.create still accepts sha-256",
40
+ typeof b.contentDigest.create("payload", { algorithms: ["sha-256"] }) === "string");
41
+ }
42
+
43
+ function testSqlFnAndPragmaRefuseProtoNames() {
44
+ PROTO_KEYS.forEach(function (k) {
45
+ var e1 = _throws(function () { b.sql.fn(k); });
46
+ check("b.sql.fn refuses proto-named function '" + k + "' (no allowlist bypass)",
47
+ e1 !== null);
48
+ var e2 = _throws(function () { b.sql.pragma(k); });
49
+ check("b.sql.pragma refuses proto-named verb '" + k + "'", e2 !== null);
50
+ });
51
+ // A real allowlisted function/pragma still resolves (no over-rejection).
52
+ check("b.sql.fn still accepts an allowlisted function",
53
+ _throws(function () { b.sql.fn("now"); }) === null);
54
+ check("b.sql.pragma still accepts an allowlisted verb",
55
+ _throws(function () { b.sql.pragma("journal_mode"); }) === null);
56
+ }
57
+
58
+ function testLogCreateRefusesProtoLevel() {
59
+ PROTO_KEYS.forEach(function (k) {
60
+ var e = _throws(function () { b.log.create({ level: k }); });
61
+ check("b.log.create refuses proto-named level '" + k + "'",
62
+ e !== null && /bad-level/.test(String(e.code || e.message)));
63
+ });
64
+ check("b.log.create still accepts level 'info'",
65
+ _throws(function () { var l = b.log.create({ level: "info" }); return l; }) === null);
66
+ }
67
+
68
+ function testCompliancePostureAccessorReturnsNullForProto() {
69
+ // The mail-scanner compliancePosture accessor maps a posture name through its
70
+ // own table; a proto-named posture must resolve to null, not an inherited
71
+ // Function.
72
+ PROTO_KEYS.forEach(function (k) {
73
+ check("mail.scan.compliancePosture('" + k + "') is null (not a Function)",
74
+ b.mail.scan.compliancePosture(k) === null);
75
+ });
76
+ check("mail.scan.compliancePosture('hipaa') still maps to a profile",
77
+ typeof b.mail.scan.compliancePosture("hipaa") === "string");
78
+ }
79
+
80
+ function testMakePostureAccessorPrimitive() {
81
+ var accessor = b.gateContract.makePostureAccessor({ hipaa: "strict", gdpr: "balanced" });
82
+ check("makePostureAccessor maps a known posture", accessor("hipaa") === "strict");
83
+ PROTO_KEYS.forEach(function (k) {
84
+ check("makePostureAccessor('" + k + "') → null (proto key)", accessor(k) === null);
85
+ });
86
+ var withFallback = b.gateContract.makePostureAccessor({ hipaa: "strict" }, { fallback: "default" });
87
+ check("makePostureAccessor honors opts.fallback for unknown", withFallback("nope") === "default");
88
+ check("makePostureAccessor honors opts.fallback for proto key", withFallback("constructor") === "default");
89
+ }
90
+
91
+ function testMailBounceDsnRefusesProtoAction() {
92
+ // DSN_ACTIONS allowlist; a proto-named action must be refused as malformed.
93
+ PROTO_KEYS.forEach(function (k) {
94
+ var e = _throws(function () {
95
+ b.mailBounce.dsn.build({
96
+ finalRecipient: "user@example.com",
97
+ action: k,
98
+ status: "5.1.1",
99
+ reportingMta: "mx.example.com",
100
+ });
101
+ });
102
+ check("mailBounce.dsn.build refuses proto-named action '" + k + "'", e !== null);
103
+ });
104
+ }
105
+
106
+ async function run() {
107
+ testContentDigestCreateRefusesProtoAlg();
108
+ testSqlFnAndPragmaRefuseProtoNames();
109
+ testLogCreateRefusesProtoLevel();
110
+ testCompliancePostureAccessorReturnsNullForProto();
111
+ testMakePostureAccessorPrimitive();
112
+ testMailBounceDsnRefusesProtoAction();
113
+ }
114
+
115
+ module.exports = { run: run };
116
+
117
+ if (require.main === module) {
118
+ run().then(function () { console.log("OK"); })
119
+ .catch(function (e) { console.error(e.stack || e); process.exit(1); });
120
+ }
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ /**
3
+ * b.middleware.rateLimit — X-Forwarded-For spoofing of the rate-limit key
4
+ * (CWE-290/348). A bare trustProxy honored a forgeable header, so a caller
5
+ * could rotate X-Forwarded-For to evade their own limit, or set a victim's
6
+ * IP to exhaust the victim's bucket (targeted DoS). The key is now peer-
7
+ * gated: trustProxy without trustedProxies/clientIpResolver/keyFn is refused
8
+ * at construction, and a declared proxy CIDR gates the header.
9
+ */
10
+
11
+ var helpers = require("../helpers");
12
+ var b = helpers.b;
13
+ var check = helpers.check;
14
+
15
+ function _runMw(mw, req) {
16
+ return new Promise(function (resolve) {
17
+ var res = { setHeader: function () {}, writeHead: function () {}, end: function () {}, statusCode: 200 };
18
+ var next = false;
19
+ var maybe = mw(req, res, function () { next = true; });
20
+ Promise.resolve(maybe).then(function () {
21
+ setImmediate(function () { resolve({ next: next }); });
22
+ });
23
+ });
24
+ }
25
+ function _req(sock, xff) {
26
+ return { url: "/x", pathname: "/x", method: "GET",
27
+ socket: { remoteAddress: sock },
28
+ headers: xff ? { "x-forwarded-for": xff } : {}, requestId: "r" };
29
+ }
30
+
31
+ async function run() {
32
+ // Construction fails closed: bare trustProxy with the default IP key.
33
+ var threwBare = null;
34
+ try { b.middleware.rateLimit({ backend: "memory", burst: 1, refillPerSecond: 0.001, trustProxy: true }); }
35
+ catch (e) { threwBare = e; }
36
+ check("create: bare trustProxy refused (spoofable IP key)", threwBare !== null);
37
+
38
+ // Allowed: bare trustProxy when the operator owns the key (not IP-derived).
39
+ var okKeyFn = true;
40
+ try { b.middleware.rateLimit({ backend: "memory", burst: 1, refillPerSecond: 0.001, trustProxy: true, keyFn: function () { return "k"; } }); }
41
+ catch (_e) { okKeyFn = false; }
42
+ check("create: trustProxy + keyFn allowed (key not IP)", okKeyFn === true);
43
+
44
+ // Malformed trustedProxies CIDR refused.
45
+ var threwCidr = null;
46
+ try { b.middleware.rateLimit({ backend: "memory", burst: 1, refillPerSecond: 0.001, trustedProxies: ["nope"] }); }
47
+ catch (e) { threwCidr = e; }
48
+ check("create: malformed trustedProxies CIDR refused", threwCidr !== null);
49
+
50
+ // Peer-gated: a direct attacker cannot evade by rotating X-Forwarded-For —
51
+ // the key is the socket address, so both requests hit the same bucket.
52
+ var atkMw = b.middleware.rateLimit({ backend: "memory", burst: 1, refillPerSecond: 0.001, trustedProxies: ["10.0.0.0/8"] });
53
+ var a1 = await _runMw(atkMw, _req("198.51.100.66", "1.1.1.1"));
54
+ var a2 = await _runMw(atkMw, _req("198.51.100.66", "2.2.2.2"));
55
+ check("attacker first request allowed", a1.next === true);
56
+ check("attacker cannot evade by rotating XFF (blocked)", a2.next === false);
57
+
58
+ // Behind a trusted proxy, distinct real clients get distinct buckets.
59
+ var proxMw = b.middleware.rateLimit({ backend: "memory", burst: 1, refillPerSecond: 0.001, trustedProxies: ["10.0.0.0/8"] });
60
+ var c1 = await _runMw(proxMw, _req("10.0.0.9", "203.0.113.1"));
61
+ var c2 = await _runMw(proxMw, _req("10.0.0.9", "203.0.113.2"));
62
+ check("trusted proxy: client A allowed", c1.next === true);
63
+ check("trusted proxy: client B (distinct bucket) allowed", c2.next === true);
64
+
65
+ // Same real client via the proxy shares a bucket (limit enforced per-client).
66
+ var c2again = await _runMw(proxMw, _req("10.0.0.9", "203.0.113.1"));
67
+ check("trusted proxy: client A second request blocked", c2again.next === false);
68
+
69
+ console.log("[rate-limit-xff-spoofing] OK — " + helpers.getChecks() + " checks passed");
70
+ }
71
+
72
+ module.exports = { run: run };
73
+ if (require.main === module) {
74
+ run().then(function () {}, function (e) { console.error("FAIL: " + helpers.formatErr(e)); process.exit(1); });
75
+ }
@@ -298,9 +298,137 @@ function testExtractBearerLeadingTrailingSpaces() {
298
298
  check("extractBearer: trims leading + trailing whitespace from token", t === "abc");
299
299
  }
300
300
 
301
+ function testClientIpDefaultIgnoresXff() {
302
+ // Default: socket address only — X-Forwarded-For is attacker-forgeable.
303
+ var req = { socket: { remoteAddress: "10.0.0.1" },
304
+ headers: { "x-forwarded-for": "203.0.113.7, 10.0.0.5" } };
305
+ check("clientIp default → socket addr", b.requestHelpers.clientIp(req) === "10.0.0.1");
306
+ }
307
+
308
+ function testClientIpPeerGatedTrustedPeer() {
309
+ // Predicate form: peer 10.0.0.1 is a trusted proxy → first untrusted hop
310
+ // walking right-to-left is the real client.
311
+ var trust = function (a) { return a.indexOf("10.") === 0; };
312
+ var req = { socket: { remoteAddress: "10.0.0.1" },
313
+ headers: { "x-forwarded-for": "203.0.113.7, 10.0.0.5" } };
314
+ check("clientIp peer-gated (trusted peer) → first untrusted hop",
315
+ b.requestHelpers.clientIp(req, { trustProxy: trust }) === "203.0.113.7");
316
+ }
317
+
318
+ function testClientIpPeerGatedUntrustedPeerIgnoresXff() {
319
+ // The bypass: a direct attacker (socket peer NOT a trusted proxy) forging
320
+ // an XFF must NOT be believed — fall through to the socket address.
321
+ var trust = function (a) { return a.indexOf("10.") === 0; };
322
+ var forged = { socket: { remoteAddress: "198.51.100.66" },
323
+ headers: { "x-forwarded-for": "203.0.113.7" } };
324
+ check("clientIp peer-gated (untrusted peer) → ignores forged XFF",
325
+ b.requestHelpers.clientIp(forged, { trustProxy: trust }) === "198.51.100.66");
326
+ }
327
+
328
+ function testClientIpPeerGatedAllHopsTrusted() {
329
+ // Whole chain trusted (no untrusted hop) → earliest claimed client.
330
+ var trust = function (a) { return a.indexOf("10.") === 0; };
331
+ var req = { socket: { remoteAddress: "10.0.0.1" },
332
+ headers: { "x-forwarded-for": "10.0.0.9, 10.0.0.5" } };
333
+ check("clientIp peer-gated (all trusted) → leftmost",
334
+ b.requestHelpers.clientIp(req, { trustProxy: trust }) === "10.0.0.9");
335
+ }
336
+
337
+ function testClientIpLegacyFormsStillWork() {
338
+ // Legacy spoofable forms preserved for edge-terminated deployments.
339
+ var req = { socket: { remoteAddress: "10.0.0.1" },
340
+ headers: { "x-forwarded-for": "203.0.113.7, 10.0.0.5" } };
341
+ check("clientIp legacy true → leftmost", b.requestHelpers.clientIp(req, { trustProxy: true }) === "203.0.113.7");
342
+ check("clientIp legacy N=1 → Nth-from-right", b.requestHelpers.clientIp(req, { trustProxy: 1 }) === "10.0.0.5");
343
+ }
344
+
345
+ function testTrustedClientIpPeerGatedFlag() {
346
+ check("trustedClientIp default → not peerGated",
347
+ b.requestHelpers.trustedClientIp().peerGated === false);
348
+ check("trustedClientIp trustedProxies → peerGated",
349
+ b.requestHelpers.trustedClientIp({ trustedProxies: ["10.0.0.0/8"] }).peerGated === true);
350
+ check("trustedClientIp clientIpResolver → peerGated",
351
+ b.requestHelpers.trustedClientIp({ clientIpResolver: function () { return "1.2.3.4"; } }).peerGated === true);
352
+ }
353
+
354
+ function testTrustedClientIpResolves() {
355
+ var pg = b.requestHelpers.trustedClientIp({ trustedProxies: ["10.0.0.0/8"] });
356
+ var forged = { socket: { remoteAddress: "198.51.100.66" },
357
+ headers: { "x-forwarded-for": "203.0.113.7" } };
358
+ check("trustedClientIp peer-gated ignores forged XFF (untrusted peer)",
359
+ pg.resolve(forged) === "198.51.100.66");
360
+ var viaProxy = { socket: { remoteAddress: "10.0.0.9" },
361
+ headers: { "x-forwarded-for": "203.0.113.7" } };
362
+ check("trustedClientIp peer-gated honors XFF behind trusted proxy",
363
+ pg.resolve(viaProxy) === "203.0.113.7");
364
+ var owned = b.requestHelpers.trustedClientIp({ clientIpResolver: function (rq) { return rq.headers["true-client-ip"]; } });
365
+ check("trustedClientIp clientIpResolver wins",
366
+ owned.resolve({ headers: { "true-client-ip": "9.9.9.9" } }) === "9.9.9.9");
367
+ }
368
+
369
+ function testTrustedProxyMappedPeerNormalized() {
370
+ // A dual-stack listener reports an IPv4 proxy peer as an IPv4-mapped IPv6
371
+ // address (::ffff:10.0.0.9). It must still match an IPv4 trustedProxies CIDR
372
+ // so X-Forwarded-* is honored — otherwise the proxy is treated as untrusted
373
+ // and the gate keys on the proxy address / misclassifies the scheme.
374
+ var pg = b.requestHelpers.trustedClientIp({ trustedProxies: ["10.0.0.0/8"] });
375
+ var viaMapped = { socket: { remoteAddress: "::ffff:10.0.0.9" },
376
+ headers: { "x-forwarded-for": "203.0.113.7" } };
377
+ check("trustedClientIp recognizes an IPv4-mapped trusted-proxy peer",
378
+ pg.resolve(viaMapped) === "203.0.113.7");
379
+
380
+ var tp = b.requestHelpers.trustedProtocol({ trustedProxies: ["10.0.0.0/8"] });
381
+ var mappedHttps = { socket: { encrypted: false, remoteAddress: "::ffff:10.0.0.9" },
382
+ headers: { "x-forwarded-proto": "https" } };
383
+ check("trustedProtocol recognizes an IPv4-mapped trusted-proxy peer",
384
+ tp.resolve(mappedHttps) === "https");
385
+
386
+ // A direct (untrusted) IPv4-mapped peer still can't forge: not in the CIDR.
387
+ var forgedMapped = { socket: { remoteAddress: "::ffff:198.51.100.66" },
388
+ headers: { "x-forwarded-for": "203.0.113.7" } };
389
+ check("trustedClientIp still ignores forged XFF from an untrusted mapped peer",
390
+ pg.resolve(forgedMapped) === "::ffff:198.51.100.66");
391
+ }
392
+
393
+ function testTrustedProtocol() {
394
+ var tp = b.requestHelpers.trustedProtocol({ trustedProxies: ["10.0.0.0/8"] });
395
+ check("trustedProtocol trustedProxies → peerGated", tp.peerGated === true);
396
+ check("trustedProtocol default → not peerGated", b.requestHelpers.trustedProtocol().peerGated === false);
397
+ var forged = { socket: { encrypted: false, remoteAddress: "198.51.100.66" }, headers: { "x-forwarded-proto": "https" } };
398
+ check("trustedProtocol: forged XFP from untrusted peer → http", tp.resolve(forged) === "http");
399
+ var viaProxy = { socket: { encrypted: false, remoteAddress: "10.0.0.9" }, headers: { "x-forwarded-proto": "https" } };
400
+ check("trustedProtocol: XFP via trusted proxy → https", tp.resolve(viaProxy) === "https");
401
+ var realTls = { socket: { encrypted: true, remoteAddress: "203.0.113.1" }, headers: {} };
402
+ check("trustedProtocol: real TLS socket → https", tp.resolve(realTls) === "https");
403
+ var owned = b.requestHelpers.trustedProtocol({ protocolResolver: function () { return "https"; } });
404
+ check("trustedProtocol: protocolResolver wins", owned.resolve({}) === "https");
405
+ var threwBadCidr = false;
406
+ try { b.requestHelpers.trustedProtocol({ trustedProxies: ["nope"] }); } catch (_e) { threwBadCidr = true; }
407
+ check("trustedProtocol: malformed CIDR refused", threwBadCidr === true);
408
+ }
409
+
410
+ function testTrustedClientIpValidates() {
411
+ var threwResolver = false;
412
+ try { b.requestHelpers.trustedClientIp({ clientIpResolver: 123 }); } catch (_e) { threwResolver = true; }
413
+ check("trustedClientIp rejects non-function resolver", threwResolver === true);
414
+ var threwCidr = false;
415
+ try { b.requestHelpers.trustedClientIp({ trustedProxies: ["not-a-cidr"] }); } catch (_e) { threwCidr = true; }
416
+ check("trustedClientIp rejects malformed CIDR", threwCidr === true);
417
+ }
418
+
301
419
  async function run() {
302
420
  testSurface();
303
421
  testSafeHeadersDistinct();
422
+ testClientIpDefaultIgnoresXff();
423
+ testClientIpPeerGatedTrustedPeer();
424
+ testClientIpPeerGatedUntrustedPeerIgnoresXff();
425
+ testClientIpPeerGatedAllHopsTrusted();
426
+ testClientIpLegacyFormsStillWork();
427
+ testTrustedClientIpPeerGatedFlag();
428
+ testTrustedClientIpResolves();
429
+ testTrustedProxyMappedPeerNormalized();
430
+ testTrustedProtocol();
431
+ testTrustedClientIpValidates();
304
432
  testResolveRoutePrefersRoutePattern();
305
433
  testResolveRouteFallsBackToUrl();
306
434
  testResolveRouteEmptyOrMissingUrl();