@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
@@ -149,6 +149,111 @@ async function testNoActorRefused() {
149
149
  rv.type === "urn:ietf:params:jmap:error:forbidden");
150
150
  }
151
151
 
152
+ // ---- Cross-tenant accountId gate (RFC 8620 §3.6.1 accountNotFound) ----
153
+ //
154
+ // accountsFor(actor) is the authorization source; a method call / blob op
155
+ // that names an accountId outside the actor's enumerated set is rejected
156
+ // with `accountNotFound` BEFORE the operator handler runs, so a tenant
157
+ // can't read/write another tenant's account.
158
+
159
+ async function testDispatchForeignAccountRefused() {
160
+ var reached = false;
161
+ var jmap = b.mail.server.jmap.create({
162
+ mailStore: { appendMessage: function () {} },
163
+ // Actor A is enumerated for A1 only.
164
+ accountsFor: async function () { return { primaryAccounts: { core: "A1" }, accounts: { A1: { name: "tenant-a" } } }; },
165
+ methods: {
166
+ "Mailbox/get": async function () { reached = true; return { list: [] }; },
167
+ },
168
+ });
169
+ // Tenant A references tenant B's accountId (B9).
170
+ var rv = await jmap.dispatch({ id: "actor-a", tenantId: "tenant-a" }, {
171
+ using: [],
172
+ methodCalls: [["Mailbox/get", { accountId: "B9" }, "c0"]],
173
+ });
174
+ check("foreign accountId → accountNotFound",
175
+ rv.methodResponses[0][0] === "error" &&
176
+ rv.methodResponses[0][1].type === "urn:ietf:params:jmap:error:accountNotFound");
177
+ check("foreign accountId → method handler NOT reached", reached === false);
178
+ check("response echoes clientId on the gate error",
179
+ rv.methodResponses[0][2] === "c0");
180
+ }
181
+
182
+ async function testDispatchOwnAccountAllowed() {
183
+ var reached = false;
184
+ var jmap = b.mail.server.jmap.create({
185
+ mailStore: { appendMessage: function () {} },
186
+ accountsFor: async function () { return { primaryAccounts: { core: "A1" }, accounts: { A1: { name: "tenant-a" } } }; },
187
+ methods: {
188
+ "Mailbox/get": async function (actor, args) { reached = true; return { accountId: args.accountId, list: [] }; },
189
+ },
190
+ });
191
+ var rv = await jmap.dispatch({ id: "actor-a" }, {
192
+ using: [],
193
+ methodCalls: [["Mailbox/get", { accountId: "A1" }, "c0"]],
194
+ });
195
+ check("own accountId → handler reached", reached === true);
196
+ check("own accountId → no error", rv.methodResponses[0][0] === "Mailbox/get");
197
+ }
198
+
199
+ async function testDispatchAccountAgnosticMethodAllowed() {
200
+ // A call with no accountId (account-agnostic) passes the gate unchanged.
201
+ var reached = false;
202
+ var jmap = b.mail.server.jmap.create({
203
+ mailStore: { appendMessage: function () {} },
204
+ accountsFor: async function () { return { primaryAccounts: {}, accounts: {} }; },
205
+ methods: {
206
+ "Core/echo": async function (actor, args) { reached = true; return { hi: args.hi }; },
207
+ },
208
+ });
209
+ var rv = await jmap.dispatch({ id: "actor-a" }, {
210
+ using: [],
211
+ methodCalls: [["Core/echo", { hi: 7 }, "c0"]],
212
+ });
213
+ check("account-agnostic method → handler reached", reached === true);
214
+ check("account-agnostic method → result returned", rv.methodResponses[0][1].hi === 7);
215
+ }
216
+
217
+ async function testUploadForeignAccountRefused() {
218
+ var backendHit = false;
219
+ var jmap = b.mail.server.jmap.create({
220
+ mailStore: {
221
+ appendMessage: function () {},
222
+ uploadBlob: function () { backendHit = true; return Promise.resolve({ blobId: "x" }); },
223
+ },
224
+ accountsFor: async function () { return { accounts: { A1: { name: "tenant-a" } } }; },
225
+ methods: {},
226
+ });
227
+ // Upload targeting tenant B's accountId (B9).
228
+ var mr = _makeUploadReqRes("/jmap/upload/B9", "text/plain", [Buffer.from("hi")]);
229
+ jmap.uploadHandler(mr.req, mr.res);
230
+ await new Promise(function (r) { setImmediate(function () { setImmediate(r); }); });
231
+ await new Promise(function (r) { setImmediate(r); });
232
+ check("upload foreign accountId → 404", mr.res._status() === 404);
233
+ check("upload foreign accountId → accountNotFound", /jmap:error:accountNotFound/.test(mr.res._buf()));
234
+ check("upload foreign accountId → backend NOT hit", backendHit === false);
235
+ }
236
+
237
+ async function testDownloadForeignAccountRefused() {
238
+ var backendHit = false;
239
+ var jmap = b.mail.server.jmap.create({
240
+ mailStore: {
241
+ appendMessage: function () {},
242
+ downloadBlob: function () { backendHit = true; return Promise.resolve({ bytes: Buffer.from("x"), type: "text/plain" }); },
243
+ },
244
+ accountsFor: async function () { return { accounts: { A1: { name: "tenant-a" } } }; },
245
+ methods: {},
246
+ });
247
+ // Download targeting tenant B's accountId (B9).
248
+ var mr = _makeUploadReqRes("/jmap/download/B9/blob_42/note.txt", null, []);
249
+ jmap.downloadHandler(mr.req, mr.res);
250
+ await new Promise(function (r) { setImmediate(function () { setImmediate(r); }); });
251
+ await new Promise(function (r) { setImmediate(r); });
252
+ check("download foreign accountId → 404", mr.res._status() === 404);
253
+ check("download foreign accountId → accountNotFound", /jmap:error:accountNotFound/.test(mr.res._buf()));
254
+ check("download foreign accountId → backend NOT hit", backendHit === false);
255
+ }
256
+
152
257
  // ---- v0.11.29 — JMAP Push (EventSource SSE per RFC 8620 §7.3) ----
