@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
@@ -145,6 +145,45 @@ async function testMiddlewareScopeDenied() {
145
145
  body && body.error && body.error.code === -32001);
146
146
  }
147
147
 
148
+ async function testMiddlewareScopeNonStringThrows() {
149
+ // A non-string scope VALUE (operator typo, e.g. an array) must be refused at
150
+ // construction — else the runtime `typeof requiredScope === "string"` gate
151
+ // silently skips, a fail-open authorization bypass on a gated skill.
152
+ var threw = null;
153
+ try {
154
+ b.a2a.middleware.tasks({ scopes: { transfer: ["a2a:transfer"] }, handler: function () {} });
155
+ } catch (e) { threw = e; }
156
+ check("middleware: non-string scope value rejected at construction",
157
+ threw && threw.code === "a2a-tasks/bad-mw-opts");
158
+ }
159
+
160
+ async function testMiddlewareRejectsHostileSkill() {
161
+ // The untrusted-peer ingress must validate task.skill (the client send path
162
+ // does); an oversized / metacharacter-laden skill must never reach the handler.
163
+ var handlerRan = false;
164
+ var mw = b.a2a.middleware.tasks({ handler: function () { handlerRan = true; return null; } });
165
+ var hostile = "A".repeat(5000);
166
+ var req = _mockReq("POST", "application/json",
167
+ Buffer.from(JSON.stringify({ jsonrpc: "2.0", id: 1, method: "tasks/send", params: { task: { skill: hostile } } })),
168
+ []);
169
+ var res = _mockRes();
170
+ var body = await _runMwAndDecode(mw, req, res);
171
+ check("middleware: oversized skill rejected (-32602), handler not run",
172
+ body && body.error && body.error.code === -32602 && handlerRan === false);
173
+ }
174
+
175
+ async function testMiddlewareRejectsHostileTaskId() {
176
+ var handlerRan = false;
177
+ var mw = b.a2a.middleware.tasks({ handler: function () { handlerRan = true; return null; } });
178
+ var req = _mockReq("POST", "application/json",
179
+ Buffer.from(JSON.stringify({ jsonrpc: "2.0", id: 1, method: "tasks/get", params: { taskId: "../../etc/passwd" } })),
180
+ []);
181
+ var res = _mockRes();
182
+ var body = await _runMwAndDecode(mw, req, res);
183
+ check("middleware: traversal taskId rejected (-32602), handler not run",
184
+ body && body.error && body.error.code === -32602 && handlerRan === false);
185
+ }
186
+
148
187
  async function testMiddlewareSuccessSend() {
149
188
  var captured = null;
150
189
  var mw = b.a2a.middleware.tasks({
@@ -199,6 +238,9 @@ async function run() {
199
238
  await testMiddlewareInvalidJson();
200
239
  await testMiddlewareMethodNotFound();
201
240
  await testMiddlewareScopeDenied();
241
+ await testMiddlewareScopeNonStringThrows();
242
+ await testMiddlewareRejectsHostileSkill();
243
+ await testMiddlewareRejectsHostileTaskId();
202
244
  await testMiddlewareSuccessSend();
203
245
  await testAgentCardMiddleware();
204
246
  }
@@ -385,6 +385,41 @@ async function testEnvelopeMacTamperFails() {
385
385
  }
386
386
  }
387
387
 
388
+ async function testCrossTopicReplayDropped() {
389
+ // A genuinely-MAC'd envelope for topic A, replayed verbatim onto topic B's
390
+ // channel, must NOT be delivered to B's handler — even when A and B share
391
+ // a schema + tenant. The MAC binds _topic=A (so it cannot be forged), but
392
+ // the subscriber must additionally bind the authenticated _topic to the
393
+ // channel it was registered for.
394
+ var tmpDir = _tmp();
395
+ await helpers.setupVaultOnly(tmpDir);
396
+ try {
397
+ var pubsub = _capturingPubsub();
398
+ var bus = b.agent.eventBus.create({ pubsub: pubsub });
399
+ bus.registerTopic("mail.scan.alpha", { schema: { source: "string" }, posture: "soc2" });
400
+ bus.registerTopic("mail.scan.beta", { schema: { source: "string" }, posture: "soc2" });
401
+ var gotA = [], gotB = [];
402
+ await bus.subscribe("mail.scan.alpha", function (p) { gotA.push(p); });
403
+ await bus.subscribe("mail.scan.beta", function (p) { gotB.push(p); });
404
+
405
+ await bus.publish("mail.scan.alpha", { source: "from-a" }, { actor: { id: "p1" } });
406
+ await helpers.waitUntil(function () { return gotA.length >= 1; }, {
407
+ timeoutMs: 5000, label: "cross-topic: baseline A delivery",
408
+ });
409
+ var genuineA = pubsub._lastEnvelope();
410
+ check("genuine A envelope carries _topic=mail.scan.alpha + a MAC",
411
+ genuineA._topic === "mail.scan.alpha" && typeof genuineA._mac === "string");
412
+
413
+ // Replay the verbatim, MAC-valid A envelope onto B's channel.
414
+ pubsub._deliver("mail.scan.beta", JSON.parse(JSON.stringify(genuineA)));
415
+ await helpers.passiveObserve(40, "cross-topic: A-event replayed onto B must NOT reach B's handler");
416
+ check("cross-topic replay (A envelope on B channel) is dropped", gotB.length === 0);
417
+ check("the legitimate A subscriber still got exactly its one event", gotA.length === 1);
418
+ } finally {
419
+ helpers.teardownVaultOnly(tmpDir);
420
+ }
421
+ }
422
+
388
423
  async function testEnvelopeMacPublishFailsClosedWithoutVault() {
389
424
  // requireMac default ON + no vault → publish refuses (fail-closed)
390
425
  // rather than emitting an unauthenticatable envelope.
@@ -419,6 +454,7 @@ async function run() {
419
454
  await testEnvelopeMacForgedRefused();
420
455
  await testEnvelopeMacHonestDelivered();
421
456
  await testEnvelopeMacTamperFails();
457
+ await testCrossTopicReplayDropped();
422
458
  await testEnvelopeMacPublishFailsClosedWithoutVault();
423
459
  }
424
460
 
@@ -481,6 +481,52 @@ async function runReseal() {
481
481
  }
482
482
  }
483
483
 
484
+ async function testAllowPlaintextRefusedUnderRegulatedPosture() {
485
+ // #112 — allowPlaintext claimed to be "refused under hipaa/pci-dss/gdpr/soc2"
486
+ // but had NO posture check: persist wrote a plaintext body and load waived
487
+ // signature verification (CWE-347) under a regulated posture. The dev escape
488
+ // hatch must actually refuse under a regulated posture, at BOTH boundaries.
489
+ var backend = _fakeBackend();
490
+ try {
491
+ b.compliance.clear();
492
+ b.compliance.set("hipaa");
493
+
494
+ // (A) persist must refuse the no-sealer plaintext path under hipaa.
495
+ var plain = b.agent.snapshot.create({
496
+ orchestrator: _fakeOrch(),
497
+ backend: backend,
498
+ signer: _fakeSigner(), // signer wired; sealer omitted -> plaintext body
499
+ allowPlaintext: true,
500
+ });
501
+ var sA = await plain.takeSnapshot({});
502
+ await expectRejection("persist allowPlaintext refused under hipaa",
503
+ plain.persist(sA), "agent-snapshot/plaintext-refused-under-posture");
504
+
505
+ // (B) load must NOT waive verify for an attacker-written unsigned snapshot
506
+ // under hipaa (the CWE-347 path).
507
+ var signed = _signedSnapshot({ backend: backend });
508
+ var sB = await signed.takeSnapshot({});
509
+ await backend.put(sB.snapshotId, Object.assign({}, sB, { sig: null }));
510
+ var plainLoader = b.agent.snapshot.create({
511
+ orchestrator: _fakeOrch(), backend: backend, signer: _fakeSigner(), allowPlaintext: true,
512
+ });
513
+ await expectRejection("load unsigned NOT waived under hipaa",
514
+ plainLoader.loadById(sB.snapshotId), "agent-snapshot/plaintext-refused-under-posture");
515
+ } finally {
516
+ b.compliance.clear();
517
+ }
518
+
519
+ // (C) control — with no regulated posture (dev), the escape hatch still works.
520
+ var devBackend = _fakeBackend();
521
+ var dev = b.agent.snapshot.create({
522
+ orchestrator: _fakeOrch(), backend: devBackend, signer: _fakeSigner(), allowPlaintext: true,
523
+ });
524
+ var sC = await dev.takeSnapshot({});
525
+ var persisted = await dev.persist(sC);
526
+ check("dev allowPlaintext persists without a regulated posture",
527
+ persisted && typeof persisted.snapshotId === "string");
528
+ }
529
+
484
530
  async function run() {
485
531
  testSurface();
486
532
  await testCreateRequiresOrchestrator();
@@ -491,6 +537,7 @@ async function run() {
491
537
  await testLoadById();
492
538
  await testTamperedEnvelopeRefused();
493
539
  await testUnsignedEnvelopeRefused();
540
+ await testAllowPlaintextRefusedUnderRegulatedPosture();
494
541
  await testRestoreTopologyChange();
495
542
  await testRefuseOnTopologyChange();
496
543
  await testSchemaVersionMismatch();
@@ -54,6 +54,29 @@ async function testGzBombCapRefused() {
54
54
  refused !== null);
55
55
  }
56
56
 
57
+ async function testGzRandomAccessSourceCapped() {
58
+ // The random-access branch must cap the COMPRESSED read before
59
+ // adapter.range(0, size) allocates the whole file — a hostile .gz (or an
60
+ // objectStore/HTTP source advertising a huge size) is an OOM lever.
61
+ // maxDecompressedBytes bounds the compressed read too. Use incompressible
62
+ // bytes so compressed.length stays well above the small cap.
63
+ var src = require("node:crypto").randomBytes(8192);
64
+ var compressed = b.archive.gz(src).toBuffer(); // ~8 KiB (random → no compression)
65
+ var refused = null;
66
+ try {
67
+ await b.archive.read.gz(b.archive.adapters.buffer(compressed), {
68
+ maxDecompressedBytes: 1024,
69
+ }).toBuffer();
70
+ } catch (e) { refused = e; }
71
+ check("archive.read.gz: random-access source over the read cap refused (OOM defense)",
72
+ refused && /source-too-large/.test(refused.code || refused.message));
73
+ // A generous cap still reads it (no over-rejection).
74
+ var ok = await b.archive.read.gz(b.archive.adapters.buffer(compressed), {
75
+ maxDecompressedBytes: 64 * 1024 * 1024,
76
+ }).toBuffer();
77
+ check("archive.read.gz: still reads within the cap", ok.equals(src));
78
+ }
79
+
57
80
  async function testTarToGzip() {
58
81
  var t = b.archive.tar();
59
82
  t.addFile("payload.txt", "hello world");
@@ -144,6 +167,7 @@ async function run() {
144
167
  await testGzRoundTrip();
145
168
  await testGzBadMagicRefused();
146
169
  await testGzBombCapRefused();
170
+ await testGzRandomAccessSourceCapped();
147
171
  await testTarToGzip();
148
172
  await testBackupTarGzRoundTrip();
149
173
  await testBackupTarGzHighRatioRoundTrip();
@@ -0,0 +1,160 @@
1
+ "use strict";
2
+ /**
3
+ * b.archive.read.tar hardening — uncapped random-access read + PAX `size`
4
+ * NaN walker-desync.
5
+ *
6
+ * - random-access cap: the random-access adapter branch read the WHOLE
7
+ * source with `adapter.range(0, size)` and no ceiling — a multi-GiB
8
+ * source is an OOM lever. It now refuses a source larger than the bomb
9
+ * policy's maxTotalDecompressedBytes.
10
+ * - PAX size NaN: a PAX extended header carrying a non-numeric `size`
11
+ * ("abc") was `parseInt`'d to NaN, which silently bypassed the
12
+ * entry-size bomb check (`NaN > max` is false) AND desynced the 512-byte
13
+ * block walker (`Math.ceil(NaN / 512)` is NaN). It now refuses a
14
+ * malformed PAX size.
15
+ */
16
+
17
+ var helpers = require("../helpers");
18
+ var check = helpers.check;
19
+ var b = helpers.b;
20
+
21
+ var BLOCK = 512;
22
+
23
+ // Minimal ustar header builder with a valid POSIX checksum (sum of all 512
24
+ // bytes with the chksum field treated as 8 spaces). Enough for the reader's
25
+ // _parseHeader (magic + chksum validated).
26
+ function _octal(n, width) {
27
+ var s = n.toString(8);
28
+ while (s.length < width - 1) s = "0" + s;
29
+ return s; // caller pads with NUL
30
+ }
31
+ function _header(name, size, typeflag) {
32
+ var buf = Buffer.alloc(BLOCK, 0);
33
+ buf.write(name, 0, Math.min(name.length, 100), "ascii");
34
+ buf.write("0000644\0", 100, 8, "ascii"); // mode
35
+ buf.write("0000000\0", 108, 8, "ascii"); // uid
36
+ buf.write("0000000\0", 116, 8, "ascii"); // gid
37
+ buf.write(_octal(size, 12) + "\0", 124, 12, "ascii"); // size (11 octal + NUL)
38
+ buf.write("00000000000\0", 136, 12, "ascii"); // mtime
39
+ buf.write(typeflag, 156, 1, "ascii"); // typeflag
40
+ buf.write("ustar\0", 257, 6, "ascii"); // magic
41
+ buf.write("00", 263, 2, "ascii"); // version
42
+ // checksum: chksum field = 8 spaces while summing
43
+ for (var i = 148; i < 156; i++) buf[i] = 0x20;
44
+ var sum = 0;
45
+ for (var j = 0; j < BLOCK; j++) sum += buf[j];
46
+ var oct = sum.toString(8);
47
+ while (oct.length < 6) oct = "0" + oct;
48
+ buf.write(oct + "\0 ", 148, 8, "ascii"); // 6 octal + NUL + space
49
+ return buf;
50
+ }
51
+ function _pad512(buf) {
52
+ var rem = buf.length % BLOCK;
53
+ return rem === 0 ? buf : Buffer.concat([buf, Buffer.alloc(BLOCK - rem, 0)]);
54
+ }
55
+ // Build a tar whose first entry is a PAX extended header (typeflag 'x')
56
+ // carrying `size=<paxSizeValue>`, followed by a real file entry.
57
+ function _paxTar(paxSizeValue, fileBody) {
58
+ var rec = " size=" + paxSizeValue + "\n";
59
+ // PAX record = "<total-length> size=<value>\n"; total counts its own digits.
60
+ var total = rec.length;
61
+ while (String(total).length + rec.length !== total) total = String(total).length + rec.length;
62
+ var paxBody = Buffer.from(String(total) + rec, "ascii");
63
+ var paxHdr = _header("PaxHeader/file.txt", paxBody.length, "x");
64
+ var fileHdr = _header("file.txt", fileBody.length, "0");
65
+ return Buffer.concat([
66
+ paxHdr, _pad512(paxBody),
67
+ fileHdr, _pad512(Buffer.from(fileBody)),
68
+ Buffer.alloc(BLOCK * 2, 0), // end-of-archive
69
+ ]);
70
+ }
71
+
72
+ function _codeOf(p) {
73
+ return p.then(function () { return null; }, function (e) { return (e && e.code) || (e && e.message) || "threw"; });
74
+ }
75
+
76
+ async function testRandomAccessReadIsCapped() {
77
+ var t = b.archive.tar();
78
+ t.addFile("a.txt", "hello world payload that is comfortably over a hundred bytes long ............................");
79
+ var bytes = t.toBuffer(); // ~2 KiB, random-access buffer adapter
80
+ // A tiny bomb policy → the 2 KiB source exceeds the read cap → refuse.
81
+ var code = await _codeOf(
82
+ b.archive.read.tar(b.archive.adapters.buffer(bytes), {
83
+ bombPolicy: { maxTotalDecompressedBytes: 100 },
84
+ }).inspect());
85
+ check("read.tar refuses a random-access source over the read cap (OOM defense)",
86
+ code === "archive-tar/source-too-large");
87
+
88
+ // A generous cap still reads it (no over-rejection).
89
+ var entries = await b.archive.read.tar(b.archive.adapters.buffer(bytes), {
90
+ bombPolicy: { maxTotalDecompressedBytes: 64 * 1024 * 1024 },
91
+ }).inspect();
92
+ check("read.tar still reads within the cap", entries.length === 1);
93
+ }
94
+
95
+ async function testPaxMalformedSizeRefused() {
96
+ // Truthy-but-non-integer PAX size values that reach the parser (an empty
97
+ // string is correctly treated as "no override" by the `if (pax.size)` guard).
98
+ var malformed = ["abc", "1e9", "100abc", "-5"];
99
+ for (var i = 0; i < malformed.length; i++) {
100
+ var tar = _paxTar(malformed[i], "x");
101
+ var code = await _codeOf(b.archive.read.tar(b.archive.adapters.buffer(tar)).inspect());
102
+ check("read.tar refuses malformed PAX size " + JSON.stringify(malformed[i]) +
103
+ " (no NaN bomb-bypass / walker desync)", code === "archive-tar/bad-pax-size");
104
+ }
105
+ }
106
+
107
+ async function testPaxValidSizeAccepted() {
108
+ // A well-formed PAX size override is honored (no over-rejection).
109
+ var tar = _paxTar("1", "x");
110
+ var entries = await b.archive.read.tar(b.archive.adapters.buffer(tar)).inspect();
111
+ check("read.tar accepts a valid PAX size override", entries.length >= 1);
112
+ }
113
+
114
+ // Build a tar whose first entry is a PAX extended header ('x') declaring a
115
+ // LARGE body size, followed by a real file. The per-entry decompressed-bytes
116
+ // cap previously only guarded regular entries (the check sat after the PAX
117
+ // `continue`), so a PAX header body escaped it.
118
+ function _largePaxBodyTar(bodyLen) {
119
+ var paxBody = Buffer.alloc(bodyLen, 0x41); // content irrelevant — cap fires before parse
120
+ var paxHdr = _header("PaxHeader/file.txt", paxBody.length, "x");
121
+ var fileHdr = _header("file.txt", 1, "0");
122
+ return Buffer.concat([
123
+ paxHdr, _pad512(paxBody),
124
+ fileHdr, _pad512(Buffer.from("x")),
125
+ Buffer.alloc(BLOCK * 2, 0),
126
+ ]);
127
+ }
128
+
129
+ async function testPaxBodyRespectsPerEntryCap() {
130
+ // A 4 KiB PAX header body under a 1 KiB per-entry cap must be refused — it
131
+ // previously bypassed maxEntryDecompressedBytes (which a regular 4 KiB entry
132
+ // would trip), forcing an over-cap string + record-Object materialization.
133
+ var tar = _largePaxBodyTar(4096);
134
+ var code = await _codeOf(b.archive.read.tar(b.archive.adapters.buffer(tar), {
135
+ bombPolicy: { maxEntryDecompressedBytes: 1024 },
136
+ }).inspect());
137
+ check("read.tar caps an oversized PAX header body (no per-entry-cap bypass)",
138
+ code === "archive-tar/entry-too-large");
139
+
140
+ // A small PAX body under the cap still parses (no over-rejection).
141
+ var okTar = _paxTar("1", "x");
142
+ var entries = await b.archive.read.tar(b.archive.adapters.buffer(okTar), {
143
+ bombPolicy: { maxEntryDecompressedBytes: 1024 },
144
+ }).inspect();
145
+ check("read.tar still accepts a small PAX body under the cap", entries.length >= 1);
146
+ }
147
+
148
+ async function run() {
149
+ await testRandomAccessReadIsCapped();
150
+ await testPaxMalformedSizeRefused();
151
+ await testPaxValidSizeAccepted();
152
+ await testPaxBodyRespectsPerEntryCap();
153
+ }
154
+
155
+ module.exports = { run: run };
156
+
157
+ if (require.main === module) {
158
+ run().then(function () { console.log("OK"); })
159
+ .catch(function (e) { console.error(e.stack || e); process.exit(1); });
160
+ }
@@ -89,9 +89,54 @@ function testUnwrapExplicit() {
89
89
  asn1.readUnsignedInt(inner) === 7);
90
90
  }
91
91
 
92
+ function testOidFirstSubidMultibyte() {
93
+ // OID 2.999 — first subidentifier = 40*2 + 999 = 1079, which is 0x88 0x37
94
+ // in base-128 (DER 06 02 88 37). A single-byte first-subid decoder reads
95
+ // it as "2.56.55"; a single-byte encoder truncates "2.999" to one octet
96
+ // and round-trips to "1.15".
97
+ var der = Buffer.from([0x06, 0x02, 0x88, 0x37]);
98
+ check("readOid: 2.999 (multi-byte first subidentifier) decodes correctly",
99
+ asn1.readOid(asn1.readNode(der)) === "2.999");
100
+ var enc = asn1.writeOid("2.999");
101
+ check("writeOid: 2.999 round-trips through readOid",
102
+ asn1.readOid(asn1.readNode(enc)) === "2.999");
103
+ check("writeOid: 2.999 emits the canonical 06 02 88 37",
104
+ Buffer.compare(enc, der) === 0);
105
+ }
106
+
107
+ function testOidNonMinimalRejected() {
108
+ // 06 04 2a 80 86 48 — the third subidentifier (840) carries a leading
109
+ // 0x80 continuation octet, which X.690 §8.19.2 forbids in DER. It would
110
+ // otherwise alias "1.2.840".
111
+ var der = Buffer.from([0x06, 0x04, 0x2a, 0x80, 0x86, 0x48]);
112
+ var threw = null;
113
+ try { asn1.readOid(asn1.readNode(der)); } catch (e) { threw = e; }
114
+ check("readOid: non-minimal (leading 0x80) subidentifier is refused",
115
+ threw && threw.code === "asn1/oid-non-minimal");
116
+ }
117
+
118
+ function testHighTagNumberNoOverflow() {
119
+ // 1f ff ff ff ff 7f 00 — high-tag-number form, five 7-bit octets =
120
+ // 0x7ffffffff = 34359738367. A `tag << 7` accumulator overflows 32-bit
121
+ // signed int and yields a negative/wrong tag; base-128 multiplication
122
+ // keeps it correct.
123
+ var node = asn1.readNode(Buffer.from([0x1f, 0xff, 0xff, 0xff, 0xff, 0x7f, 0x00]));
124
+ check("readNode: high-tag-number does not overflow to a negative tag",
125
+ node.tag === 34359738367);
126
+ // A genuinely oversized tag (well past 2^53) must be refused, not wrap.
127
+ var huge = Buffer.from([0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0x00]);
128
+ var threw = null;
129
+ try { asn1.readNode(huge); } catch (e) { threw = e; }
130
+ check("readNode: tag past MAX_SAFE_INTEGER is refused",
131
+ threw && threw.code === "asn1/tag-too-large");
132
+ }
133
+
92
134
  async function run() {
93
135
  testReadNodeShortFormLength();
94
136
  testReadOid();
137
+ testOidFirstSubidMultibyte();
138
+ testOidNonMinimalRejected();
139
+ testHighTagNumberNoOverflow();
95
140
  testReadSequence();
96
141
  testReadOctetString();
97
142
  testReadBitString();
@@ -40,6 +40,35 @@ function testReadsBufferAndString() {
40
40
  } finally { dir.cleanup(); }
41
41
  }
42
42
 
43
+ function testWithStat() {
44
+ // #341: withStat returns the bound fd's fstat alongside the bytes, so the
45
+ // mode/owner describe the exact inode read (TOCTOU-free), not a re-stat.
46
+ var dir = _dir();
47
+ try {
48
+ var p = path.join(dir.path, "secret.bin");
49
+ var payload = Buffer.from("with-stat payload", "utf8");
50
+ fs.writeFileSync(p, payload, { mode: 0o600 });
51
+ var r = b.atomicFile.fdSafeReadSync(p, { withStat: true });
52
+ check("fdSafeReadSync withStat: returns { bytes, stat }",
53
+ r && Buffer.isBuffer(r.bytes) && r.bytes.equals(payload) && r.stat && typeof r.stat === "object");
54
+ check("fdSafeReadSync withStat: stat.size matches bytes", r.stat.size === payload.length);
55
+ check("fdSafeReadSync withStat: stat carries mode + ino",
56
+ typeof r.stat.mode === "number" && typeof r.stat.ino === "number");
57
+ // The mode is the bound fd's — on POSIX a 0o600 secret has no group/other
58
+ // bits. Windows doesn't enforce Unix permission bits, so gate the assertion.
59
+ if (process.platform !== "win32") {
60
+ check("fdSafeReadSync withStat: mode reflects 0o600 (no group/other)", (r.stat.mode & 0o077) === 0);
61
+ }
62
+ // withStat composes with encoding: bytes is the decoded string.
63
+ var rEnc = b.atomicFile.fdSafeReadSync(p, { withStat: true, encoding: "utf8" });
64
+ check("fdSafeReadSync withStat + encoding: bytes is a string",
65
+ rEnc.bytes === payload.toString("utf8") && rEnc.stat.size === payload.length);
66
+ // Without withStat, the bare value is still returned (back-compat).
67
+ var bare = b.atomicFile.fdSafeReadSync(p);
68
+ check("fdSafeReadSync: default still returns the bare Buffer", Buffer.isBuffer(bare));
69
+ } finally { dir.cleanup(); }
70
+ }
71
+
43
72
  function testMaxBytesCap() {
44
73
  var dir = _dir();
45
74
  try {
@@ -134,6 +163,7 @@ function testRefuseSymlinkAndInodeHappyPath() {
134
163
 
135
164
  async function run() {
136
165
  testReadsBufferAndString();
166
+ testWithStat();
137
167
  testMaxBytesCap();
138
168
  testExpectedHash();
139
169
  testEnoent();
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ // b.atomicFile.openNoFollowSync — O_NOFOLLOW read-only open for streaming reads
3
+ // that cannot buffer (static-serve range / SRI hashing, object-store download).
4
+ // Opens a path read-only and refuses a symlink at the final component (ELOOP)
5
+ // instead of following it — the streaming counterpart to fdSafeReadSync's
6
+ // refuseSymlink. POSIX-only; on a platform without O_NOFOLLOW the flag is 0.
7
+
8
+ var fs = require("node:fs");
9
+ var os = require("node:os");
10
+ var path = require("node:path");
11
+ var helpers = require("../helpers");
12
+ var b = helpers.b;
13
+ var check = helpers.check;
14
+
15
+ function run() {
16
+ check("b.atomicFile.openNoFollowSync is a function",
17
+ typeof b.atomicFile.openNoFollowSync === "function");
18
+
19
+ var dir = fs.mkdtempSync(path.join(os.tmpdir(), "blamejs-nofollow-"));
20
+ try {
21
+ var real = path.join(dir, "real.txt");
22
+ fs.writeFileSync(real, "payload-bytes");
23
+
24
+ // Regular file: opens, returns a numeric fd, reads the bytes, closes.
25
+ var fd = b.atomicFile.openNoFollowSync(real);
26
+ check("returns a numeric fd for a regular file", typeof fd === "number" && fd >= 0);
27
+ var buf = Buffer.alloc(64);
28
+ var n = fs.readSync(fd, buf, 0, buf.length, 0);
29
+ fs.closeSync(fd);
30
+ check("the fd reads the real file's bytes", buf.slice(0, n).toString("utf8") === "payload-bytes");
31
+
32
+ // Stream from the fd (the intended consumer shape).
33
+ var streamed = "";
34
+ // (synchronous read above already proves the fd; the stream shape is
35
+ // exercised by static-serve / object-store integration — here just assert
36
+ // the fd is consumable by createReadStream without double-open.)
37
+ var fd2 = b.atomicFile.openNoFollowSync(real);
38
+ var rs = fs.createReadStream(real, { fd: fd2 });
39
+ rs.on("data", function (c) { streamed += c.toString("utf8"); });
40
+ // Missing file → ENOENT (propagated raw).
41
+ var enoentCode = null;
42
+ try { b.atomicFile.openNoFollowSync(path.join(dir, "nope.txt")); }
43
+ catch (e) { enoentCode = e && e.code; }
44
+ check("missing file throws ENOENT", enoentCode === "ENOENT");
45
+
46
+ // Symlink at the final component → refused with ELOOP (POSIX). On a
47
+ // platform without O_NOFOLLOW (Windows) the flag is 0 and the open follows;
48
+ // symlink creation also often requires privilege there, so gate the assertion.
49
+ if (process.platform !== "win32") {
50
+ var linkPath = path.join(dir, "link.txt");
51
+ var symlinkMade = false;
52
+ try { fs.symlinkSync(real, linkPath); symlinkMade = true; } catch (_e) { /* no symlink priv */ }
53
+ if (symlinkMade) {
54
+ var loopCode = null;
55
+ try { b.atomicFile.openNoFollowSync(linkPath); }
56
+ catch (e) { loopCode = e && e.code; }
57
+ check("symlink final component is refused (ELOOP, not followed)", loopCode === "ELOOP");
58
+ }
59
+ }
60
+
61
+ return new Promise(function (resolve) {
62
+ rs.on("end", function () {
63
+ check("an fd from openNoFollowSync is consumable by createReadStream",
64
+ streamed === "payload-bytes");
65
+ resolve();
66
+ });
67
+ });
68
+ } finally {
69
+ try { fs.rmSync(dir, { recursive: true, force: true }); } catch (_e) { /* best-effort */ }
70
+ }
71
+ }
72
+
73
+ module.exports = { run: run };
74
+ if (require.main === module) {
75
+ Promise.resolve(run()).then(
76
+ function () { console.log("OK — atomicFile.openNoFollowSync (" + helpers.getChecks() + " checks)"); process.exit(0); },
77
+ function (e) { console.error("FAIL: " + (e && e.message)); process.exit(1); }
78
+ );
79
+ }