@blamejs/blamejs-shop 0.4.30 → 0.4.32

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 (338) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/lib/asset-manifest.json +1 -1
  3. package/lib/checkout.js +8 -0
  4. package/lib/order.js +71 -11
  5. package/lib/vendor/MANIFEST.json +392 -278
  6. package/lib/vendor/blamejs/.github/workflows/ci.yml +34 -3
  7. package/lib/vendor/blamejs/.github/workflows/npm-publish.yml +21 -4
  8. package/lib/vendor/blamejs/.gitignore +6 -0
  9. package/lib/vendor/blamejs/CHANGELOG.md +26 -0
  10. package/lib/vendor/blamejs/MIGRATING.md +43 -0
  11. package/lib/vendor/blamejs/README.md +8 -6
  12. package/lib/vendor/blamejs/SECURITY.md +19 -3
  13. package/lib/vendor/blamejs/api-snapshot.json +2190 -664
  14. package/lib/vendor/blamejs/docker/caddy/localstack.Caddyfile +19 -0
  15. package/lib/vendor/blamejs/docker/init/generate-certs.sh +1 -1
  16. package/lib/vendor/blamejs/docker/otel/config.yaml +42 -0
  17. package/lib/vendor/blamejs/docker/otel/export/.gitkeep +0 -0
  18. package/lib/vendor/blamejs/docker/postgres/initdb/10-replication.sh +15 -0
  19. package/lib/vendor/blamejs/docker/postgres/replica-entrypoint.sh +38 -0
  20. package/lib/vendor/blamejs/docker/toxiproxy/toxiproxy.json +14 -0
  21. package/lib/vendor/blamejs/docker-compose.test.yml +209 -0
  22. package/lib/vendor/blamejs/examples/wiki/lib/page-generator.js +132 -0
  23. package/lib/vendor/blamejs/examples/wiki/lib/source-comment-block-validator.js +221 -61
  24. package/lib/vendor/blamejs/examples/wiki/lib/source-doc-parser.js +144 -9
  25. package/lib/vendor/blamejs/examples/wiki/test/e2e.js +99 -0
  26. package/lib/vendor/blamejs/fuzz/guard-sql.fuzz.js +36 -0
  27. package/lib/vendor/blamejs/index.js +4 -0
  28. package/lib/vendor/blamejs/lib/agent-envelope-mac.js +104 -0
  29. package/lib/vendor/blamejs/lib/agent-event-bus.js +105 -4
  30. package/lib/vendor/blamejs/lib/agent-posture-chain.js +8 -42
  31. package/lib/vendor/blamejs/lib/ai-content-detect.js +9 -10
  32. package/lib/vendor/blamejs/lib/api-key.js +158 -77
  33. package/lib/vendor/blamejs/lib/atomic-file.js +62 -4
  34. package/lib/vendor/blamejs/lib/audit-chain.js +47 -11
  35. package/lib/vendor/blamejs/lib/audit-sign.js +77 -2
  36. package/lib/vendor/blamejs/lib/audit-tools.js +79 -51
  37. package/lib/vendor/blamejs/lib/audit.js +259 -123
  38. package/lib/vendor/blamejs/lib/auth/oauth.js +53 -9
  39. package/lib/vendor/blamejs/lib/auth/openid-federation.js +108 -47
  40. package/lib/vendor/blamejs/lib/auth/saml.js +6 -8
  41. package/lib/vendor/blamejs/lib/auth/sd-jwt-vc.js +31 -5
  42. package/lib/vendor/blamejs/lib/backup/index.js +45 -10
  43. package/lib/vendor/blamejs/lib/break-glass.js +355 -147
  44. package/lib/vendor/blamejs/lib/cache.js +174 -105
  45. package/lib/vendor/blamejs/lib/chain-writer.js +38 -16
  46. package/lib/vendor/blamejs/lib/cli.js +19 -14
  47. package/lib/vendor/blamejs/lib/cluster-provider-db.js +130 -104
  48. package/lib/vendor/blamejs/lib/cluster-storage.js +119 -22
  49. package/lib/vendor/blamejs/lib/cluster.js +119 -71
  50. package/lib/vendor/blamejs/lib/codepoint-class.js +23 -0
  51. package/lib/vendor/blamejs/lib/compliance.js +206 -4
  52. package/lib/vendor/blamejs/lib/consent.js +82 -29
  53. package/lib/vendor/blamejs/lib/constants.js +27 -11
  54. package/lib/vendor/blamejs/lib/crypto-field.js +916 -156
  55. package/lib/vendor/blamejs/lib/db-declare-row-policy.js +35 -22
  56. package/lib/vendor/blamejs/lib/db-file-lifecycle.js +3 -2
  57. package/lib/vendor/blamejs/lib/db-query.js +882 -260
  58. package/lib/vendor/blamejs/lib/db-schema.js +228 -44
  59. package/lib/vendor/blamejs/lib/db.js +249 -99
  60. package/lib/vendor/blamejs/lib/dsr.js +385 -55
  61. package/lib/vendor/blamejs/lib/error-page.js +14 -1
  62. package/lib/vendor/blamejs/lib/external-db-migrate.js +239 -137
  63. package/lib/vendor/blamejs/lib/external-db.js +549 -34
  64. package/lib/vendor/blamejs/lib/file-upload.js +52 -7
  65. package/lib/vendor/blamejs/lib/framework-error.js +20 -1
  66. package/lib/vendor/blamejs/lib/framework-files.js +73 -0
  67. package/lib/vendor/blamejs/lib/framework-schema.js +695 -394
  68. package/lib/vendor/blamejs/lib/gate-contract.js +659 -1
  69. package/lib/vendor/blamejs/lib/guard-agent-registry.js +26 -44
  70. package/lib/vendor/blamejs/lib/guard-all.js +1 -0
  71. package/lib/vendor/blamejs/lib/guard-auth.js +42 -112
  72. package/lib/vendor/blamejs/lib/guard-cidr.js +33 -154
  73. package/lib/vendor/blamejs/lib/guard-csv.js +46 -113
  74. package/lib/vendor/blamejs/lib/guard-domain.js +34 -157
  75. package/lib/vendor/blamejs/lib/guard-dsn.js +27 -43
  76. package/lib/vendor/blamejs/lib/guard-email.js +47 -69
  77. package/lib/vendor/blamejs/lib/guard-envelope.js +19 -32
  78. package/lib/vendor/blamejs/lib/guard-event-bus-payload.js +24 -42
  79. package/lib/vendor/blamejs/lib/guard-event-bus-topic.js +25 -43
  80. package/lib/vendor/blamejs/lib/guard-filename.js +42 -106
  81. package/lib/vendor/blamejs/lib/guard-graphql.js +42 -123
  82. package/lib/vendor/blamejs/lib/guard-html.js +53 -108
  83. package/lib/vendor/blamejs/lib/guard-idempotency-key.js +24 -42
  84. package/lib/vendor/blamejs/lib/guard-image.js +46 -103
  85. package/lib/vendor/blamejs/lib/guard-imap-command.js +18 -32
  86. package/lib/vendor/blamejs/lib/guard-jmap.js +16 -30
  87. package/lib/vendor/blamejs/lib/guard-json.js +38 -108
  88. package/lib/vendor/blamejs/lib/guard-jsonpath.js +38 -171
  89. package/lib/vendor/blamejs/lib/guard-jwt.js +49 -179
  90. package/lib/vendor/blamejs/lib/guard-list-id.js +25 -41
  91. package/lib/vendor/blamejs/lib/guard-list-unsubscribe.js +27 -43
  92. package/lib/vendor/blamejs/lib/guard-mail-compose.js +24 -42
  93. package/lib/vendor/blamejs/lib/guard-mail-move.js +26 -44
  94. package/lib/vendor/blamejs/lib/guard-mail-query.js +28 -46
  95. package/lib/vendor/blamejs/lib/guard-mail-reply.js +24 -42
  96. package/lib/vendor/blamejs/lib/guard-mail-sieve.js +24 -42
  97. package/lib/vendor/blamejs/lib/guard-managesieve-command.js +17 -31
  98. package/lib/vendor/blamejs/lib/guard-markdown.js +37 -104
  99. package/lib/vendor/blamejs/lib/guard-message-id.js +26 -45
  100. package/lib/vendor/blamejs/lib/guard-mime.js +39 -151
  101. package/lib/vendor/blamejs/lib/guard-oauth.js +54 -135
  102. package/lib/vendor/blamejs/lib/guard-pdf.js +45 -101
  103. package/lib/vendor/blamejs/lib/guard-pop3-command.js +21 -31
  104. package/lib/vendor/blamejs/lib/guard-posture-chain.js +24 -42
  105. package/lib/vendor/blamejs/lib/guard-regex.js +33 -107
  106. package/lib/vendor/blamejs/lib/guard-saga-config.js +24 -42
  107. package/lib/vendor/blamejs/lib/guard-shell.js +42 -172
  108. package/lib/vendor/blamejs/lib/guard-smtp-command.js +48 -54
  109. package/lib/vendor/blamejs/lib/guard-snapshot-envelope.js +24 -42
  110. package/lib/vendor/blamejs/lib/guard-sql.js +1491 -0
  111. package/lib/vendor/blamejs/lib/guard-stream-args.js +24 -43
  112. package/lib/vendor/blamejs/lib/guard-svg.js +47 -65
  113. package/lib/vendor/blamejs/lib/guard-template.js +35 -172
  114. package/lib/vendor/blamejs/lib/guard-tenant-id.js +26 -45
  115. package/lib/vendor/blamejs/lib/guard-time.js +32 -154
  116. package/lib/vendor/blamejs/lib/guard-trace-context.js +25 -44
  117. package/lib/vendor/blamejs/lib/guard-uuid.js +32 -153
  118. package/lib/vendor/blamejs/lib/guard-xml.js +38 -113
  119. package/lib/vendor/blamejs/lib/guard-yaml.js +51 -163
  120. package/lib/vendor/blamejs/lib/http-client.js +37 -9
  121. package/lib/vendor/blamejs/lib/inbox.js +120 -107
  122. package/lib/vendor/blamejs/lib/legal-hold.js +121 -50
  123. package/lib/vendor/blamejs/lib/log-stream-cloudwatch.js +47 -31
  124. package/lib/vendor/blamejs/lib/log-stream-otlp.js +32 -18
  125. package/lib/vendor/blamejs/lib/mail-auth.js +236 -0
  126. package/lib/vendor/blamejs/lib/mail-crypto-smime.js +2 -6
  127. package/lib/vendor/blamejs/lib/mail-dkim.js +1 -0
  128. package/lib/vendor/blamejs/lib/mail-greylist.js +2 -6
  129. package/lib/vendor/blamejs/lib/mail-helo.js +2 -6
  130. package/lib/vendor/blamejs/lib/mail-journal.js +85 -64
  131. package/lib/vendor/blamejs/lib/mail-rbl.js +2 -6
  132. package/lib/vendor/blamejs/lib/mail-scan.js +2 -6
  133. package/lib/vendor/blamejs/lib/mail-server-jmap.js +117 -12
  134. package/lib/vendor/blamejs/lib/mail-server-mx.js +276 -7
  135. package/lib/vendor/blamejs/lib/mail-spam-score.js +2 -6
  136. package/lib/vendor/blamejs/lib/mail-store.js +293 -154
  137. package/lib/vendor/blamejs/lib/mail.js +8 -4
  138. package/lib/vendor/blamejs/lib/middleware/body-parser.js +71 -25
  139. package/lib/vendor/blamejs/lib/middleware/csrf-protect.js +19 -8
  140. package/lib/vendor/blamejs/lib/middleware/dpop.js +10 -1
  141. package/lib/vendor/blamejs/lib/middleware/fetch-metadata.js +17 -7
  142. package/lib/vendor/blamejs/lib/middleware/idempotency-key.js +75 -51
  143. package/lib/vendor/blamejs/lib/middleware/rate-limit.js +102 -32
  144. package/lib/vendor/blamejs/lib/middleware/security-headers.js +21 -5
  145. package/lib/vendor/blamejs/lib/migrations.js +108 -66
  146. package/lib/vendor/blamejs/lib/network-heartbeat.js +7 -0
  147. package/lib/vendor/blamejs/lib/network-proxy.js +24 -1
  148. package/lib/vendor/blamejs/lib/nonce-store.js +31 -9
  149. package/lib/vendor/blamejs/lib/object-store/azure-blob-bucket-ops.js +9 -4
  150. package/lib/vendor/blamejs/lib/object-store/azure-blob.js +57 -3
  151. package/lib/vendor/blamejs/lib/object-store/gcs.js +4 -1
  152. package/lib/vendor/blamejs/lib/object-store/sigv4-bucket-ops.js +5 -2
  153. package/lib/vendor/blamejs/lib/object-store/sigv4.js +38 -6
  154. package/lib/vendor/blamejs/lib/observability-otlp-exporter.js +9 -1
  155. package/lib/vendor/blamejs/lib/observability.js +124 -0
  156. package/lib/vendor/blamejs/lib/otel-export.js +12 -3
  157. package/lib/vendor/blamejs/lib/outbox.js +184 -83
  158. package/lib/vendor/blamejs/lib/parsers/safe-xml.js +47 -7
  159. package/lib/vendor/blamejs/lib/pqc-agent.js +44 -0
  160. package/lib/vendor/blamejs/lib/pubsub-cluster.js +42 -20
  161. package/lib/vendor/blamejs/lib/queue-local.js +225 -140
  162. package/lib/vendor/blamejs/lib/queue-redis.js +9 -1
  163. package/lib/vendor/blamejs/lib/queue-sqs.js +6 -0
  164. package/lib/vendor/blamejs/lib/queue.js +7 -0
  165. package/lib/vendor/blamejs/lib/redact.js +68 -11
  166. package/lib/vendor/blamejs/lib/redis-client.js +160 -31
  167. package/lib/vendor/blamejs/lib/request-helpers.js +7 -0
  168. package/lib/vendor/blamejs/lib/retention.js +101 -40
  169. package/lib/vendor/blamejs/lib/router.js +212 -5
  170. package/lib/vendor/blamejs/lib/safe-dns.js +29 -45
  171. package/lib/vendor/blamejs/lib/safe-ical.js +18 -33
  172. package/lib/vendor/blamejs/lib/safe-icap.js +27 -43
  173. package/lib/vendor/blamejs/lib/safe-sieve.js +21 -40
  174. package/lib/vendor/blamejs/lib/safe-sql.js +212 -3
  175. package/lib/vendor/blamejs/lib/safe-url.js +170 -3
  176. package/lib/vendor/blamejs/lib/safe-vcard.js +18 -33
  177. package/lib/vendor/blamejs/lib/scheduler.js +35 -12
  178. package/lib/vendor/blamejs/lib/seeders.js +122 -74
  179. package/lib/vendor/blamejs/lib/session-stores.js +42 -14
  180. package/lib/vendor/blamejs/lib/session.js +175 -77
  181. package/lib/vendor/blamejs/lib/sql.js +3842 -0
  182. package/lib/vendor/blamejs/lib/sse.js +26 -0
  183. package/lib/vendor/blamejs/lib/ssrf-guard.js +151 -4
  184. package/lib/vendor/blamejs/lib/static.js +177 -34
  185. package/lib/vendor/blamejs/lib/subject.js +96 -49
  186. package/lib/vendor/blamejs/lib/vault/index.js +3 -2
  187. package/lib/vendor/blamejs/lib/vault/passphrase-ops.js +3 -2
  188. package/lib/vendor/blamejs/lib/vault/rotate.js +168 -108
  189. package/lib/vendor/blamejs/lib/vault-aad.js +6 -0
  190. package/lib/vendor/blamejs/lib/vendor-data.js +2 -0
  191. package/lib/vendor/blamejs/lib/websocket.js +35 -5
  192. package/lib/vendor/blamejs/lib/worker-pool.js +11 -0
  193. package/lib/vendor/blamejs/package.json +2 -2
  194. package/lib/vendor/blamejs/release-notes/v0.14.x.json +1503 -0
  195. package/lib/vendor/blamejs/release-notes/v0.15.0.json +77 -0
  196. package/lib/vendor/blamejs/release-notes/v0.15.1.json +22 -0
  197. package/lib/vendor/blamejs/release-notes/v0.15.2.json +22 -0
  198. package/lib/vendor/blamejs/release-notes/v0.15.3.json +39 -0
  199. package/lib/vendor/blamejs/release-notes/v0.15.4.json +39 -0
  200. package/lib/vendor/blamejs/release-notes/v0.15.5.json +22 -0
  201. package/lib/vendor/blamejs/release-notes/v0.15.6.json +59 -0
  202. package/lib/vendor/blamejs/scripts/check-services.js +21 -0
  203. package/lib/vendor/blamejs/scripts/gen-migrating.js +51 -0
  204. package/lib/vendor/blamejs/scripts/release.js +398 -38
  205. package/lib/vendor/blamejs/test/00-primitives.js +117 -0
  206. package/lib/vendor/blamejs/test/10-state.js +140 -14
  207. package/lib/vendor/blamejs/test/20-db.js +65 -2
  208. package/lib/vendor/blamejs/test/helpers/db.js +9 -0
  209. package/lib/vendor/blamejs/test/helpers/drivers.js +27 -15
  210. package/lib/vendor/blamejs/test/helpers/services.js +21 -0
  211. package/lib/vendor/blamejs/test/integration/audit-actor-binding-pg.test.js +246 -0
  212. package/lib/vendor/blamejs/test/integration/audit-chain-external-db.test.js +517 -0
  213. package/lib/vendor/blamejs/test/integration/audit-stack-mysql.test.js +639 -0
  214. package/lib/vendor/blamejs/test/integration/audit-stack-postgres.test.js +832 -0
  215. package/lib/vendor/blamejs/test/integration/backup-restore-objectstore.test.js +453 -0
  216. package/lib/vendor/blamejs/test/integration/data-layer-cluster-mysql.test.js +649 -0
  217. package/lib/vendor/blamejs/test/integration/data-layer-cluster-pg.test.js +770 -0
  218. package/lib/vendor/blamejs/test/integration/data-layer-mysql-privacy.test.js +630 -0
  219. package/lib/vendor/blamejs/test/integration/data-layer-mysql.test.js +610 -0
  220. package/lib/vendor/blamejs/test/integration/data-layer-pg.test.js +577 -0
  221. package/lib/vendor/blamejs/test/integration/data-layer-postgres.test.js +771 -0
  222. package/lib/vendor/blamejs/test/integration/db-layer-mysql.test.js +549 -0
  223. package/lib/vendor/blamejs/test/integration/db-layer-postgres.test.js +598 -0
  224. package/lib/vendor/blamejs/test/integration/distributed-scheduler-fencing-pg.test.js +602 -0
  225. package/lib/vendor/blamejs/test/integration/external-db-postgres.test.js +576 -0
  226. package/lib/vendor/blamejs/test/integration/framework-schema-mysql.test.js +353 -0
  227. package/lib/vendor/blamejs/test/integration/log-stream-cloudwatch.test.js +224 -0
  228. package/lib/vendor/blamejs/test/integration/mail-crypto-smime.test.js +142 -17
  229. package/lib/vendor/blamejs/test/integration/network-heartbeat.test.js +25 -10
  230. package/lib/vendor/blamejs/test/integration/object-store-azure.test.js +101 -0
  231. package/lib/vendor/blamejs/test/integration/object-store-gcs.test.js +239 -0
  232. package/lib/vendor/blamejs/test/integration/object-store-sigv4.test.js +35 -16
  233. package/lib/vendor/blamejs/test/integration/object-store-worm-lock.test.js +291 -0
  234. package/lib/vendor/blamejs/test/integration/pubsub.test.js +14 -0
  235. package/lib/vendor/blamejs/test/integration/queue-sqs.test.js +322 -0
  236. package/lib/vendor/blamejs/test/integration/redis-reconnect-toxiproxy.test.js +300 -0
  237. package/lib/vendor/blamejs/test/integration/sql-fts5-catalog-sqlite.test.js +154 -0
  238. package/lib/vendor/blamejs/test/integration/tls-classical-downgrade-audit.test.js +71 -0
  239. package/lib/vendor/blamejs/test/layer-0-primitives/agent-event-bus.test.js +175 -12
  240. package/lib/vendor/blamejs/test/layer-0-primitives/atomic-file-exclusive-temp.test.js +216 -0
  241. package/lib/vendor/blamejs/test/layer-0-primitives/audit-checkpoint-false-rollback.test.js +203 -0
  242. package/lib/vendor/blamejs/test/layer-0-primitives/audit-query-self-log.test.js +126 -0
  243. package/lib/vendor/blamejs/test/layer-0-primitives/audit-safeemit-redacts-secrets.test.js +196 -0
  244. package/lib/vendor/blamejs/test/layer-0-primitives/audit-signing-key-rotation.test.js +197 -0
  245. package/lib/vendor/blamejs/test/layer-0-primitives/audit-verifybundle-tamper.test.js +209 -0
  246. package/lib/vendor/blamejs/test/layer-0-primitives/azure-blob-key-encoding.test.js +121 -0
  247. package/lib/vendor/blamejs/test/layer-0-primitives/backup-residency-posture.test.js +168 -0
  248. package/lib/vendor/blamejs/test/layer-0-primitives/backup-scheduletest-drill.test.js +318 -0
  249. package/lib/vendor/blamejs/test/layer-0-primitives/break-glass.test.js +233 -7
  250. package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +1120 -14
  251. package/lib/vendor/blamejs/test/layer-0-primitives/compliance.test.js +229 -0
  252. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-derived-hash.test.js +24 -7
  253. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-dual-read-migrate.test.js +165 -0
  254. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-per-row-key.test.js +350 -0
  255. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-unseal-rate-cap.test.js +27 -9
  256. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-upgrade-dialect.test.js +76 -0
  257. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-interop-oracles.test.js +392 -0
  258. package/lib/vendor/blamejs/test/layer-0-primitives/csrf-protect.test.js +159 -0
  259. package/lib/vendor/blamejs/test/layer-0-primitives/db-column-gate.test.js +180 -1
  260. package/lib/vendor/blamejs/test/layer-0-primitives/db-query-cross-schema.test.js +5 -2
  261. package/lib/vendor/blamejs/test/layer-0-primitives/db-query-sealed-field-in.test.js +101 -0
  262. package/lib/vendor/blamejs/test/layer-0-primitives/db-raw-residency-gate.test.js +128 -0
  263. package/lib/vendor/blamejs/test/layer-0-primitives/db-schema-drift.test.js +38 -5
  264. package/lib/vendor/blamejs/test/layer-0-primitives/db-schema-reconcile-emittable.test.js +127 -0
  265. package/lib/vendor/blamejs/test/layer-0-primitives/db-stream-and-payload-shape.test.js +267 -0
  266. package/lib/vendor/blamejs/test/layer-0-primitives/db-worm.test.js +150 -0
  267. package/lib/vendor/blamejs/test/layer-0-primitives/defineguard-default-gate-posture-caps.test.js +30 -0
  268. package/lib/vendor/blamejs/test/layer-0-primitives/dpop-middleware-replaystore-required.test.js +46 -0
  269. package/lib/vendor/blamejs/test/layer-0-primitives/dsr.test.js +218 -0
  270. package/lib/vendor/blamejs/test/layer-0-primitives/erase-posture-vacuum.test.js +210 -0
  271. package/lib/vendor/blamejs/test/layer-0-primitives/external-db-hardening.test.js +4 -1
  272. package/lib/vendor/blamejs/test/layer-0-primitives/external-db-migrate.test.js +48 -2
  273. package/lib/vendor/blamejs/test/layer-0-primitives/federation-vc-suite.test.js +237 -5
  274. package/lib/vendor/blamejs/test/layer-0-primitives/fetch-metadata.test.js +20 -9
  275. package/lib/vendor/blamejs/test/layer-0-primitives/file-upload-content-safety-skip-audit.test.js +193 -0
  276. package/lib/vendor/blamejs/test/layer-0-primitives/guard-csv.test.js +90 -0
  277. package/lib/vendor/blamejs/test/layer-0-primitives/http-client-stream.test.js +85 -0
  278. package/lib/vendor/blamejs/test/layer-0-primitives/idempotency-key.test.js +10 -6
  279. package/lib/vendor/blamejs/test/layer-0-primitives/inbox.test.js +15 -4
  280. package/lib/vendor/blamejs/test/layer-0-primitives/legal-hold.test.js +146 -0
  281. package/lib/vendor/blamejs/test/layer-0-primitives/mail-auth.test.js +189 -0
  282. package/lib/vendor/blamejs/test/layer-0-primitives/mail-journal.test.js +3 -1
  283. package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-jmap.test.js +123 -4
  284. package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-mx.test.js +207 -2
  285. package/lib/vendor/blamejs/test/layer-0-primitives/mail-store.test.js +74 -0
  286. package/lib/vendor/blamejs/test/layer-0-primitives/oauth-callback.test.js +43 -0
  287. package/lib/vendor/blamejs/test/layer-0-primitives/otel-export.test.js +133 -0
  288. package/lib/vendor/blamejs/test/layer-0-primitives/otlp-attr-redaction.test.js +101 -0
  289. package/lib/vendor/blamejs/test/layer-0-primitives/outbox-inflight-reaper.test.js +136 -0
  290. package/lib/vendor/blamejs/test/layer-0-primitives/parsers-standalone.test.js +83 -0
  291. package/lib/vendor/blamejs/test/layer-0-primitives/passkey-real-vectors.test.js +429 -0
  292. package/lib/vendor/blamejs/test/layer-0-primitives/pqc-agent-curve.test.js +21 -11
  293. package/lib/vendor/blamejs/test/layer-0-primitives/queue-byo-db.test.js +40 -0
  294. package/lib/vendor/blamejs/test/layer-0-primitives/redact-dlp.test.js +83 -0
  295. package/lib/vendor/blamejs/test/layer-0-primitives/redis-client.test.js +113 -0
  296. package/lib/vendor/blamejs/test/layer-0-primitives/retention-dryrun-no-vacuum.test.js +99 -0
  297. package/lib/vendor/blamejs/test/layer-0-primitives/router-use-path-scope.test.js +255 -0
  298. package/lib/vendor/blamejs/test/layer-0-primitives/safe-url-canonicalize.test.js +309 -0
  299. package/lib/vendor/blamejs/test/layer-0-primitives/safe-xml.test.js +143 -0
  300. package/lib/vendor/blamejs/test/layer-0-primitives/saml-subjectconfirmation-notonorafter.test.js +287 -0
  301. package/lib/vendor/blamejs/test/layer-0-primitives/sd-jwt-vc-ecdsa-p1363.test.js +79 -0
  302. package/lib/vendor/blamejs/test/layer-0-primitives/sd-jwt-vc.test.js +50 -0
  303. package/lib/vendor/blamejs/test/layer-0-primitives/security-headers.test.js +31 -4
  304. package/lib/vendor/blamejs/test/layer-0-primitives/session-extensions.test.js +45 -0
  305. package/lib/vendor/blamejs/test/layer-0-primitives/sigv4-bucket-ops.test.js +49 -0
  306. package/lib/vendor/blamejs/test/layer-0-primitives/sql.test.js +595 -0
  307. package/lib/vendor/blamejs/test/layer-0-primitives/sse-backpressure.test.js +91 -0
  308. package/lib/vendor/blamejs/test/layer-0-primitives/ssrf-guard.test.js +69 -0
  309. package/lib/vendor/blamejs/test/layer-0-primitives/static.test.js +194 -2
  310. package/lib/vendor/blamejs/test/layer-0-primitives/websocket-extension-header.test.js +88 -0
  311. package/lib/vendor/blamejs/test/layer-0-primitives/worker-pool-recycle-race.test.js +66 -0
  312. package/lib/vendor/blamejs/test/layer-1-state/api-key.test.js +84 -0
  313. package/lib/vendor/blamejs/test/layer-5-integration/external-db-residency.test.js +638 -0
  314. package/lib/vendor/blamejs/test/layer-5-integration/guard-host-integration.test.js +21 -0
  315. package/lib/vendor/blamejs/test/smoke.js +79 -21
  316. package/package.json +1 -1
  317. package/lib/vendor/blamejs/release-notes/v0.14.0.json +0 -43
  318. package/lib/vendor/blamejs/release-notes/v0.14.1.json +0 -60
  319. package/lib/vendor/blamejs/release-notes/v0.14.10.json +0 -54
  320. package/lib/vendor/blamejs/release-notes/v0.14.11.json +0 -72
  321. package/lib/vendor/blamejs/release-notes/v0.14.12.json +0 -95
  322. package/lib/vendor/blamejs/release-notes/v0.14.13.json +0 -52
  323. package/lib/vendor/blamejs/release-notes/v0.14.14.json +0 -31
  324. package/lib/vendor/blamejs/release-notes/v0.14.16.json +0 -45
  325. package/lib/vendor/blamejs/release-notes/v0.14.17.json +0 -57
  326. package/lib/vendor/blamejs/release-notes/v0.14.18.json +0 -127
  327. package/lib/vendor/blamejs/release-notes/v0.14.19.json +0 -61
  328. package/lib/vendor/blamejs/release-notes/v0.14.2.json +0 -18
  329. package/lib/vendor/blamejs/release-notes/v0.14.20.json +0 -73
  330. package/lib/vendor/blamejs/release-notes/v0.14.21.json +0 -98
  331. package/lib/vendor/blamejs/release-notes/v0.14.22.json +0 -91
  332. package/lib/vendor/blamejs/release-notes/v0.14.3.json +0 -18
  333. package/lib/vendor/blamejs/release-notes/v0.14.4.json +0 -18
  334. package/lib/vendor/blamejs/release-notes/v0.14.5.json +0 -18
  335. package/lib/vendor/blamejs/release-notes/v0.14.6.json +0 -60
  336. package/lib/vendor/blamejs/release-notes/v0.14.7.json +0 -77
  337. package/lib/vendor/blamejs/release-notes/v0.14.8.json +0 -27
  338. package/lib/vendor/blamejs/release-notes/v0.14.9.json +0 -40