153
258
 
154
259
  function _makeMockReqRes(url) {
@@ -366,7 +471,7 @@ async function testUploadHandlerHappyPath() {
366
471
  return Promise.resolve({ blobId: "blob_42", type: type, size: bytes.length });
367
472
  },
368
473
  },
369
- accountsFor: async function () { return {}; },
474
+ accountsFor: async function () { return { accounts: { A1: { name: "x" } } }; },
370
475
  methods: {},
371
476
  });
372
477
  var body = Buffer.from("Hello, blob world.");
@@ -450,7 +555,7 @@ async function testDownloadHandlerHappyPath() {
450
555
  return Promise.resolve({ bytes: Buffer.from("hello blob"), type: "text/plain" });
451
556
  },
452
557
  },
453
- accountsFor: async function () { return {}; },
558
+ accountsFor: async function () { return { accounts: { A1: { name: "x" } } }; },
454
559
  methods: {},
455
560
  });
456
561
  var mr = _makeUploadReqRes("/jmap/download/A1/blob_42/note.txt", null, []);
@@ -498,7 +603,13 @@ async function testJmapIdAcceptsFullLength() {
498
603
  uploadBlob: function () { return Promise.resolve({ blobId: "B" }); },
499
604
  downloadBlob: function () { return Promise.resolve({ bytes: Buffer.from("x"), type: "text/plain" }); },
500
605
  },
501
- accountsFor: async function () { return {}; },
606
+ // The full-length accountId is enumerated for the actor so the gate
607
+ // permits it; this test exercises the JMAP Id length cap, not authz.
608
+ accountsFor: async function () {
609
+ var id = "A".repeat(200); // allow:raw-byte-literal — 200 chars, under RFC 8620 §1.2 255 cap
610
+ var accts = {}; accts[id] = { name: "x" };
611
+ return { accounts: accts };
612
+ },
502
613
  methods: {},
503
614
  });
504
615
  var longId = "A".repeat(200); // allow:raw-byte-literal — 200 chars, under RFC 8620 §1.2 255 cap
@@ -524,13 +635,15 @@ async function testDownloadHandlerNotFound() {
524
635
  appendMessage: function () {},
525
636
  downloadBlob: function () { return Promise.resolve(null); },
526
637
  },
527
- accountsFor: async function () { return {}; },
638
+ accountsFor: async function () { return { accounts: { A1: { name: "x" } } }; },
528
639
  methods: {},
