@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
@@ -44,6 +44,11 @@
44
44
  var structuredFields = require("./structured-fields");
45
45
  var pick = require("./pick");
46
46
  var codepointClass = require("./codepoint-class");
47
+ var lazyRequire = require("./lazy-require");
48
+ // Lazy — ssrf-guard pulls in the network/DNS stack, and request-helpers is
49
+ // required very early in the boot graph. Only touched at middleware-construction
50
+ // time by trustedClientIp(), never on the hot path.
51
+ var _ssrfGuard = lazyRequire(function () { return require("./ssrf-guard"); });
47
52
 
48
53
  var HTTP_STATUS = Object.freeze({
49
54
  OK: 0xC8,
@@ -214,15 +219,24 @@ function resolveActorWithOverride(callerOpts, baseOverride) {
214
219
  *
215
220
  * Resolve the originating client IP from a request. Default reads
216
221
  * only `req.socket.remoteAddress` — `X-Forwarded-For` is ignored
217
- * because without a sanitizing reverse proxy it's
218
- * attacker-forgeable. Behind a trusted proxy, operators opt in via
219
- * `trustProxy: true` (use the leftmost XFF hop) or
220
- * `trustProxy: <N>` (skip N trusted hops from the right and return
221
- * the Nth-from-rightmost). Returns `null` when no address can be
222
- * read never throws.
222
+ * because without a sanitizing reverse proxy it's attacker-forgeable.
223
+ *
224
+ * For an access-control decision (allowlist, rate-limit key, IP-bound
225
+ * grant), pass `trustProxy` as a PREDICATE `function(addr) => boolean`
226
+ * naming your trusted reverse proxies. The header is then honored only
227
+ * when the immediate TCP peer is itself a trusted proxy, and the client
228
+ * is the first untrusted address walking the chain right-to-left. A
229
+ * direct attacker cannot forge it — this is the only peer-gated form.
230
+ *
231
+ * The legacy `trustProxy: true` (leftmost XFF hop) and `trustProxy: <N>`
232
+ * (Nth-from-rightmost) forms do NOT verify the peer: a client connecting
233
+ * directly can forge any value. They are safe only when an upstream you
234
+ * control terminates and rewrites X-Forwarded-For on every request — never
235
+ * for a security decision on an internet-facing listener. Prefer the
236
+ * predicate form. Returns `null` when no address can be read — never throws.
223
237
  *
224
238
  * @opts
225
- * trustProxy: boolean | number // false (default) | true | hop count
239
+ * trustProxy: boolean | number | function // false (default) | predicate (peer-gated) | legacy true/hop-count
226
240
  *
227
241
  * @example
228
242
  * var req = {
@@ -232,30 +246,55 @@ function resolveActorWithOverride(callerOpts, baseOverride) {
232
246
  * b.requestHelpers.clientIp(req);
233
247
  * // → "10.0.0.1" (forwarded headers ignored by default)
234
248
  *
235
- * b.requestHelpers.clientIp(req, { trustProxy: true });
236
- * // → "203.0.113.7" (leftmost XFF hop)
249
+ * var fromTrusted = function (a) { return a.indexOf("10.") === 0; };
250
+ * b.requestHelpers.clientIp(req, { trustProxy: fromTrusted });
251
+ * // → "203.0.113.7" (peer 10.0.0.1 trusted; first untrusted hop)
237
252
  *
238
- * b.requestHelpers.clientIp(req, { trustProxy: 1 });
239
- * // "10.0.0.5" (1 trusted hop from the right)
253
+ * var forged = { socket: { remoteAddress: "198.51.100.66" },
254
+ * headers: { "x-forwarded-for": "203.0.113.7" } };
255
+ * b.requestHelpers.clientIp(forged, { trustProxy: fromTrusted });
256
+ * // → "198.51.100.66" (peer untrusted → forged header ignored)
240
257
  *
241
258
  * b.requestHelpers.clientIp(undefined);
242
259
  * // → null
243
260
  */
244
261
  function clientIp(req, opts) {
245
262
  if (!req) return null;
263
+ var socketAddr =
264
+ (req.socket && typeof req.socket.remoteAddress === "string" && req.socket.remoteAddress) ? req.socket.remoteAddress
265
+ : (req.connection && typeof req.connection.remoteAddress === "string" && req.connection.remoteAddress) ? req.connection.remoteAddress
266
+ : null;
246
267
  var trust = opts && opts.trustProxy;
247
268
  if (trust && req.headers) {
248
269
  var xff = req.headers["x-forwarded-for"];
249
270
  if (xff) {
250
271
  var hops = parseListHeader(xff);
251
- if (trust === true) return hops[0];
252
- if (typeof trust === "number" && trust >= 1 && hops.length >= trust) {
253
- return hops[hops.length - trust];
272
+ if (hops.length) {
273
+ if (typeof trust === "function") {
274
+ // Peer-gated resolution: `trust(addr)` names the trusted reverse
275
+ // proxies. X-Forwarded-For is honored ONLY when the immediate TCP
276
+ // peer is itself a trusted proxy; the real client is then the first
277
+ // untrusted address walking the chain right-to-left (each hop is
278
+ // appended by the proxy that observed it). A direct attacker — whose
279
+ // socket peer is not a trusted proxy — cannot forge the result: the
280
+ // forgeable header is ignored and we fall through to the socket
281
+ // address. This is the only form safe for an access-control decision.
282
+ if (socketAddr && trust(socketAddr)) {
283
+ for (var i = hops.length - 1; i >= 0; i--) {
284
+ if (!trust(hops[i])) return hops[i];
285
+ }
286
+ return hops[0]; // entire chain trusted — earliest claimed client
287
+ }
288
+ // peer is not a trusted proxy → ignore forgeable XFF, fall through
289
+ } else if (trust === true) {
290
+ return hops[0];
291
+ } else if (typeof trust === "number" && trust >= 1 && hops.length >= trust) {
292
+ return hops[hops.length - trust];
293
+ }
254
294
  }
255
295
  }
256
296
  }
257
- if (req.socket && typeof req.socket.remoteAddress === "string") return req.socket.remoteAddress;
258
- if (req.connection && typeof req.connection.remoteAddress === "string") return req.connection.remoteAddress;
297
+ if (socketAddr) return socketAddr;
259
298
  // Express-shaped requests expose the resolved client address as `req.ip`
260
299
  // (Express derives it from the socket, honoring its own trust-proxy
261
300
  // setting) without a `socket.remoteAddress` surface. Fall back to it so a
@@ -266,6 +305,125 @@ function clientIp(req, opts) {
266
305
  return null;
267
306
  }
268
307
 
308
+ /**
309
+ * @primitive b.requestHelpers.trustedClientIp
310
+ * @signature b.requestHelpers.trustedClientIp(opts?)
311
+ * @since 0.15.14
312
+ * @related b.requestHelpers.clientIp
313
+ *
314
+ * Build a peer-gated client-IP resolver for an access-control decision
315
+ * (allowlist, rate-limit key, IP-bound grant). The bare `trustProxy`
316
+ * forms of `clientIp` are forgeable; this is the shape every gate shares
317
+ * so the trust model is identical across them. Returns
318
+ * `{ resolve(req), peerGated }`: `resolve` reads the client IP, `peerGated`
319
+ * is true when `trustedProxies` or `clientIpResolver` was supplied — a
320
+ * gate uses it to refuse a bare `trustProxy` at construction (fail closed).
321
+ *
322
+ * With `clientIpResolver(req)` the operator owns resolution entirely. With
323
+ * `trustedProxies` (CIDRs of the reverse proxies), `X-Forwarded-For` is
324
+ * honored only when the immediate peer is one of them. With neither, only
325
+ * the socket address is used and forwarded headers are ignored.
326
+ *
327
+ * @opts
328
+ * trustedProxies: string | string[], // CIDRs — peer-gate X-Forwarded-For
329
+ * clientIpResolver: function(req): string|null, // own resolution entirely
330
+ *
331
+ * @example
332
+ * var tip = b.requestHelpers.trustedClientIp({ trustedProxies: ["10.0.0.0/8"] });
333
+ * var ip = tip.resolve(req); // peer-gated; forged XFF from a direct caller ignored
334
+ */
335
+ // Build the trusted-proxy predicate shared by trustedClientIp / trustedProtocol.
336
+ // Validates each CIDR (a CIDR is valid iff it contains its own network address,
337
+ // reusing the same matcher the predicate uses so format rules can't diverge) and
338
+ // returns fn(addr)=>boolean, or null when no trustedProxies were given. `where`
339
+ // names the calling helper for the error message.
340
+ function _trustedProxyPredicate(trustedProxies, where) {
341
+ if (!trustedProxies || !trustedProxies.length) return null;
342
+ var ssrfGuard = _ssrfGuard();
343
+ for (var i = 0; i < trustedProxies.length; i++) {
344
+ var cidr = trustedProxies[i];
345
+ var slash = typeof cidr === "string" ? cidr.indexOf("/") : -1;
346
+ if (slash === -1 || !ssrfGuard.cidrContains(cidr, cidr.slice(0, slash))) {
347
+ throw new TypeError(where + ": trustedProxies[" + i + "] is not a valid CIDR, got " + JSON.stringify(cidr));
348
+ }
349
+ }
350
+ return function (addr) {
351
+ // Fold an IPv4-mapped IPv6 peer (::ffff:a.b.c.d, common on a dual-stack
352
+ // listener) to its dotted IPv4 form so it matches an IPv4 trustedProxies
353
+ // CIDR — cidrContains rejects a cross-family compare, so without this a
354
+ // mapped proxy peer reads as untrusted and X-Forwarded-* is ignored. Only
355
+ // the ::ffff:0:0/96 block folds (canonicalizeHost leaves NAT64 / 6to4 as
356
+ // IPv6), so this can't widen the trusted set.
357
+ var canon = ssrfGuard.canonicalizeHost(addr);
358
+ for (var j = 0; j < trustedProxies.length; j++) {
359
+ if (ssrfGuard.cidrContains(trustedProxies[j], canon)) return true;
360
+ }
361
+ return false;
362
+ };
363
+ }
364
+
365
+ function _normTrustedProxies(opts) {
366
+ return Array.isArray(opts.trustedProxies) ? opts.trustedProxies.slice()
367
+ : (typeof opts.trustedProxies === "string" && opts.trustedProxies.length ? [opts.trustedProxies] : []);
368
+ }
369
+
370
+ function trustedClientIp(opts) {
371
+ opts = opts || {};
372
+ var resolver = opts.clientIpResolver;
373
+ if (resolver != null && typeof resolver !== "function") {
374
+ throw new TypeError("trustedClientIp: clientIpResolver must be a function(req) => ip|null");
375
+ }
376
+ var predicate = _trustedProxyPredicate(_normTrustedProxies(opts), "trustedClientIp");
377
+ return {
378
+ peerGated: !!(resolver || predicate),
379
+ resolve: function (req) {
380
+ if (resolver) return resolver(req);
381
+ if (predicate) return clientIp(req, { trustProxy: predicate });
382
+ return clientIp(req, { trustProxy: false });
383
+ },
384
+ };
385
+ }
386
+
387
+ /**
388
+ * @primitive b.requestHelpers.trustedProtocol
389
+ * @signature b.requestHelpers.trustedProtocol(opts?)
390
+ * @since 0.15.14
391
+ * @related b.requestHelpers.requestProtocol, b.requestHelpers.trustedClientIp
392
+ *
393
+ * Peer-gated companion to trustedClientIp for the request scheme. The
394
+ * Secure-cookie / HSTS / secure-context decisions hinge on whether a request
395
+ * arrived over HTTPS; behind a TLS-terminating proxy that comes from
396
+ * X-Forwarded-Proto, which is forgeable unless the immediate peer is a trusted
397
+ * proxy. Returns `{ resolve(req)=>"http"|"https", peerGated }`. With
398
+ * `trustedProxies` (CIDRs) the header is honored only from a trusted peer; with
399
+ * `protocolResolver(req)` the operator owns the decision; with neither only the
400
+ * real TLS socket is consulted (forwarded headers ignored).
401
+ *
402
+ * @opts
403
+ * trustedProxies: string | string[],
404
+ * protocolResolver: function(req): "http"|"https",
405
+ *
406
+ * @example
407
+ * var tp = b.requestHelpers.trustedProtocol({ trustedProxies: ["10.0.0.0/8"] });
408
+ * tp.resolve(req); // "https" only when X-Forwarded-Proto came via a trusted peer
409
+ */
410
+ function trustedProtocol(opts) {
411
+ opts = opts || {};
412
+ var resolver = opts.protocolResolver;
413
+ if (resolver != null && typeof resolver !== "function") {
414
+ throw new TypeError("trustedProtocol: protocolResolver must be a function(req) => 'http'|'https'");
415
+ }
416
+ var predicate = _trustedProxyPredicate(_normTrustedProxies(opts), "trustedProtocol");
417
+ return {
418
+ peerGated: !!(resolver || predicate),
419
+ resolve: function (req) {
420
+ if (resolver) return resolver(req);
421
+ if (predicate) return requestProtocol(req, { trustProxy: predicate });
422
+ return requestProtocol(req, { trustProxy: false });
423
+ },
424
+ };
425
+ }
426
+
269
427
  /**
270
428
  * @primitive b.requestHelpers.requestProtocol
271
429
  * @signature b.requestHelpers.requestProtocol(req, opts?)
@@ -274,14 +432,17 @@ function clientIp(req, opts) {
274
432
  *
275
433
  * Resolve the inbound transport scheme. Default returns `"https"`
276
434
  * when `req.socket.encrypted` is set, otherwise `"http"`. Behind a
277
- * trusted reverse proxy that terminates TLS, set `trustProxy: true`
278
- * to read the leftmost `X-Forwarded-Proto` hop instead — without
279
- * the explicit opt-in the framework refuses to pick up the
280
- * attacker-forgeable header. Always returns a string; on bad input
281
- * falls back to `"http"`.
435
+ * trusted reverse proxy that terminates TLS, pass `trustProxy` as a
436
+ * PREDICATE `function(addr)=>boolean` naming your proxies:
437
+ * `X-Forwarded-Proto` is then honored only when the immediate peer is
438
+ * a trusted proxy, so a direct caller can't forge it (use
439
+ * `b.requestHelpers.trustedProtocol` to build this). The legacy
440
+ * `trustProxy: true` reads the leftmost hop without checking the peer —
441
+ * forgeable, safe only behind an edge that rewrites the header. Always
442
+ * returns a string; on bad input falls back to `"http"`.
282
443
  *
283
444
  * @opts
284
- * trustProxy: boolean // false (default) | true
445
+ * trustProxy: boolean | function // false (default) | predicate (peer-gated) | legacy true
285
446
  *
286
447
  * @example
287
448
  * var req = { socket: { encrypted: true } };
@@ -305,7 +466,22 @@ function requestProtocol(req, opts) {
305
466
  var fwd = req.headers["x-forwarded-proto"];
306
467
  if (typeof fwd === "string" && fwd.length > 0) {
307
468
  var hops = parseListHeader(fwd, { lowercase: true });
308
- if (hops.length > 0) return hops[0];
469
+ if (hops.length > 0) {
470
+ if (typeof trust === "function") {
471
+ // Peer-gated: honor X-Forwarded-Proto only when the immediate TCP
472
+ // peer is a trusted proxy. A direct caller's forged header is
473
+ // ignored — fall through to the real TLS socket. The only form safe
474
+ // for a Secure-cookie / HSTS / secure-context decision.
475
+ var peer =
476
+ (req.socket && typeof req.socket.remoteAddress === "string" && req.socket.remoteAddress) ? req.socket.remoteAddress
477
+ : (req.connection && typeof req.connection.remoteAddress === "string" && req.connection.remoteAddress) ? req.connection.remoteAddress
478
+ : null;
479
+ if (peer && trust(peer)) return hops[0];
480
+ // peer not a trusted proxy → ignore forgeable header, fall through
481
+ } else {
482
+ return hops[0]; // legacy true/number — spoofable, see docstring
483
+ }
484
+ }
309
485
  }
310
486
  }
311
487
  if (req.socket && req.socket.encrypted) return "https";
@@ -890,7 +1066,9 @@ module.exports = {
890
1066
  parseListHeader: parseListHeader,
891
1067
  // proxy-trust primitives (default refuses forwarded headers)
892
1068
  clientIp: clientIp,
1069
+ trustedClientIp: trustedClientIp,
893
1070
  requestProtocol: requestProtocol,
1071
+ trustedProtocol: trustedProtocol,
894
1072
  appendVary: appendVary,
895
1073
  // CVE-2026-21710 wrap — safe alternative to req.headersDistinct
896
1074
  safeHeadersDistinct: safeHeadersDistinct,
@@ -49,7 +49,7 @@ function create(opts) {
49
49
  validateOpts.requireNonEmptyString(opts.resource, "resource",
50
50
  ResourceAccessLockError, "resource-access-lock/no-resource");
51
51
  var startMode = opts.startMode || "open";
52
- if (!VALID_MODES[startMode]) {
52
+ if (!Object.prototype.hasOwnProperty.call(VALID_MODES, startMode)) {
53
53
  throw new ResourceAccessLockError(
54
54
  "resource-access-lock/bad-start-mode",
55
55
  "startMode must be one of: " + Object.keys(VALID_MODES).join(" / "));
@@ -71,12 +71,12 @@ function create(opts) {
71
71
  function permits(action) {
72
72
  if (mode === "open") return true;
73
73
  if (mode === "locked") return false;
74
- return !!READ_ACTIONS[action];
74
+ return !!Object.prototype.hasOwnProperty.call(READ_ACTIONS, action);
75
75
  }
76
76
 
77
77
  function set(newMode, ctx) {
78
78
  ctx = ctx || {};
79
- if (!VALID_MODES[newMode]) {
79
+ if (!Object.prototype.hasOwnProperty.call(VALID_MODES, newMode)) {
80
80
  throw new ResourceAccessLockError(
81
81
  "resource-access-lock/bad-mode",
82
82
  "set: mode must be one of: " + Object.keys(VALID_MODES).join(" / "));
@@ -48,6 +48,7 @@
48
48
  var nodeFs = require("node:fs");
49
49
  var nodePath = require("node:path");
50
50
  var atomicFile = require("./atomic-file");
51
+ var C = require("./constants");
51
52
  var backupCrypto = require("./backup/crypto");
52
53
  var backupManifest = require("./backup/manifest");
53
54
  var validateOpts = require("./validate-opts");
@@ -146,13 +147,21 @@ async function extract(opts) {
146
147
  // 1. Read + parse + validate manifest
147
148
  _emit(progress, { phase: "read_manifest" });
148
149
  var manifestPath = nodePath.join(bundleDir, "manifest.json");
149
- if (!nodeFs.existsSync(manifestPath)) {
150
- throw new RestoreBundleError("restore-bundle/missing-manifest",
151
- "extract: bundleDir has no manifest.json bundle is incomplete or not a blamejs backup");
152
- }
150
+ // Capped fd-bound read OUTSIDE the parse try/catch, so a missing / oversized
151
+ // bundle manifest surfaces the precise restore-bundle code (not a generic
152
+ // parse error). A backup manifest is small JSON; 4 MiB bounds a hostile one
153
+ // before backupManifest.parse materializes it.
154
+ var manifestRaw = atomicFile.fdSafeReadSync(manifestPath, {
155
+ maxBytes: C.BYTES.mib(4), encoding: "utf8",
156
+ errorFor: function (kind, detail) {
157
+ if (kind === "enoent") return new RestoreBundleError("restore-bundle/missing-manifest", "extract: bundleDir has no manifest.json — bundle is incomplete or not a blamejs backup");
158
+ if (kind === "too-large") return new RestoreBundleError("restore-bundle/bad-manifest", "extract: manifest.json exceeds " + detail.max + " bytes");
159
+ return new RestoreBundleError("restore-bundle/bad-manifest", "extract: manifest unreadable: " + kind);
160
+ },
161
+ });
153
162
  var manifest;
154
163
  try {
155
- manifest = backupManifest.parse(nodeFs.readFileSync(manifestPath, "utf8"));
164
+ manifest = backupManifest.parse(manifestRaw);
156
165
  } catch (e) {
157
166
  if (e && e.isBackupManifestError) throw e;
158
167
  throw new RestoreBundleError("restore-bundle/bad-manifest",
@@ -216,12 +225,20 @@ async function extract(opts) {
216
225
  }
217
226
 
218
227
  var blobPath = nodePath.join(bundleDir, entry.encryptedPath);
219
- if (!nodeFs.existsSync(blobPath)) {
220
- throw new RestoreBundleError("restore-bundle/missing-blob",
221
- "extract: manifest references '" + entry.encryptedPath +
222
- "' but the bundle has no such file");
223
- }
224
- var blob = nodeFs.readFileSync(blobPath);
228
+ // Cap the read to the manifest's declared encryptedSize so an oversize-on-
229
+ // disk blob is refused BEFORE it is read into memory (was: read fully, then
230
+ // compare an OOM window for a huge swapped blob). A valid blob is exactly
231
+ // encryptedSize; the post-read compare still catches the under-size case.
232
+ var blobCap = (typeof entry.encryptedSize === "number" && entry.encryptedSize > 0)
233
+ ? entry.encryptedSize : C.BYTES.gib(8);
234
+ var blob = atomicFile.fdSafeReadSync(blobPath, {
235
+ maxBytes: blobCap,
236
+ errorFor: function (kind, detail) {
237
+ if (kind === "enoent") return new RestoreBundleError("restore-bundle/missing-blob", "extract: manifest references '" + entry.encryptedPath + "' but the bundle has no such file");
238
+ if (kind === "too-large") return new RestoreBundleError("restore-bundle/size-mismatch", "extract: blob '" + entry.encryptedPath + "' has size " + detail.size + " but manifest expected " + entry.encryptedSize);
239
+ return new RestoreBundleError("restore-bundle/missing-blob", "extract: blob '" + entry.encryptedPath + "' unreadable: " + kind);
240
+ },
241
+ });
225
242
  if (blob.length !== entry.encryptedSize) {
226
243
  throw new RestoreBundleError("restore-bundle/size-mismatch",
227
244
  "extract: blob '" + entry.encryptedPath + "' has size " + blob.length +
@@ -235,12 +252,18 @@ async function extract(opts) {
235
252
 
236
253
  var plaintext;
237
254
  try {
238
- plaintext = await backupCrypto.decryptWithPassphrase(blob, passphrase, entry.salt);
255
+ // Bundles written with manifest.aadBound sealed each blob with its
256
+ // relativePath as AEAD associated data — pass the SAME path so a blob
257
+ // remapped to a different manifest entry fails the tag here (the
258
+ // blob-remap / restore-corruption defense). Legacy bundles without
259
+ // the flag decrypt with no AAD (backward compatible).
260
+ var blobAad = manifest.aadBound === true ? Buffer.from(entry.relativePath, "utf8") : undefined;
261
+ plaintext = await backupCrypto.decryptWithPassphrase(blob, passphrase, entry.salt, blobAad);
239
262
  } catch (e) {
240
263
  if (e && e.isBackupCryptoError && e.code === "backup-crypto/decrypt-failed") {
241
264
  throw new RestoreBundleError("restore-bundle/decrypt-failed",
242
265
  "extract: blob '" + entry.encryptedPath + "' did not decrypt — " +
243
- "passphrase rejected or ciphertext tampered");
266
+ "passphrase rejected, ciphertext tampered, or blob remapped to a different path");
244
267
  }
245
268
  throw e;
246
269
  }
@@ -326,11 +349,16 @@ function inspect(opts) {
326
349
  "inspect: opts.bundleDir is required and must exist");
327
350
  }
328
351
  var manifestPath = nodePath.join(opts.bundleDir, "manifest.json");
329
- if (!nodeFs.existsSync(manifestPath)) {
330
- throw new RestoreBundleError("restore-bundle/missing-manifest",
331
- "inspect: bundleDir has no manifest.json");
332
- }
333
- return backupManifest.parse(nodeFs.readFileSync(manifestPath, "utf8"));
352
+ // Capped fd-bound read (no-passphrase preview path, reachable with only read
353
+ // access to the bundle): bound a hostile manifest before parse.
354
+ return backupManifest.parse(atomicFile.fdSafeReadSync(manifestPath, {
355
+ maxBytes: C.BYTES.mib(4), encoding: "utf8",
356
+ errorFor: function (kind) {
357
+ if (kind === "enoent") return new RestoreBundleError("restore-bundle/missing-manifest", "inspect: bundleDir has no manifest.json");
358
+ if (kind === "too-large") return new RestoreBundleError("restore-bundle/bad-manifest", "inspect: manifest.json too large");
359
+ return new RestoreBundleError("restore-bundle/missing-manifest", "inspect: manifest unreadable: " + kind);
360
+ },
361
+ }));
334
362
  }
335
363
 
336
364
  module.exports = {
@@ -283,10 +283,16 @@ function list(opts) {
283
283
  var p = nodePath.join(rollbackRoot, name);
284
284
  var markerPath = p + ".marker.json";
285
285
  var marker = null;
286
- if (nodeFs.existsSync(markerPath)) {
287
- try { marker = safeJson.parse(nodeFs.readFileSync(markerPath, "utf8"), { maxBytes: C.BYTES.kib(64) }); }
288
- catch (_e) { marker = null; }
289
- }
286
+ // Capped fd-bound read (no existsSync check-then-read window): the fs read is
287
+ // now bounded to 64 KiB too, so a tampered multi-GB marker.json is refused
288
+ // BEFORE allocation (the parse-only cap let readFileSync slurp the whole file
289
+ // first). refuseSymlink: the marker lives under the operator's rollbackRoot,
290
+ // never a secret-mount. Any failure → marker:null, the best-effort behavior.
291
+ try {
292
+ marker = safeJson.parse(
293
+ atomicFile.fdSafeReadSync(markerPath, { maxBytes: C.BYTES.kib(64), encoding: "utf8", refuseSymlink: true }),
294
+ { maxBytes: C.BYTES.kib(64) });
295
+ } catch (_e) { marker = null; }
290
296
  var stat;
291
297
  try { stat = nodeFs.statSync(p); } catch (_e) { continue; }
292
298
  out.push({
@@ -83,6 +83,7 @@ function create(opts) {
83
83
  validateOpts(opts, [
84
84
  "dataDir", "storage", "passphrase", "rollbackRoot", "audit",
85
85
  "maxPulledBytes", "maxPulledFiles",
86
+ "requireSignature", "expectedFingerprint", "verifySignature",
86
87
  ], "restore");
87
88
  validateOpts.requireNonEmptyString(opts.dataDir, "create: opts.dataDir", RestoreError, "restore/no-datadir");
88
89
  _validateStorage(opts.storage);
@@ -96,6 +97,19 @@ function create(opts) {
96
97
  var passphrase = opts.passphrase;
97
98
  var rollbackRoot = opts.rollbackRoot || (dataDir + ".rollbacks");
98
99
  var auditOn = opts.audit !== false;
100
+ // Manifest-signature policy. The framework signs bundles best-effort (an
101
+ // unsigned bundle is the documented CLI / standalone / worker case), so the
102
+ // non-opt-in integrity default is the per-blob AEAD path-binding below
103
+ // (which defeats the blob-remap attack on EVERY bundle, signed or not);
104
+ // requireSignature is the additional provenance policy operators under
105
+ // HIPAA/PCI opt into to mandate a verified signer. expectedFingerprint pins
106
+ // a signer; verifySignature:false allows a cold/cross-org restore. All
107
+ // three are threaded into restoreBundle.extract on every run — previously
108
+ // omitted entirely, so a present signature was never even verified and a
109
+ // requireSignature policy could not be enforced (CWE-347).
110
+ var requireSignature = opts.requireSignature === true;
111
+ var expectedFingerprint = opts.expectedFingerprint;
112
+ var verifySignature = opts.verifySignature;
99
113
 
100
114
  // Preflight footprint caps. Defended against storage that returns a
101
115
  // tampered or oversized bundle: we cap both the storage-reported size
@@ -271,6 +285,9 @@ function create(opts) {
271
285
  passphrase: passphrase,
272
286
  filter: runOpts.filter,
273
287
  progressCallback: runOpts.progressCallback,
288
+ requireSignature: requireSignature,
289
+ expectedFingerprint: expectedFingerprint,
290
+ verifySignature: verifySignature,
274
291
  });
275
292
  } catch (e) {
276
293
  _cleanupTmp();
@@ -283,6 +300,8 @@ function create(opts) {
283
300
  else if (code === "restore-bundle/missing-manifest") mappedCode = "restore/missing-manifest";
284
301
  else if (code === "restore-bundle/missing-blob") mappedCode = "restore/missing-blob";
285
302
  else if (code === "restore-bundle/size-mismatch") mappedCode = "restore/size-mismatch";
303
+ else if (code === "restore-bundle/missing-signature") mappedCode = "restore/missing-signature";
304
+ else if (code === "restore-bundle/bad-signature") mappedCode = "restore/bad-signature";
286
305
  _emitAudit("restore.failure",
287
306
  { bundleId: bundleId, reason: (e && e.message) || String(e) }, "failure");
288
307
  throw new RestoreError(mappedCode,
@@ -431,17 +431,29 @@ function create(opts) {
431
431
 
432
432
  try {
433
433
  var moreRows = true;
434
+ // Keyset pagination cursor. Without it the loop re-selects the SAME batch
435
+ // forever whenever a full batch of rows is NOT removed from the candidate
436
+ // set by its action — legal-hold-skipped, "warn"-stage, errored, and
437
+ // (critically) EVERY row under dryRun mutate nothing, so a LIMIT-from-the-
438
+ // top query returns the identical rows each pass and `rows.length ===
439
+ // batchSize` never goes false. Ordering by _id and advancing past the last
440
+ // row seen guarantees forward progress regardless of whether a row was
441
+ // actioned. (Deleted / anonymized rows are also simply skipped past.)
442
+ var lastId = null;
434
443
  while (moreRows) {
435
444
  var rows;
436
445
  // The candidate WHERE-clause: age + not-already-erased + not-on-legal-hold +
437
- // (when soft-delete is configured) not-already-soft-deleted. Built
438
- // through b.sql so the operator-supplied table / ageField / softDeleteField
439
- // identifiers are quoted by construction and every value binds as a
440
- // placeholder (the '' empty-string compare included — no embedded literal).
446
+ // (when soft-delete is configured) not-already-soft-deleted + keyset cursor.
447
+ // Built through b.sql so the operator-supplied table / ageField /
448
+ // softDeleteField identifiers are quoted by construction and every value
449
+ // binds as a placeholder (the '' empty-string compare included — no
450
+ // embedded literal).
441
451
  function _candidateBase() {
442
452
  var qb = sql.select(rule.table, SQL_OPTS)
443
453
  .where(rule.ageField, "<=", cutoff);
444
454
  if (rule.softDeleteField) qb.whereNull(rule.softDeleteField);
455
+ if (lastId !== null) qb.where("_id", ">", lastId);
456
+ qb.orderBy("_id", "asc");
445
457
  return qb;
446
458
  }
447
459
  var selStmt;
@@ -504,6 +516,10 @@ function create(opts) {
504
516
  reason: (e && e.message) || String(e) });
505
517
  }
506
518
  }
519
+ // Advance the keyset cursor past this batch so already-seen rows
520
+ // (including skipped / warned / errored / dry-run rows that stay in the
521
+ // candidate set) are never re-selected — the loop-termination guarantee.
522
+ lastId = rows[rows.length - 1]._id;
507
523
  if (rows.length < rule.batchSize) moreRows = false;
508
524
  }
509
525
  } catch (e) {
@@ -36,6 +36,7 @@ var http = require("node:http");
36
36
  var http2 = require("node:http2");
37
37
  var nodeFs = require("node:fs");
38
38
  var nodePath = require("node:path");
39
+ var atomicFile = require("./atomic-file");
39
40
  var C = require("./constants");
40
41
  var requestHelpers = require("./request-helpers");
41
42
  var lazyRequire = require("./lazy-require");
@@ -147,7 +148,11 @@ function _makeSchemaValidator(spec) {
147
148
  if (!qq.ok) return _writeValidationError(req, res, "query", qq.errors);
148
149
  req.query = qq.value;
149
150
  }
150
- if (spec.body && req.body !== undefined) {
151
+ if (spec.body) {
152
+ // Validate even when req.body is undefined (no body parsed / empty POST):
153
+ // a required body schema must report the violation, not be silently
154
+ // skipped. safeParse(undefined) fails a non-optional schema and passes an
155
+ // optional()/default() one. Mirrors the always-run params/query checks.
151
156
  var bb = spec.body.safeParse(req.body);
152
157
  if (!bb.ok) return _writeValidationError(req, res, "body", bb.errors);
153
158
  req.body = bb.value;
@@ -1425,11 +1430,19 @@ function serveStatic(dir) {
1425
1430
  // that shares the root's name as a prefix — e.g. root `/srv/public`
1426
1431
  // vs `/srv/public-evil` — cannot satisfy the containment check.
1427
1432
  if (filePath !== root && !filePath.startsWith(root + nodePath.sep)) return next();
1428
- if (!nodeFs.existsSync(filePath) || nodeFs.statSync(filePath).isDirectory()) return next();
1433
+ // Open the (lexically-confined) path ONCE with O_NOFOLLOW and bind existence,
1434
+ // stat, and streaming to that single fd. The previous existsSync → statSync →
1435
+ // statSync → createReadStream sequence was a triple check-then-read TOCTOU
1436
+ // (CWE-367) on a request-derived pathname, and the plain createReadStream
1437
+ // would follow a symlink swapped in after the lexical check (CWE-22).
1438
+ // Opening before writeHead lets a refusal still fall through to next().
1439
+ var fd;
1440
+ try { fd = atomicFile.openNoFollowSync(filePath); } catch (_e) { return next(); }
1441
+ var stat = nodeFs.fstatSync(fd);
1442
+ if (stat.isDirectory()) { nodeFs.closeSync(fd); return next(); }
1429
1443
 
1430
1444
  var ext = nodePath.extname(filePath).toLowerCase();
1431
1445
  var mime = MIME_TYPES[ext] || "application/octet-stream";
1432
- var stat = nodeFs.statSync(filePath);
1433
1446
  var hasVersion = req.url && req.url.includes("?v=");
1434
1447
  var cacheControl = hasVersion
1435
1448
  ? "public, max-age=31536000, immutable"
@@ -1439,7 +1452,7 @@ function serveStatic(dir) {
1439
1452
  "Content-Length": stat.size,
1440
1453
  "Cache-Control": cacheControl,
1441
1454
  });
1442
- nodeFs.createReadStream(filePath).pipe(res);
1455
+ nodeFs.createReadStream(filePath, { fd: fd }).pipe(res);
1443
1456
  };
1444
1457
  }
1445
1458
 
@@ -444,7 +444,7 @@ function _parseComponent(lines, startIdx, ctx, depth) {
444
444
  "safeIcal.parse: expected BEGIN, got '" + begin.name + "'");
445
445
  }
446
446
  var compName = begin.value.toUpperCase();
447
- if (!KNOWN_COMPONENTS[compName] && !ctx.extraComps[compName] &&
447
+ if (!Object.prototype.hasOwnProperty.call(KNOWN_COMPONENTS, compName) && !ctx.extraComps[compName] &&
448
448
  compName.indexOf("X-") !== 0) {
449
449
  throw new SafeIcalError("safe-ical/unknown-component",
450
450
  "safeIcal.parse: unknown component '" + compName +
@@ -475,7 +475,7 @@ function _parseComponent(lines, startIdx, ctx, depth) {
475
475
  }
476
476
  // Validate property name.
477
477
  var pn = ln.name;
478
- if (!KNOWN_PROPERTIES[pn] && !ctx.extraProps[pn] && pn.indexOf("X-") !== 0) {
478
+ if (!Object.prototype.hasOwnProperty.call(KNOWN_PROPERTIES, pn) && !ctx.extraProps[pn] && pn.indexOf("X-") !== 0) {
479
479
  throw new SafeIcalError("safe-ical/unknown-property",
480
480
  "safeIcal.parse: unknown property '" + pn +
481
481
  "' (extend via opts.extraProperties or use X- prefix)");
@@ -426,7 +426,7 @@ function _parseEncapsulated(value) {
426
426
  }
427
427
  var part = token.slice(0, eq);
428
428
  var offStr = token.slice(eq + 1);
429
- if (!ENCAPSULATED_PARTS[part]) {
429
+ if (!Object.prototype.hasOwnProperty.call(ENCAPSULATED_PARTS, part)) {
430
430
  throw new SafeIcapError("safe-icap/bad-encapsulated",
431
431
  "safeIcap.parse: Encapsulated part '" + part + "' is not one of " +
432
432
  Object.keys(ENCAPSULATED_PARTS).join(", "));