@@ -8,6 +8,9 @@
8
8
  * - downloadStream hash-mismatch refuses, deletes tmp, throws
9
9
  * httpclient/hash-mismatch, audits .refused
10
10
  * - downloadStream HTTP error surfaces without dest write
11
+ * - downloadStream stages into an exclusive, no-follow temp file: the
12
+ * happy path round-trips and a symlink at the dest is replaced (not
13
+ * followed) so the victim it pointed at is left untouched
11
14
  * - uploadMultipartStream POSTs file body via multipart/form-data,
12
15
  * server receives the bytes + the operator-supplied field
13
16
  * - uploadMultipartStream missing-file refuses at config time
@@ -216,6 +219,87 @@ async function testStreamErrorBodyPreserved() {
216
219
  });
217
220
  }
218
221
 
222
+ async function testDownloadTempCreateIsExclusiveNoFollow() {
223
+ // CWE-377 / CWE-59: downloadStream stages the body into a sibling temp
224
+ // file created with O_EXCL | O_NOFOLLOW before the atomic rename. Two
225
+ // properties to assert through the public API:
226
+ // (a) the happy path still round-trips (the exclusive create didn't
227
+ // break the streaming/rename contract);
228
+ // (b) a symlink planted at the DESTINATION is replaced by the rename,
229
+ // not followed — the victim the symlink pointed at stays untouched.
230
+ await _withServer(function (req, res) {
231
+ res.writeHead(200, { "Content-Type": "application/octet-stream" });
232
+ res.end(FIXTURE_BYTES);
233
+ }, async function (baseUrl) {
234
+ // (a) round-trip into a fresh dest, confirm exactly one final file and
235
+ // no leaked temp.
236
+ var dir = b.testing.tempDir("httpclient-stream-excl");
237
+ try {
238
+ var dest = path.join(dir.path, "release.bin");
239
+ var result = await b.httpClient.downloadStream({
240
+ url: baseUrl + "/payload",
241
+ dest: dest,
242
+ allowedProtocols: b.safeUrl.ALLOW_HTTP_ALL,
243
+ allowInternal: true,
244
+ });
245
+ check("downloadStream(excl): round-trips bytes",
246
+ result.bytesWritten === FIXTURE_BYTES.length &&
247
+ fs.readFileSync(dest).equals(FIXTURE_BYTES));
248
+ var leftovers = fs.readdirSync(dir.path).filter(function (n) {
249
+ return n.indexOf("release.bin.tmp-") === 0;
250
+ });
251
+ check("downloadStream(excl): no temp file leaked on success",
252
+ leftovers.length === 0);
253
+
254
+ // (b) symlink-at-destination — replaced by the rename, victim safe.
255
+ var victim = path.join(dir.path, "victim.bin");
256
+ fs.writeFileSync(victim, "DO NOT OVERWRITE", { mode: 0o600 });
257
+ var linkDest = path.join(dir.path, "link-dest.bin");
258
+ var symlinkOk = true;
259
+ try { fs.symlinkSync(victim, linkDest); }
260
+ catch (_e) { symlinkOk = false; } // Windows w/o symlink privilege
261
+
262
+ if (symlinkOk) {
263
+ await b.httpClient.downloadStream({
264
+ url: baseUrl + "/payload",
265
+ dest: linkDest,
266
+ allowedProtocols: b.safeUrl.ALLOW_HTTP_ALL,
267
+ allowInternal: true,
268
+ });
269
+ // Open ONE no-follow fd and take both the type check (fstat) and
270
+ // the byte read from that same descriptor — no lstat-then-read
271
+ // against the path, which would be a check-then-use file-system
272
+ // race (CWE-367). O_NOFOLLOW makes the open fail if linkDest were
273
+ // still a symlink, so a successful open already proves the rename
274
+ // replaced the link with a regular file.
275
+ var linkFd = fs.openSync(linkDest, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW || 0));
276
+ try {
277
+ var linkStat = fs.fstatSync(linkFd);
278
+ check("downloadStream(excl): symlink dest replaced by regular file",
279
+ linkStat.isFile() && !linkStat.isSymbolicLink());
280
+ var linkBytes = Buffer.alloc(linkStat.size);
281
+ var linkGot = 0;
282
+ while (linkGot < linkStat.size) {
283
+ var ln = fs.readSync(linkFd, linkBytes, linkGot, linkStat.size - linkGot, null);
284
+ if (ln === 0) break;
285
+ linkGot += ln;
286
+ }
287
+ check("downloadStream(excl): symlink dest holds downloaded bytes",
288
+ linkGot === FIXTURE_BYTES.length && linkBytes.equals(FIXTURE_BYTES));
289
+ } finally {
290
+ fs.closeSync(linkFd);
291
+ }
292
+ check("downloadStream(excl): symlink target (victim) untouched",
293
+ fs.readFileSync(victim, "utf8") === "DO NOT OVERWRITE");
294
+ } else {
295
+ check("downloadStream(excl): symlink-dest case skipped (no privilege)", true);
296
+ }
297
+ } finally {
298
+ dir.cleanup();
299
+ }
300
+ });
301
+ }
302
+
219
303
  async function testDownloadBadOpts() {
220
304
  var thrown = null;
221
305
  try {
@@ -319,6 +403,7 @@ async function run() {
319
403
  await testDownloadHashMismatch();
320
404
  await testDownloadHttpError();
321
405
  await testStreamErrorBodyPreserved();
406
+ await testDownloadTempCreateIsExclusiveNoFollow();
322
407
  await testDownloadBadOpts();
323
408
  await testUploadHappyPath();
324
409
  await testUploadMissingFile();
@@ -260,14 +260,18 @@ function _mockDb() {
260
260
  if (/^CREATE (TABLE|INDEX)/i.test(sql)) {
261
261
  return { run: function () { return { changes: 0 }; } };
262
262
  }
263
- if (/^SELECT (k, )?fingerprint, status_code, headers, body, expires_at FROM /i.test(sql)) {
263
+ // b.sql emits double-quoted identifiers ("k", "fingerprint", ...);
264
+ // the patterns tolerate optional quotes so the mock matches the
265
+ // builder's quote-by-construction output as well as the legacy bare
266
+ // form.
267
+ if (/^SELECT "?k"?(, "?fingerprint"?)?.* FROM /i.test(sql)) {
264
268
  return {
265
269
  get: function (k) {
266
270
  var row = data.get(k);
267
271
  return row ? Object.assign({ k: k }, row) : undefined;
268
272
  },
269
- // resealMigrate() also issues `SELECT k, ... FROM <table>`
270
- // without a WHERE k = ? clause — walk all rows.
273
+ // resealMigrate() also issues `SELECT "k", ... FROM <table>`
274
+ // without a WHERE "k" = ? clause — walk all rows.
271
275
  all: function () {
272
276
  var out = [];
273
277
  data.forEach(function (row, k) {
@@ -277,7 +281,7 @@ function _mockDb() {
277
281
  },
278
282
  };
279
283
  }
280
- if (/^INSERT INTO [^ ]+\(k, fingerprint, status_code, headers, body, expires_at\)/i.test(sql)) {
284
+ if (/^INSERT INTO .*\(\s*"?k"?, "?fingerprint"?, "?status_code"?, "?headers"?, "?body"?, "?expires_at"?\s*\)/i.test(sql)) {
281
285
  return {
282
286
  run: function (k, fingerprint, statusCode, headers, body, expiresAt) {
283
287
  data.set(k, {
@@ -291,7 +295,7 @@ function _mockDb() {
291
295
  },
292
296
  };
293
297
  }
294
- if (/^DELETE FROM [^ ]+ WHERE k = \? AND expires_at <= \?/i.test(sql)) {
298
+ if (/^DELETE FROM .* WHERE "?k"? = \? AND "?expires_at"? <= \?/i.test(sql)) {
295
299
  return {
296
300
  run: function (k, expiresAt) {
297
301
  var row = data.get(k);
@@ -407,7 +411,7 @@ function testDbStoreExpiredRaceNoFreshClobber() {
407
411
  var origGet = db.prepare;
408
412
  db.prepare = function (sql) {
409
413
  var stmt = origGet.call(db, sql);
410
- if (/^SELECT (k, )?fingerprint, status_code, headers, body, expires_at FROM /i.test(sql)) {
414
+ if (/^SELECT "?k"?(, "?fingerprint"?)?.* FROM /i.test(sql)) {
411
415
  var realGet = stmt.get;
412
416
  return {
413
417
  get: function (k) {
@@ -23,16 +23,27 @@ function _makeFakeExternalDb() {
23
23
  dialect: "sqlite",
24
24
  query: async function (sql, args) {
25
25
  var sqlLower = sql.toLowerCase();
26
- if (sqlLower.indexOf("insert or ignore") !== -1) {
26
+ // recordReceive composes through b.sql.upsert(...).doNothing()
27
+ // .returning(...) -> `INSERT INTO ... ON CONFLICT (...) DO NOTHING
28
+ // RETURNING "message_id"`. Match either that or the legacy
29
+ // `INSERT OR IGNORE ... RETURNING` shape. The b.sql column order is
30
+ // [message_id, source, ...] so args[0] = mid, args[1] = src.
31
+ var isDedupeInsert = sqlLower.indexOf("insert or ignore") !== -1 ||
32
+ (sqlLower.indexOf("insert into") !== -1 &&
33
+ sqlLower.indexOf("on conflict") !== -1 &&
34
+ sqlLower.indexOf("do nothing") !== -1);
35
+ if (isDedupeInsert) {
27
36
  var src = args[1], mid = args[0];
28
37
  var existing = rows.filter(function (r) { return r.source === src && r.message_id === mid; });
29
38
  if (existing.length === 0) {
30
39
  rows.push({ message_id: mid, source: src, received_at: new Date().toISOString(), processed_at: null, metadata_json: args[2] });
31
40
  lastChanges = 1;
32
- // RETURNING 1 mirror the SQLite 3.35+ semantics. Fresh
33
- // inserts get one row back; duplicates get zero.
41
+ // RETURNING — DO NOTHING returns one row on a fresh insert, none
42
+ // on a duplicate. The migrated path RETURNs "message_id" (the
43
+ // portable presence sentinel); the freshness check only reads
44
+ // rows.length, so return the inserted message_id row.
34
45
  if (sqlLower.indexOf("returning") !== -1) {
35
- return { rows: [{ "1": 1 }] };
46
+ return { rows: [{ message_id: mid }] };
36
47
  }
37
48
  } else {
38
49
  lastChanges = 0;
@@ -112,7 +112,153 @@ async function run() {
112
112
  check("cryptoField.clearResidencyForTest is fn",
113
113
  typeof b.cryptoField.clearResidencyForTest === "function");
114
114
 
115
+ runPerRowResidencyUnit();
116
+
117
+ // ---- #114: legal-hold + subject-restriction PII must be SEALED at rest ----
118
+ // These local tables hold legal-basis / custodian / ticket-reference free
119
+ // text that links a data subject to a legal matter — PII at rest. The raw
120
+ // write path (sql.insert + db.prepare().run()) bypassed the structured
121
+ // builder's auto-seal, so the values landed in clear despite the schema.
122
+ var LH_SECRET = "TOPSECRET-legal-reason-9f3a";
123
+ var LH_CUSTODIAN = "custodian-secret-7b2c@example.com";
124
+ holds.place("seal-subj-1", { reason: LH_SECRET, custodian: LH_CUSTODIAN, citation: "SEC-Rule-17a-4" });
125
+ var lhRaw = JSON.stringify(b.db.prepare("SELECT reason, custodian, citation FROM \"_blamejs_legal_hold\"").all());
126
+ check("#114 legal-hold reason is sealed at rest (not plaintext)", lhRaw.indexOf(LH_SECRET) === -1);
127
+ check("#114 legal-hold custodian is sealed at rest (not plaintext)", lhRaw.indexOf(LH_CUSTODIAN) === -1);
128
+ var lhGet = holds.get("seal-subj-1");
129
+ check("#114 legal-hold get() unseals on the consumer path",
130
+ !!(lhGet && lhGet.reason === LH_SECRET && lhGet.custodian === LH_CUSTODIAN));
131
+ var lhList = holds.list();
132
+ check("#114 legal-hold list() unseals on the consumer path",
133
+ lhList.some(function (h) { return h.reason === LH_SECRET; }));
134
+
135
+ var RES_SECRET = "TOPSECRET-restrict-reason-4e1d";
136
+ b.subject.restrict("seal-subj-2", { on: true, reason: RES_SECRET });
137
+ var resRaw = JSON.stringify(b.db.prepare("SELECT reason FROM \"_blamejs_subject_restrictions\"").all());
138
+ check("#114 subject-restriction reason is sealed at rest (not plaintext)", resRaw.indexOf(RES_SECRET) === -1);
139
+
115
140
  await dbHelper.teardownTestDb(tmpDir);
116
141
  }
117
142
 
143
+ // Returns the thrown error's .code when fn() throws, else null. Mirrors
144
+ // the threw-matching pattern used elsewhere in the layer-0 suite.
145
+ function codeFromThrow(fn) {
146
+ try { fn(); } catch (e) { return e && e.code; }
147
+ return null;
148
+ }
149
+
150
+ // Per-row residency unit surface (declare/get/clear). The cryptoField
151
+ // residency registry is in-process global state, so the block restores
152
+ // it via clearResidencyForTest in a finally so a parallel smoke file
153
+ // running another residency case isn't poisoned.
154
+ function runPerRowResidencyUnit() {
155
+ b.cryptoField.clearResidencyForTest();
156
+ try {
157
+ // ---- valid declare → return shape + getPerRowResidency round-trip ----
158
+ var decl = b.cryptoField.declarePerRowResidency("residents", {
159
+ residencyColumn: "dataRegion",
160
+ allowedTags: ["eu-west-1", "us-east-1", "global"],
161
+ });
162
+ check("declarePerRowResidency returns table",
163
+ decl.table === "residents");
164
+ check("declarePerRowResidency returns residencyColumn",
165
+ decl.residencyColumn === "dataRegion");
166
+ check("declarePerRowResidency returns allowedTags copy",
167
+ Array.isArray(decl.allowedTags) && decl.allowedTags.length === 3 &&
168
+ decl.allowedTags.indexOf("eu-west-1") !== -1);
169
+
170
+ var got = b.cryptoField.getPerRowResidency("residents");
171
+ check("getPerRowResidency round-trips residencyColumn",
172
+ got && got.residencyColumn === "dataRegion");
173
+ check("getPerRowResidency round-trips allowedTags",
174
+ got && got.allowedTags.join(",") === "eu-west-1,us-east-1,global");
175
+ check("getPerRowResidency returns a defensive allowedTags copy",
176
+ got.allowedTags !== decl.allowedTags);
177
+ check("getPerRowResidency on undeclared table → null",
178
+ b.cryptoField.getPerRowResidency("never-declared") === null);
179
+
180
+ // ---- refusals: each asserts the thrown e.code ----
181
+ check("declarePerRowResidency empty table → table-empty code",
182
+ codeFromThrow(function () {
183
+ b.cryptoField.declarePerRowResidency("", {
184
+ residencyColumn: "dataRegion", allowedTags: ["eu-west-1"],
185
+ });
186
+ }) === "crypto-field/per-row-residency-table-empty");
187
+
188
+ check("declarePerRowResidency null opts → opts-not-object code",
189
+ codeFromThrow(function () {
190
+ b.cryptoField.declarePerRowResidency("residents", null);
191
+ }) === "crypto-field/per-row-residency-opts-not-object");
192
+
193
+ check("declarePerRowResidency bad residencyColumn → column-invalid code",
194
+ codeFromThrow(function () {
195
+ b.cryptoField.declarePerRowResidency("residents", {
196
+ residencyColumn: "", allowedTags: ["eu-west-1"],
197
+ });
198
+ }) === "crypto-field/per-row-residency-column-invalid");
199
+
200
+ check("declarePerRowResidency empty allowedTags → tags-invalid code",
201
+ codeFromThrow(function () {
202
+ b.cryptoField.declarePerRowResidency("residents", {
203
+ residencyColumn: "dataRegion", allowedTags: [],
204
+ });
205
+ }) === "crypto-field/per-row-residency-tags-invalid");
206
+
207
+ check("declarePerRowResidency non-array allowedTags → tags-invalid code",
208
+ codeFromThrow(function () {
209
+ b.cryptoField.declarePerRowResidency("residents", {
210
+ residencyColumn: "dataRegion", allowedTags: "eu-west-1",
211
+ });
212
+ }) === "crypto-field/per-row-residency-tags-invalid");
213
+
214
+ check("declarePerRowResidency non-string tag → tag-empty code",
215
+ codeFromThrow(function () {
216
+ b.cryptoField.declarePerRowResidency("residents", {
217
+ residencyColumn: "dataRegion", allowedTags: ["eu-west-1", 42],
218
+ });
219
+ }) === "crypto-field/per-row-residency-tag-empty");
220
+
221
+ // Unknown opt key throws via validateOpts (plain Error, no .code) —
222
+ // assert the message names the unknown key + the primitive.
223
+ var unknownKeyErr = null;
224
+ try {
225
+ b.cryptoField.declarePerRowResidency("residents", {
226
+ residencyColumn: "dataRegion", allowedTags: ["eu-west-1"], bogusKey: 1,
227
+ });
228
+ } catch (e) { unknownKeyErr = e; }
229
+ check("declarePerRowResidency unknown opt key throws",
230
+ unknownKeyErr !== null);
231
+ check("declarePerRowResidency unknown opt key message names the key",
232
+ unknownKeyErr && /unknown option 'bogusKey'/.test(unknownKeyErr.message) &&
233
+ /declarePerRowResidency/.test(unknownKeyErr.message));
234
+
235
+ // A sealed column can't be the residency tag column — the gate
236
+ // reads the tag as plaintext before sealRow, and reads return it
237
+ // verbatim. Declaring a sealed column refuses at declaration time.
238
+ b.cryptoField.registerTable("sealed_residents", { sealedFields: ["dataRegion"] });
239
+ check("declarePerRowResidency rejects a sealed column as the tag column",
240
+ codeFromThrow(function () {
241
+ b.cryptoField.declarePerRowResidency("sealed_residents", {
242
+ residencyColumn: "dataRegion", allowedTags: ["eu-west-1"],
243
+ });
244
+ }) === "crypto-field/per-row-residency-sealed-conflict");
245
+ check("declarePerRowResidency accepts a non-sealed column on the same table",
246
+ b.cryptoField.declarePerRowResidency("sealed_residents", {
247
+ residencyColumn: "region_tag", allowedTags: ["eu-west-1"],
248
+ }).residencyColumn === "region_tag");
249
+
250
+ // ---- clearResidencyForTest clears the per-row registry too ----
251
+ b.cryptoField.declarePerRowResidency("residents", {
252
+ residencyColumn: "dataRegion", allowedTags: ["eu-west-1"],
253
+ });
254
+ check("per-row residency present before clear",
255
+ b.cryptoField.getPerRowResidency("residents") !== null);
256
+ b.cryptoField.clearResidencyForTest();
257
+ check("clearResidencyForTest drops the per-row residency registry",
258
+ b.cryptoField.getPerRowResidency("residents") === null);
259
+ } finally {
260
+ b.cryptoField.clearResidencyForTest();
261
+ }
262
+ }
263
+
118
264
  module.exports = { run: run };
@@ -108,6 +108,190 @@ async function testDmarcEvaluateUnaligned() {
108
108
  rv.result === "fail" && rv.recommendedAction === "quarantine");
109
109
  }
110
110
 
111
+ // ---- b.mail.inbound.verify — receiver pipeline (RFC 7489 §6.6) ----
112
+
113
+ function _inboundDns(records) {
114
+ return async function (host, type) {
115
+ if (records[host + "/" + type]) return records[host + "/" + type];
116
+ var err = new Error("ENOTFOUND"); err.code = "ENOTFOUND"; throw err;
117
+ };
118
+ }
119
+
120
+ async function testInboundVerifyAlignedPass() {
121
+ var dnsLookup = _inboundDns({
122
+ "example.com/TXT": [["v=spf1 ip4:192.0.2.0/24 -all"]],
123
+ "_dmarc.example.com/TXT": [["v=DMARC1; p=reject"]],
124
+ });
125
+ var msg = "From: Alice <alice@example.com>\r\nSubject: hi\r\n\r\nhello\r\n";
126
+ var v = await b.mail.inbound.verify({
127
+ ip: "192.0.2.5",
128
+ helo: "mail.example.com",
129
+ mailFrom: "alice@example.com",
130
+ message: Buffer.from(msg),
131
+ dnsLookup: dnsLookup,
132
+ authservId: "mx.local.test",
133
+ });
134
+ check("inbound.verify: aligned SPF → dmarc pass + deliver",
135
+ v.spf.result === "pass" && v.dmarc.result === "pass" &&
136
+ v.dmarc.recommendedAction === "deliver");
137
+ check("inbound.verify: From extracted from Buffer message",
138
+ v.from.count === 1 && v.from.address === "alice@example.com" &&
139
+ v.from.domain === "example.com");
140
+ check("inbound.verify: A-R header emitted with authserv-id first",
141
+ typeof v.authResults === "string" &&
142
+ v.authResults.indexOf("Authentication-Results: mx.local.test") === 0 &&
143
+ /spf=pass/.test(v.authResults) && /dmarc=pass/.test(v.authResults));
144
+ check("inbound.verify: unsigned message verifies dkim none",
145
+ Array.isArray(v.dkim) && v.dkim[0] && v.dkim[0].result === "none");
146
+ }
147
+
148
+ async function testInboundVerifySpoofRejected() {
149
+ var dnsLookup = _inboundDns({
150
+ "spoofed.example/TXT": [["v=spf1 -all"]],
151
+ "_dmarc.spoofed.example/TXT": [["v=DMARC1; p=reject"]],
152
+ });
153
+ var msg = "From: ceo@spoofed.example\r\nSubject: urgent\r\n\r\nwire money\r\n";
154
+ var v = await b.mail.inbound.verify({
155
+ ip: "203.0.113.9",
156
+ helo: "evil.host",
157
+ mailFrom: "ceo@spoofed.example",
158
+ message: msg,
159
+ dnsLookup: dnsLookup,
160
+ });
161
+ check("inbound.verify: spoofed sender → spf fail + dmarc fail + reject",
162
+ v.spf.result === "fail" && v.dmarc.result === "fail" &&
163
+ v.dmarc.recommendedAction === "reject");
164
+ check("inbound.verify: no authservId → no A-R header", v.authResults === null);
165
+ }
166
+
167
+ async function testInboundVerifyFromHeaderDiscipline() {
168
+ var dnsLookup = _inboundDns({});
169
+ // Two From fields — the header-duplication spoofing shape.
170
+ var dup = await b.mail.inbound.verify({
171
+ ip: "203.0.113.9", helo: "evil.host", mailFrom: "safe@aligned.example",
172
+ message: "From: safe@aligned.example\r\nFrom: ceo@victim.example\r\n\r\nbody\r\n",
173
+ dnsLookup: dnsLookup,
174
+ });
175
+ check("inbound.verify: duplicated From → permerror + reject (RFC 7489 §6.6.1)",
176
+ dup.from.count === 2 && dup.dmarc.result === "permerror" &&
177
+ dup.dmarc.recommendedAction === "reject");
178
+ // Two angle-addr authors inside one field.
179
+ var multi = await b.mail.inbound.verify({
180
+ ip: "203.0.113.9", helo: "evil.host", mailFrom: "safe@aligned.example",
181
+ message: "From: <safe@aligned.example> <ceo@victim.example>\r\n\r\nbody\r\n",
182
+ dnsLookup: dnsLookup,
183
+ });
184
+ check("inbound.verify: two angle-addrs in one From → permerror + reject",
185
+ multi.from.count === 2 && multi.dmarc.recommendedAction === "reject");
186
+ // Bare address list (no angle-addrs) — unparsable rather than
187
+ // picking one of the authors.
188
+ var bareList = await b.mail.inbound.verify({
189
+ ip: "203.0.113.9", helo: "evil.host", mailFrom: "safe@aligned.example",
190
+ message: "From: a@aligned.example, b@victim.example\r\n\r\nbody\r\n",
191
+ dnsLookup: dnsLookup,
192
+ });
193
+ check("inbound.verify: bare From address-list → no author domain picked + reject",
194
+ bareList.from.domain === null && bareList.dmarc.recommendedAction === "reject");
195
+ // No From header at all.
196
+ var none = await b.mail.inbound.verify({
197
+ ip: "203.0.113.9", helo: "evil.host", mailFrom: "safe@aligned.example",
198
+ message: "Subject: headless\r\n\r\nbody\r\n",
199
+ dnsLookup: dnsLookup,
200
+ });
201
+ check("inbound.verify: missing From → permerror + reject",
202
+ none.from.count === 0 && none.dmarc.result === "permerror" &&
203
+ none.dmarc.recommendedAction === "reject");
204
+ // Folded From header unfolds before extraction.
205
+ var folded = await b.mail.inbound.verify({
206
+ ip: "203.0.113.9", helo: "evil.host", mailFrom: "x@folded.example",
207
+ message: "From: Folded Name\r\n <x@folded.example>\r\nSubject: f\r\n\r\nbody\r\n",
208
+ dnsLookup: dnsLookup,
209
+ });
210
+ check("inbound.verify: folded From unfolds (RFC 5322 §2.2.3)",
211
+ folded.from.count === 1 && folded.from.domain === "folded.example");
212
+ // Quoted display-names: a literal `<` or comma inside a
213
+ // quoted-string is display-name text, not a second author — valid
214
+ // single-author mail must not false-positive into permerror.
215
+ var quotedLt = await b.mail.inbound.verify({
216
+ ip: "203.0.113.9", helo: "h.example", mailFrom: "u@quoted.example",
217
+ message: "From: \"John <Jr.> Smith\" <u@quoted.example>\r\n\r\nbody\r\n",
218
+ dnsLookup: dnsLookup,
219
+ });
220
+ check("inbound.verify: quoted display-name with literal < is one author",
221
+ quotedLt.from.count === 1 && quotedLt.from.domain === "quoted.example");
222
+ var quotedComma = await b.mail.inbound.verify({
223
+ ip: "203.0.113.9", helo: "h.example", mailFrom: "j@comma.example",
224
+ message: "From: \"Doe, John\" <j@comma.example>\r\n\r\nbody\r\n",
225
+ dnsLookup: dnsLookup,
226
+ });
227
+ check("inbound.verify: quoted display-name with comma is one author",
228
+ quotedComma.from.count === 1 && quotedComma.from.domain === "comma.example");
229
+ // Comma-separated angle-addr list — multiple authors refused.
230
+ var twoAngle = await b.mail.inbound.verify({
231
+ ip: "203.0.113.9", helo: "evil.host", mailFrom: "safe@aligned.example",
232
+ message: "From: <safe@aligned.example>, Boss <ceo@victim.example>\r\n\r\nbody\r\n",
233
+ dnsLookup: dnsLookup,
234
+ });
235
+ check("inbound.verify: comma-separated angle-addr list → multiple authors refused",
236
+ twoAngle.from.count === 2 && twoAngle.dmarc.recommendedAction === "reject");
237
+ }
238
+
239
+ // RFC 7489 §6.6.2 — a fail verdict computed while SPF or DKIM returned
240
+ // temperror must surface as temperror (the transiently-failed lookup
241
+ // could have produced the aligned pass), so the MX gate defers with
242
+ // 451 instead of permanently refusing a legitimate sender mid-DNS-blip.
243
+ async function testInboundVerifyTemperrorPrecedence() {
244
+ // SPF TXT lookup fails transiently (SERVFAIL — no ENOTFOUND code);
245
+ // the DMARC policy lookup itself succeeds with p=reject.
246
+ var dnsLookup = async function (host, type) {
247
+ if (host === "_dmarc.blip.example" && type === "TXT") {
248
+ return [["v=DMARC1; p=reject"]];
249
+ }
250
+ throw new Error("SERVFAIL");
251
+ };
252
+ var v = await b.mail.inbound.verify({
253
+ ip: "203.0.113.9",
254
+ helo: "mail.blip.example",
255
+ mailFrom: "news@blip.example",
256
+ message: "From: news@blip.example\r\nSubject: hi\r\n\r\nhello\r\n",
257
+ dnsLookup: dnsLookup,
258
+ });
259
+ check("inbound.verify: SPF temperror under p=reject → dmarc temperror, not fail",
260
+ v.spf.result === "temperror" && v.dmarc.result === "temperror" &&
261
+ v.dmarc.recommendedAction !== "reject");
262
+ // A pass stands regardless of the other authenticator's temperror:
263
+ // aligned DKIM pass + SPF temperror is still a DMARC pass.
264
+ var dnsLookup2 = async function (host, type) {
265
+ if (host === "_dmarc.blip.example" && type === "TXT") {
266
+ return [["v=DMARC1; p=reject"]];
267
+ }
268
+ throw new Error("SERVFAIL");
269
+ };
270
+ var v2 = await b.mail.dmarc.evaluate({
271
+ from: "news@blip.example",
272
+ spf: { result: "temperror", domain: "blip.example" },
273
+ dkim: [{ result: "pass", domain: "blip.example" }],
274
+ dnsLookup: dnsLookup2,
275
+ });
276
+ check("dmarc.evaluate: aligned DKIM pass beats SPF temperror (pass stands)",
277
+ v2.result === "pass" && v2.recommendedAction === "deliver");
278
+ }
279
+
280
+ async function testInboundVerifyValidation() {
281
+ var e1 = null;
282
+ try { await b.mail.inbound.verify({ message: "x" }); } catch (e) { e1 = e; }
283
+ check("inbound.verify: missing ip refused", e1 !== null);
284
+ var e2 = null;
285
+ try { await b.mail.inbound.verify({ ip: "203.0.113.9", message: "" }); } catch (e) { e2 = e; }
286
+ check("inbound.verify: empty message refused",
287
+ e2 && /inbound-bad-message/.test(e2.code || ""));
288
+ var e3 = null;
289
+ try {
290
+ await b.mail.inbound.verify({ ip: "203.0.113.9", message: "From: a@b.c\r\n\r\nx", bogus: 1 });
291
+ } catch (e) { e3 = e; }
292
+ check("inbound.verify: unknown opt refused (config-time)", e3 !== null);
293
+ }
294
+
111
295
  async function testArcVerifyMissing() {
112
296
  var msg = "ARC-Seal: i=1; a=rsa-sha256; cv=none; d=example.com; s=arc; b=AAAA\r\n" +
113
297
  "From: alice@example.com\r\n\r\nbody\r\n";
@@ -1214,6 +1398,11 @@ async function run() {
1214
1398
  await testDmarcEvaluateUnaligned();
1215
1399
  await testDmarcEvaluateOrgDomainViaPsl();
1216
1400
  await testDmarcEvaluateNpPolicy();
1401
+ await testInboundVerifyAlignedPass();
1402
+ await testInboundVerifySpoofRejected();
1403
+ await testInboundVerifyFromHeaderDiscipline();
1404
+ await testInboundVerifyTemperrorPrecedence();
1405
+ await testInboundVerifyValidation();
1217
1406
  await testArcVerifyMissing();
1218
1407
  await testArcVerifyNone();
1219
1408
  await testArcVerifyBadSignatures();
@@ -162,7 +162,9 @@ function testSealUnsealRoundTrip() {
162
162
  };
163
163
  return [];
164
164
  }
165
- if (/SELECT.*FROM.*WHERE journal_id = \?/.test(sql)) {
165
+ // b.sql quotes identifiers ("journal_id"); match the quoted or
166
+ // bare column form so this fake works against the composed SQL.
167
+ if (/SELECT[\s\S]*FROM[\s\S]*WHERE "?journal_id"? = \?/.test(sql)) {
166
168
  return insertedRow ? [insertedRow] : [];
167
169
  }
168
170
  return [];