529
640
  });
530
641
  var mr = _makeUploadReqRes("/jmap/download/A1/missing/note.txt", null, []);
531
642
  jmap.downloadHandler(mr.req, mr.res);
532
643
  await new Promise(function (r) { setImmediate(function () { setImmediate(r); }); });
533
644
  check("download missing → 404", mr.res._status() === 404);
645
+ check("download missing → blob-not-found (not account gate)",
646
+ /Blob not found/.test(mr.res._buf()));
534
647
  }
535
648
 
536
649
  function testDownloadHandlerRefusesUnauth() {
@@ -597,6 +710,12 @@ async function run() {
597
710
  await testUnknownMethod();
598
711
  await testMethodThrewMaskedAsServerFail();
599
712
  await testNoActorRefused();
713
+ // Cross-tenant accountId gate (RFC 8620 §3.6.1 accountNotFound)
714
+ await testDispatchForeignAccountRefused();
715
+ await testDispatchOwnAccountAllowed();
716
+ await testDispatchAccountAgnosticMethodAllowed();
717
+ await testUploadForeignAccountRefused();
718
+ await testDownloadForeignAccountRefused();
600
719
  // v0.11.29 — JMAP Push (EventSource SSE per RFC 8620 §7.3)
601
720
  testEventSourceHandlerExists();
602
721
  testEventSourceRefusesUnauthenticated();
@@ -135,12 +135,17 @@ async function _sendCommand(socket, line) {
135
135
  var last = lines[lines.length - 1];
136
136
  if (/^\d{3} /.test(last)) {
137
137
  socket.removeListener("data", onData);
138
+ socket.removeListener("error", onError);
138
139
  resolve(buf);
139
140
  }
140
141
  }
141
142
  }
143
+ // Detach on settle — a long transaction issues a dozen commands on
144
+ // one socket, and never-fired once("error") handlers accumulate
145
+ // past the MaxListeners warning threshold.
146
+ function onError(e) { socket.removeListener("data", onData); reject(e); }
142
147
  socket.on("data", onData);
143
- socket.once("error", reject);
148
+ socket.once("error", onError);
144
149
  socket.write(line + "\r\n");
145
150
  });
146
151
  }
@@ -152,11 +157,13 @@ async function _readGreeting(socket) {
152
157
  buf += chunk.toString("utf8");
153
158
  if (buf.indexOf("\r\n") !== -1) {
154
159
  socket.removeListener("data", onData);
160
+ socket.removeListener("error", onError);
155
161
  resolve(buf);
156
162
  }
157
163
  }
164
+ function onError(e) { socket.removeListener("data", onData); reject(e); }
158
165
  socket.on("data", onData);
159
- socket.once("error", reject);
166
+ socket.once("error", onError);
160
167
  });
161
168
  }
162
169
 
@@ -426,6 +433,203 @@ async function testGateOverStartTls() {
426
433
  } finally { await srv.close({ timeoutMs: 1000 }); } // allow:raw-time-literal — test-only short drain
427
434
  }
428
435
 
436
+ // DATA-phase SPF/DKIM/DMARC gate (opts.guardEnvelope → b.mail.inbound
437
+ // .verify). Drives full SMTP transactions against a mocked DNS: the
438
+ // policy-reject path answers 550 5.7.1 before the agent handoff, the
439
+ // aligned path delivers with the verdict on the handoff ctx + the
440
+ // RFC 8601 Authentication-Results header prepended, quarantine
441
+ // delivers annotated, monitor mode never refuses, and DNS temperror
442
+ // defers (451) or accepts per onTemperror.
443
+ async function testGuardEnvelopeGate() {
444
+ var ctx;
445
+ try { ctx = await _makeTestTlsContext(); }
446
+ catch (_e) { check("guardEnvelope gate (skipped — test cert fixture unavailable)", true); return; }
447
+ var records = {
448
+ "external.com/TXT": [["v=spf1 ip4:127.0.0.1 -all"]],
449
+ "_dmarc.external.com/TXT": [["v=DMARC1; p=reject"]],
450
+ "spoof.example/TXT": [["v=spf1 -all"]],
451
+ "_dmarc.spoof.example/TXT": [["v=DMARC1; p=reject"]],
452
+ "spoofq.example/TXT": [["v=spf1 -all"]],
453
+ "_dmarc.spoofq.example/TXT": [["v=DMARC1; p=quarantine"]],
454
+ };
455
+ var dnsLookup = async function (host, type) {
456
+ if (records[host + "/" + type]) return records[host + "/" + type];
457
+ var err = new Error("ENOTFOUND"); err.code = "ENOTFOUND"; throw err;
458
+ };
459
+ async function _transact(socket, mailFrom, body) {
460
+ await _sendCommand(socket, "MAIL FROM:<" + mailFrom + ">");
461
+ await _sendCommand(socket, "RCPT TO:<alice@example.com>");
462
+ await _sendCommand(socket, "DATA");
463
+ return _sendCommand(socket, body + "\r\n.");
464
+ }
465
+
466
+ // Boot-time validation of the gate config.
467
+ var eBad = null;
468
+ try {
469
+ b.mail.server.mx.create({ tlsContext: ctx, guardEnvelope: "yes" });
470
+ } catch (e) { eBad = e; }
471
+ check("guardEnvelope: non-boolean/object config refused at boot", eBad !== null);
472
+ var eMode = null;
473
+ try {
474
+ b.mail.server.mx.create({ tlsContext: ctx, guardEnvelope: { mode: "loud" } });
475
+ } catch (e) { eMode = e; }
476
+ check("guardEnvelope: unknown mode refused at boot", eMode !== null);
477
+ // DKIM verifier ranges are mirrored at boot — a config the verifier
478
+ // would refuse per-message must fail startup, not break live SMTP.
479
+ var eSigs = null;
480
+ try {
481
+ b.mail.server.mx.create({ tlsContext: ctx, guardEnvelope: { maxSignatures: 100 } });
482
+ } catch (e) { eSigs = e; }
483
+ check("guardEnvelope: maxSignatures above the DKIM verifier ceiling refused at boot",
484
+ eSigs !== null && /bad-bound/.test(eSigs.code || ""));
485
+ var eSkew = null;
486
+ try {
487
+ b.mail.server.mx.create({ tlsContext: ctx,
488
+ guardEnvelope: { clockSkewMs: b.mail.dkim.DKIM_CLOCK_SKEW_MS_MAX + 1 } });
489
+ } catch (e) { eSkew = e; }
490
+ check("guardEnvelope: clockSkewMs above the DKIM verifier ceiling refused at boot",
491
+ eSkew !== null && /bad-bound/.test(eSkew.code || ""));
492
+
493
+ // ---- enforce mode ----
494
+ var handoffs = [];
495
+ var srv = b.mail.server.mx.create({
496
+ tlsContext: ctx, profile: "permissive", localDomains: ["example.com"],
497
+ guardEnvelope: { mode: "enforce", dnsLookup: dnsLookup },
498
+ agent: { handoff: async function (h) { handoffs.push(h); return { messageId: "m" + handoffs.length }; } },
499
+ });
500
+ var info = await srv.listen({ port: 0, address: "127.0.0.1" });
501
+ try {
502
+ var sock = nodeNet.connect(info.port, "127.0.0.1");
503
+ await new Promise(function (r) { sock.once("connect", r); });
504
+ await _readGreeting(sock);
505
+ await _sendCommand(sock, "EHLO sender.example.com");
506
+
507
+ // Policy reject: SPF fail against p=reject → refused at the wire
508
+ // with the RFC 7372 multiple-authentication-checks-failed code.
509
+ var rej = await _transact(sock, "ceo@spoof.example",
510
+ "From: ceo@spoof.example\r\nSubject: urgent\r\n\r\npay this invoice\r\n");
511
+ check("guardEnvelope enforce: DMARC p=reject + SPF fail → 550 5.7.26 (RFC 7372)", /^550 5\.7\.26/.test(rej));
512
+ check("guardEnvelope enforce: refused message never reaches the agent", handoffs.length === 0);
513
+
514
+ // Multi-From spoofing shape → refused.
515
+ var multi = await _transact(sock, "s@external.com",
516
+ "From: s@external.com\r\nFrom: ceo@spoof.example\r\nSubject: x\r\n\r\nbody\r\n");
517
+ check("guardEnvelope enforce: duplicated From → 550 5.7.1 (RFC 7489 §6.6.1)",
518
+ /^550 5\.7\.1/.test(multi) && handoffs.length === 0);
519
+
520
+ // Aligned pass: delivered with verdict + A-R header. The message
521
+ // arrives with a FORGED Authentication-Results header claiming
522
+ // this receiver's authserv-id — RFC 8601 §5 requires it stripped
523
+ // before the computed one is prepended.
524
+ var ok = await _transact(sock, "s@external.com",
525
+ "Authentication-Results: example.com;\r\n dkim=pass header.d=forged.example\r\n" +
526
+ "From: s@external.com\r\nSubject: hi\r\n\r\nhello\r\n");
527
+ check("guardEnvelope enforce: aligned SPF pass → 250 accepted", /^250 /.test(ok));
528
+ check("guardEnvelope: handoff carries the auth verdict",
529
+ handoffs.length === 1 && handoffs[0].auth &&
530
+ handoffs[0].auth.action === "accept" &&
531
+ handoffs[0].auth.spf.result === "pass" &&
532
+ handoffs[0].auth.dmarc.result === "pass" &&
533
+ handoffs[0].auth.quarantine === false);
534
+ var delivered = handoffs.length === 1 ? handoffs[0].body.toString("utf8") : "";
535
+ check("guardEnvelope: A-R header prepended with the localDomains authserv-id",
536
+ delivered.indexOf("Authentication-Results: example.com") === 0 &&
537
+ /spf=pass/.test(delivered) && /dmarc=pass/.test(delivered));
538
+ check("guardEnvelope: forged same-authserv-id A-R header stripped (RFC 8601 §5)",
539
+ delivered.indexOf("forged.example") === -1 &&
540
+ delivered.split("Authentication-Results: example.com").length === 2);
541
+ check("guardEnvelope: original message preserved after the A-R header",
542
+ delivered.indexOf("Subject: hi") !== -1 && delivered.indexOf("hello") !== -1);
543
+
544
+ // Quarantine policy: delivered, annotated for the downstream agent.
545
+ var q = await _transact(sock, "news@spoofq.example",
546
+ "From: news@spoofq.example\r\nSubject: promo\r\n\r\ndeal\r\n");
547
+ check("guardEnvelope enforce: p=quarantine → 250 delivered annotated",
548
+ /^250 /.test(q) && handoffs.length === 2 &&
549
+ handoffs[1].auth.quarantine === true &&
550
+ handoffs[1].auth.action === "quarantine");
551
+ sock.destroy();
552
+ } finally { await srv.close({ timeoutMs: 1000 }); } // allow:raw-time-literal — test-only short drain
553
+
554
+ // ---- monitor mode: same spoof, never refused ----
555
+ var monHandoffs = [];
556
+ var monSrv = b.mail.server.mx.create({
557
+ tlsContext: ctx, profile: "permissive", localDomains: ["example.com"],
558
+ guardEnvelope: { mode: "monitor", dnsLookup: dnsLookup },
559
+ agent: { handoff: async function (h) { monHandoffs.push(h); return {}; } },
560
+ });
561
+ var monInfo = await monSrv.listen({ port: 0, address: "127.0.0.1" });
562
+ try {
563
+ var monSock = nodeNet.connect(monInfo.port, "127.0.0.1");
564
+ await new Promise(function (r) { monSock.once("connect", r); });
565
+ await _readGreeting(monSock);
566
+ await _sendCommand(monSock, "EHLO sender.example.com");
567
+ var monRej = await _transact(monSock, "ceo@spoof.example",
568
+ "From: ceo@spoof.example\r\nSubject: urgent\r\n\r\npay\r\n");
569
+ check("guardEnvelope monitor: policy-reject message still delivered",
570
+ /^250 /.test(monRej) && monHandoffs.length === 1 &&
571
+ monHandoffs[0].auth.action === "reject" &&
572
+ monHandoffs[0].auth.mode === "monitor");
573
+ monSock.destroy();
574
+ } finally { await monSrv.close({ timeoutMs: 1000 }); } // allow:raw-time-literal — test-only short drain
575
+
576
+ // ---- DNS temperror: defer (default) vs accept ----
577
+ var servfail = async function () { throw new Error("SERVFAIL"); };
578
+ var deferSrv = b.mail.server.mx.create({
579
+ tlsContext: ctx, profile: "permissive", localDomains: ["example.com"],
580
+ guardEnvelope: { mode: "enforce", dnsLookup: servfail },
581
+ });
582
+ var deferInfo = await deferSrv.listen({ port: 0, address: "127.0.0.1" });
583
+ try {
584
+ var dSock = nodeNet.connect(deferInfo.port, "127.0.0.1");
585
+ await new Promise(function (r) { dSock.once("connect", r); });
586
+ await _readGreeting(dSock);
587
+ await _sendCommand(dSock, "EHLO sender.example.com");
588
+ var deferred = await _transact(dSock, "s@external.com",
589
+ "From: s@external.com\r\nSubject: hi\r\n\r\nhello\r\n");
590
+ check("guardEnvelope: DNS temperror defers with 451 4.7.0 (sender retries)",
591
+ /^451 4\.7\.0/.test(deferred));
592
+ dSock.destroy();
593
+ } finally { await deferSrv.close({ timeoutMs: 1000 }); } // allow:raw-time-literal — test-only short drain
594
+
595
+ // ---- pipeline wall-clock timeout: a hanging resolver cannot pin
596
+ // the connection slot — the race defers on the temperror path ----
597
+ var hangForever = function () { return new Promise(function () {}); };
598
+ var timeoutSrv = b.mail.server.mx.create({
599
+ tlsContext: ctx, profile: "permissive", localDomains: ["example.com"],
600
+ guardEnvelope: { mode: "enforce", dnsLookup: hangForever, timeoutMs: 250 }, // allow:raw-time-literal — test-only short budget
601
+ });
602
+ var timeoutInfo = await timeoutSrv.listen({ port: 0, address: "127.0.0.1" });
603
+ try {
604
+ var tSock = nodeNet.connect(timeoutInfo.port, "127.0.0.1");
605
+ await new Promise(function (r) { tSock.once("connect", r); });
606
+ await _readGreeting(tSock);
607
+ await _sendCommand(tSock, "EHLO sender.example.com");
608
+ var timedOut = await _transact(tSock, "s@external.com",
609
+ "From: s@external.com\r\nSubject: hi\r\n\r\nhello\r\n");
610
+ check("guardEnvelope: hanging resolver hits timeoutMs → 451 4.7.0",
611
+ /^451 4\.7\.0/.test(timedOut));
612
+ tSock.destroy();
613
+ } finally { await timeoutSrv.close({ timeoutMs: 1000 }); } // allow:raw-time-literal — test-only short drain
614
+
615
+ var acceptSrv = b.mail.server.mx.create({
616
+ tlsContext: ctx, profile: "permissive", localDomains: ["example.com"],
617
+ guardEnvelope: { mode: "enforce", onTemperror: "accept", dnsLookup: servfail },
618
+ });
619
+ var acceptInfo = await acceptSrv.listen({ port: 0, address: "127.0.0.1" });
620
+ try {
621
+ var aSock = nodeNet.connect(acceptInfo.port, "127.0.0.1");
622
+ await new Promise(function (r) { aSock.once("connect", r); });
623
+ await _readGreeting(aSock);
624
+ await _sendCommand(aSock, "EHLO sender.example.com");
625
+ var accepted = await _transact(aSock, "s@external.com",
626
+ "From: s@external.com\r\nSubject: hi\r\n\r\nhello\r\n");
627
+ check("guardEnvelope: onTemperror accept admits when DNS is down",
628
+ /^250 /.test(accepted));
629
+ aSock.destroy();
630
+ } finally { await acceptSrv.close({ timeoutMs: 1000 }); } // allow:raw-time-literal — test-only short drain
631
+ }
632
+
429
633
  async function run() {
430
634
  testSurface();
431
635
  testCreateRequiresTlsContext();
@@ -438,6 +642,7 @@ async function run() {
438
642
  await testStrictProfileRequiresStartTls();
439
643
  await testConnectionGates();
440
644
  await testGateOverStartTls();
645
+ await testGuardEnvelopeGate();
441
646
  }
442
647
 
443
648
  module.exports = { run: run };
@@ -291,6 +291,78 @@ async function testMoveMessages() {
291
291
  } finally { _teardown(fx); }
292
292
  }
293
293
 
294
+ async function testSearch() {
295
+ var fx = await _setupStore("search");
296
+ try {
297
+ var store = b.mailStore.create({ backend: fx.db });
298
+ var m1 = store.appendMessage("INBOX",
299
+ _msg(["From: alice@example.com", "To: bob@example.com",
300
+ "Subject: Kubernetes deploy notes", "Message-Id: <s1@x>"],
301
+ "the kubernetes cluster is healthy"));
302
+ var m2 = store.appendMessage("INBOX",
303
+ _msg(["From: carol@example.com", "To: bob@example.com",
304
+ "Subject: Lunch plans", "Message-Id: <s2@x>"],
305
+ "want to grab lunch tomorrow"));
306
+
307
+ // Body/subject text MATCH (FTS5 IN-subquery via whereMatch).
308
+ var r1 = store.search("INBOX", { text: "kubernetes" });
309
+ check("search: text=kubernetes hits only m1",
310
+ r1.rows.length === 1 && r1.rows[0].objectid === m1.objectid && !!r1.matchExpr);
311
+
312
+ // Subject-only column MATCH.
313
+ var r2 = store.search("INBOX", { subject: "lunch" });
314
+ check("search: subject=lunch hits only m2",
315
+ r2.rows.length === 1 && r2.rows[0].objectid === m2.objectid);
316
+
317
+ // Address column MATCH (from/to share addr_toks).
318
+ var r3 = store.search("INBOX", { from: "alice@example.com" });
319
+ check("search: from=alice hits only m1",
320
+ r3.rows.length === 1 && r3.rows[0].objectid === m1.objectid);
321
+
322
+ // No surviving tokens → empty result set, not a fallback.
323
+ var r4 = store.search("INBOX", { text: "nonexistentterm" });
324
+ check("search: no-match returns zero rows", r4.rows.length === 0);
325
+
326
+ // No text-side filter → falls through to the modseq cursor.
327
+ var r5 = store.search("INBOX", {});
328
+ check("search: no-filter falls back to modseq cursor", r5.rows.length === 2);
329
+ } finally { _teardown(fx); }
330
+ }
331
+
332
+ async function testHardExpunge() {
333
+ var fx = await _setupStore("expunge");
334
+ try {
335
+ var store = b.mailStore.create({ backend: fx.db });
336
+ var m1 = store.appendMessage("INBOX",
337
+ _msg(["From: a@x", "Subject: keep", "Message-Id: <e1@x>"], "keep me"));
338
+ var m2 = store.appendMessage("INBOX",
339
+ _msg(["From: a@x", "Subject: drop", "Message-Id: <e2@x>"], "drop me"));
340
+ var q0 = store.quota("INBOX");
341
+ check("expunge-pre: 2 messages", q0.usedCount === 2);
342
+
343
+ // hardExpunge resolves the candidate set through json_each(?).
344
+ var ex = store.hardExpunge("INBOX", [m2.objectid]);
345
+ check("expunge: m2 deleted", ex.deleted.length === 1 && ex.deleted[0] === m2.objectid);
346
+ check("expunge: nothing refused", ex.refused.length === 0);
347
+
348
+ // Row + FTS row + quota all reflect the delete.
349
+ check("expunge: m2 fetch is null", store.fetchByObjectId("INBOX", m2.objectid) === null);
350
+ check("expunge: m1 still present", store.fetchByObjectId("INBOX", m1.objectid) !== null);
351
+ check("expunge: FTS no longer matches m2",
352
+ store.search("INBOX", { subject: "drop" }).rows.length === 0);
353
+ var q1 = store.quota("INBOX");
354
+ check("expunge: quota decremented to 1", q1.usedCount === 1);
355
+
356
+ // A legal-held message is refused, not deleted.
357
+ store.setLegalHold([m1.objectid], { hold: true });
358
+ var ex2 = store.hardExpunge("INBOX", [m1.objectid]);
359
+ check("expunge: legal-held refused",
360
+ ex2.deleted.length === 0 && ex2.refused.length === 1 &&
361
+ ex2.refused[0].reason === "legal-hold");
362
+ check("expunge: held m1 still present", store.fetchByObjectId("INBOX", m1.objectid) !== null);
363
+ } finally { _teardown(fx); }
364
+ }
365
+
294
366
  async function testRefusesBadBackend() {
295
367
  var threw = null;
296
368
  try { b.mailStore.create({ backend: {} }); }
@@ -312,6 +384,8 @@ async function run() {
312
384
  await testRefusesBadInput();
313
385
  await testCustomFolder();
314
386
  await testMoveMessages();
387
+ await testSearch();
388
+ await testHardExpunge();
315
389
  await testRefusesBadBackend();
316
390
  }
317
391
 
@@ -455,6 +455,9 @@ async function run() {
455
455
  // ---- RFC 9101 + RFC 9126 §3 — PAR with a signed request object ----
456
456
  await _testParRequestObject();
457
457
  await _testParPlainUnchanged();
458
+
459
+ // ---- OIDC Back-Channel Logout — bounded token + header parse ----
460
+ await _testBackchannelLogoutOversized();
458
461
  }
459
462
 
460
463
  // PAR carrying a signed request object: the form body MUST hold `request=`
@@ -481,6 +484,9 @@ async function _testParRequestObject() {
481
484
  });
482
485
  var rv = await oa.pushAuthorizationRequest({
483
486
  signedRequestObject: { key: kp.privateKey, kid: "c1" },
487
+ authorizationDetails: [
488
+ { type: "payment_initiation", actions: ["initiate"], locations: ["https://rs.example/pay"] },
489
+ ],
484
490
  });
485
491
  check("oauth.PAR+RO: returns a request_uri", rv.requestUri === "urn:ietf:params:oauth:request_uri:abc123");
486
492
  check("oauth.PAR+RO: flags requestObjectSent", rv.requestObjectSent === true);
@@ -506,9 +512,46 @@ async function _testParRequestObject() {
506
512
  parsed.params.redirect_uri === "https://rp.example/cb" && parsed.params.scope === "openid profile" &&
507
513
  parsed.params.code_challenge_method === "S256" && typeof parsed.params.code_challenge === "string");
508
514
  check("oauth.PAR+RO: request object aud is the AS issuer", parsed.claims.aud === issuer);
515
+ // RFC 9101/9396 — authorization_details travels as a JSON ARRAY claim,
516
+ // not a JSON string (a conforming AS rejects the string-valued claim).
517
+ check("oauth.PAR+RO: authorization_details is a native array claim",
518
+ Array.isArray(parsed.params.authorization_details) &&
519
+ parsed.params.authorization_details.length === 1 &&
520
+ parsed.params.authorization_details[0].type === "payment_initiation");
509
521
  } finally { server.close(); }
510
522
  }
511
523
 
524
+ // RFC OIDC Back-Channel Logout — the pre-verify header parse + the token
525
+ // split run on an attacker-reachable, not-yet-signature-checked token, so
526
+ // the token must be length-bounded before either.
527
+ async function _testBackchannelLogoutOversized() {
528
+ var oa = b.auth.oauth.create({
529
+ issuer: "https://idp.example",
530
+ clientId: "rp-bcl",
531
+ redirectUri: "https://rp.example/cb",
532
+ isOidc: true,
533
+ allowHttp: true,
534
+ allowInternal: true,
535
+ });
536
+ // A logout_token far larger than the response-bytes cap must be refused
537
+ // BEFORE the split / base64url header decode.
538
+ var huge = "a".repeat(300000) + ".b.c";
539
+ var threw = null;
540
+ try { await oa.verifyBackchannelLogoutToken(huge); }
541
+ catch (e) { threw = e; }
542
+ check("oauth.backchannelLogout: oversized token refused before parse",
543
+ threw && threw.code === "auth-oauth/logout-token-too-large");
544
+
545
+ // A well-formed-shape but bogus header still parses through safeJson
546
+ // (size-bounded) and fails downstream, not with an unbounded-parse crash.
547
+ var bogusHeader = Buffer.from("{not json", "utf8").toString("base64url");
548
+ var threw2 = null;
549
+ try { await oa.verifyBackchannelLogoutToken(bogusHeader + ".payload.sig"); }
550
+ catch (e) { threw2 = e; }
551
+ check("oauth.backchannelLogout: malformed header refused (bounded parse)",
552
+ threw2 && threw2.code === "auth-oauth/bad-logout-header");
553
+ }
554
+
512
555
  // PAR WITHOUT a signed request object: byte-for-byte the prior plain-form
513
556
  // behavior — authorization parameters are bare form params, no `request=`.
514
557
  async function _testParPlainUnchanged() {