@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
@@ -43,6 +43,7 @@ var cluster = require("./cluster");
43
43
  var cryptoField = require("./crypto-field");
44
44
  var requestHelpers = require("./request-helpers");
45
45
  var validateOpts = require("./validate-opts");
46
+ var sql = require("./sql");
46
47
  var C = require("./constants");
47
48
  var numericChecks = require("./numeric-checks");
48
49
  var { ApiKeyError } = require("./framework-error");
@@ -53,16 +54,28 @@ function _emitEvent(n, v, l) { observability().safeEvent(n, v, l || {}); }
53
54
 
54
55
  var _err = ApiKeyError.factory;
55
56
 
56
- var TABLE = "_blamejs_api_keys";
57
- // Pre-quoted form for SQL interpolation. Defense-in-depth: even though
58
- // our constant is bare-identifier-shaped, every interpolation site uses
59
- // the wrapped form so a future rename to a reserved-word or
60
- // whitespace-bearing name would still resolve correctly.
61
- var Q_TABLE = '"' + TABLE + '"';
62
-
63
- // Column order used for INSERT kept as a constant so the placeholders
64
- // list and the values list stay in sync. Must match _blamejs_api_keys'
65
- // schema in db.js (single-node) and framework-schema.js (cluster mode).
57
+ // Logical framework table name. Self-mapped in LOCAL_TO_EXTERNAL, so it is
58
+ // passed BARE to b.sql: clusterStorage.execute rewrites it to the configured
59
+ // prefix and placeholderizes the `?` markers, so one query text runs against
60
+ // the local SQLite single-node backend and the operator's external DB in
61
+ // cluster mode.
62
+ var TABLE = "_blamejs_api_keys"; // allow:hand-rolled-sql bare logical name, passed to b.sql for clusterStorage rewrite
63
+
64
+ // b.sql opts for every _blamejs_api_keys statement: thread the ACTIVE backend
65
+ // dialect (clusterStorage.dialect() "sqlite" single-node, "postgres" |
66
+ // "mysql" in cluster mode) so the emitted identifier quoting + dialect idioms
67
+ // match the backend the SQL dispatches to. Defaulting to "sqlite" works on
68
+ // Postgres only by accident (both double-quote identifiers) and emits the
69
+ // wrong quoting on MySQL, so this is the canonical resolver threaded into
70
+ // b.sql. clusterStorage.execute still rewrites the bare table name +
71
+ // translates `?` placeholders at dispatch; this controls only the builder-
72
+ // side quoting + idiom selection. The table name stays BARE (no quoteName)
73
+ // so clusterStorage's prefix rewrite still fires.
74
+ function _sqlOpts() { return { dialect: clusterStorage.dialect() }; }
75
+
76
+ // Column order used for INSERT — kept as a constant so the column list and
77
+ // the row object stay in sync. Must match _blamejs_api_keys' schema in
78
+ // db.js (single-node) and framework-schema.js (cluster mode).
66
79
  var COLS = [
67
80
  "id", "namespace", "ownerId", "ownerIdHash", "secretHash",
68
81
  "secondarySecretHash", "secondaryExpiresAt",
@@ -305,10 +318,11 @@ function create(opts) {
305
318
  );
306
319
  }
307
320
 
308
- function _selectAll() {
309
- return "SELECT id, namespace, ownerId, ownerIdHash, secretHash, " +
310
- "secondarySecretHash, secondaryExpiresAt, " +
311
- "scopes, metadata, createdAt, expiresAt, revokedAt, lastUsedAt, prefix FROM " + Q_TABLE;
321
+ // Fresh SELECT builder over the full column set. BARE logical table name
322
+ // (_blamejs_api_keys) clusterStorage rewrites it to the configured
323
+ // prefix and placeholderizes. Callers chain the WHERE family + .toSql().
324
+ function _selectBuilder() {
325
+ return sql.select(TABLE, _sqlOpts()).columns(COLS); // allow:hand-rolled-sql — bare logical name for clusterStorage rewrite
312
326
  }
313
327
 
314
328
  function _scrubRecord(row) {
@@ -369,14 +383,13 @@ function create(opts) {
369
383
  lastUsedAt: null,
370
384
  prefix: prefix,
371
385
  });
372
- var values = COLS.map(function (c) { return sealed[c]; });
373
- var placeholders = COLS.map(function () { return "?"; }).join(", ");
374
- var quoted = COLS.map(function (c) { return '"' + c + '"'; }).join(", ");
375
-
376
- await clusterStorage.execute(
377
- "INSERT INTO " + Q_TABLE + " (" + quoted + ") VALUES (" + placeholders + ")",
378
- values
379
- );
386
+ var insertRow = {};
387
+ for (var ci = 0; ci < COLS.length; ci++) insertRow[COLS[ci]] = sealed[COLS[ci]];
388
+ var insertBuilt = sql.insert(TABLE, _sqlOpts()) // allow:hand-rolled-sql bare logical name for clusterStorage rewrite
389
+ .columns(COLS)
390
+ .values(insertRow)
391
+ .toSql();
392
+ await clusterStorage.execute(insertBuilt.sql, insertBuilt.params);
380
393
 
381
394
  _emit("apikey.issue", {
382
395
  actor: _actor(issueOpts, issueOpts.ownerId),
@@ -402,10 +415,8 @@ function create(opts) {
402
415
  if (parsed.prefix !== prefix || parsed.namespace !== namespace) return null;
403
416
 
404
417
  var compositeId = _composedId(namespace, parsed.idHex);
405
- var row = await clusterStorage.executeOne(
406
- _selectAll() + " WHERE id = ?",
407
- [compositeId]
408
- );
418
+ var verifyBuilt = _selectBuilder().where("id", compositeId).toSql();
419
+ var row = await clusterStorage.executeOne(verifyBuilt.sql, verifyBuilt.params);
409
420
  if (!row) {
410
421
  if (auditFailures) {
411
422
  _emit("apikey.verify", {
@@ -472,13 +483,55 @@ function create(opts) {
472
483
  return null;
473
484
  }
474
485
 
475
- if (trackLastUsedAt && cluster.isLeader()) {
476
- try {
477
- await clusterStorage.execute(
478
- "UPDATE " + Q_TABLE + " SET lastUsedAt = ? WHERE id = ?",
479
- [nowMs, compositeId]
480
- );
481
- } catch (_e) { /* best-effort; verify success not blocked by lastUsed update */ }
486
+ // Leader-gated best-effort writes on a successful verify: bump
487
+ // lastUsedAt when tracked, and transparently re-hash the stored secret
488
+ // when its envelope no longer matches the active algorithm — the
489
+ // rotate-on-next-verify that credentialHash documents but, until now,
490
+ // no consumer wired. Primary match only: the secondary (graceful-
491
+ // rotation) slot is not the active secret, so it must not overwrite
492
+ // secretHash. The whole block is best-effort the credential already
493
+ // verified under the stored hash and stays valid even if the write
494
+ // fails; the row re-upgrades on the next leader verify.
495
+ if (cluster.isLeader()) {
496
+ var touchFields = trackLastUsedAt ? { lastUsedAt: nowMs } : null;
497
+ var didRehash = false;
498
+ if (primaryMatch && credentialHash.needsRehash(row.secretHash, { algo: hashAlgo })) {
499
+ try {
500
+ var freshSecretHash = await credentialHash.hash(parsed.secretHex, { algo: hashAlgo });
501
+ touchFields = touchFields || {};
502
+ touchFields.secretHash = freshSecretHash;
503
+ didRehash = true;
504
+ } catch (_e) { /* re-hash is best-effort; verify success stands */ }
505
+ }
506
+ if (touchFields) {
507
+ try {
508
+ var touchQb = sql.update(TABLE, _sqlOpts()) // allow:hand-rolled-sql — bare logical name for clusterStorage rewrite
509
+ .set(touchFields)
510
+ .where("id", compositeId);
511
+ if (didRehash) {
512
+ // Compare-and-swap on the exact hash we verified against: only land
513
+ // the re-hash if the stored primary is STILL that value. A verify
514
+ // that races rotate()/hardRotate (which already installed a new
515
+ // secretHash) must not clobber the rotated secret back to the old
516
+ // one — the predicate then matches no rows and the upgrade no-ops,
517
+ // which is correct because the row is already on a fresh hash.
518
+ touchQb.where("secretHash", row.secretHash);
519
+ }
520
+ var touchBuilt = touchQb.toSql();
521
+ var touchResult = await clusterStorage.execute(touchBuilt.sql, touchBuilt.params);
522
+ // Only record the migration when the CAS actually swapped a row (a
523
+ // rowCount of 0 means a concurrent rotation won the race).
524
+ if (didRehash && !(touchResult && touchResult.rowCount === 0)) {
525
+ _emitEvent("apikey.secret_rehash", 1, { namespace: namespace, algo: hashAlgo });
526
+ _emit("apikey.secret_rehash", {
527
+ actor: _actor(verifyOpts, rowOwnerId),
528
+ resource: { kind: "apikey", id: compositeId },
529
+ outcome: "success",
530
+ metadata: { algo: hashAlgo },
531
+ });
532
+ }
533
+ } catch (_e) { /* best-effort; verify success not blocked by the write */ }
534
+ }
482
535
  }
483
536
 
484
537
  if (auditSuccess) {
@@ -501,10 +554,12 @@ function create(opts) {
501
554
  if (typeof idHex !== "string" || idHex.length === 0) return false;
502
555
  var compositeId = _composedId(namespace, idHex);
503
556
  var nowMs = clock();
504
- var result = await clusterStorage.execute(
505
- "UPDATE " + Q_TABLE + " SET revokedAt = ? WHERE id = ? AND revokedAt IS NULL",
506
- [nowMs, compositeId]
507
- );
557
+ var revokeBuilt = sql.update(TABLE, _sqlOpts()) // allow:hand-rolled-sql — bare logical name for clusterStorage rewrite
558
+ .set({ revokedAt: nowMs })
559
+ .where("id", compositeId)
560
+ .whereNull("revokedAt")
561
+ .toSql();
562
+ var result = await clusterStorage.execute(revokeBuilt.sql, revokeBuilt.params);
508
563
  var changed = (result.rowCount || 0) > 0;
509
564
  if (changed) {
510
565
  _emit("apikey.revoke", {
@@ -542,10 +597,8 @@ function create(opts) {
542
597
  }
543
598
 
544
599
  var compositeId = _composedId(namespace, idHex);
545
- var existing = await clusterStorage.executeOne(
546
- _selectAll() + " WHERE id = ?",
547
- [compositeId]
548
- );
600
+ var rotateSelBuilt = _selectBuilder().where("id", compositeId).toSql();
601
+ var existing = await clusterStorage.executeOne(rotateSelBuilt.sql, rotateSelBuilt.params);
549
602
  if (!existing) {
550
603
  throw _err("NOT_FOUND", "apiKey.rotate: id '" + idHex + "' not found in namespace '" + namespace + "'");
551
604
  }
@@ -558,19 +611,27 @@ function create(opts) {
558
611
 
559
612
  if (gracePeriodMs > 0) {
560
613
  // Move current hash → secondary slot, install new hash as primary.
561
- await clusterStorage.execute(
562
- "UPDATE " + Q_TABLE + " SET secretHash = ?, " +
563
- "secondarySecretHash = ?, secondaryExpiresAt = ? WHERE id = ?",
564
- [newHash, existing.secretHash, nowMs + gracePeriodMs, compositeId]
565
- );
614
+ var graceBuilt = sql.update(TABLE, _sqlOpts()) // allow:hand-rolled-sql — bare logical name for clusterStorage rewrite
615
+ .set({
616
+ secretHash: newHash,
617
+ secondarySecretHash: existing.secretHash,
618
+ secondaryExpiresAt: nowMs + gracePeriodMs,
619
+ })
620
+ .where("id", compositeId)
621
+ .toSql();
622
+ await clusterStorage.execute(graceBuilt.sql, graceBuilt.params);
566
623
  } else {
567
624
  // Hard cutover — old secret stops working immediately. Clears
568
- // any prior secondary slot too.
569
- await clusterStorage.execute(
570
- "UPDATE " + Q_TABLE + " SET secretHash = ?, " +
571
- "secondarySecretHash = NULL, secondaryExpiresAt = NULL WHERE id = ?",
572
- [newHash, compositeId]
573
- );
625
+ // any prior secondary slot too (bound NULL via the set map).
626
+ var cutoverBuilt = sql.update(TABLE, _sqlOpts()) // allow:hand-rolled-sql — bare logical name for clusterStorage rewrite
627
+ .set({
628
+ secretHash: newHash,
629
+ secondarySecretHash: null,
630
+ secondaryExpiresAt: null,
631
+ })
632
+ .where("id", compositeId)
633
+ .toSql();
634
+ await clusterStorage.execute(cutoverBuilt.sql, cutoverBuilt.params);
574
635
  }
575
636
 
576
637
  _emit("apikey.rotate", {
@@ -598,17 +659,30 @@ function create(opts) {
598
659
  var lookup = cryptoField.lookupHash(TABLE, "ownerId", ownerId);
599
660
  if (!lookup) {
600
661
  throw _err("MISCONFIGURED",
601
- "_blamejs_api_keys schema is missing the ownerIdHash derived hash — framework misconfigured");
662
+ TABLE + " schema is missing the ownerIdHash derived hash — framework misconfigured");
602
663
  }
603
- var sql = _selectAll() + " WHERE namespace = ? AND ownerIdHash = ?";
604
- var params = [namespace, lookup.value];
605
- if (!includeRevoked) sql += " AND revokedAt IS NULL";
664
+ // Dual-read across the keyed-MAC flip: match the active digest AND the
665
+ // legacy salted-sha3 digest a pre-v0.15.0 row carries (whereIn with a
666
+ // single value emits `IN (?)`, equivalent to `=`).
667
+ var ownerHashes = [lookup.value];
668
+ if (lookup.legacyValue != null && lookup.legacyValue !== lookup.value) {
669
+ ownerHashes.push(lookup.legacyValue);
670
+ }
671
+ var listQb = _selectBuilder()
672
+ .where("namespace", namespace)
673
+ .whereIn("ownerIdHash", ownerHashes);
674
+ if (!includeRevoked) listQb.whereNull("revokedAt");
606
675
  if (!includeExpired) {
607
- sql += " AND (expiresAt IS NULL OR expiresAt >= ?)";
608
- params.push(clock());
676
+ var nowForExpiry = clock();
677
+ // (expiresAt IS NULL OR expiresAt >= now) — an OR group ANDed onto
678
+ // the chain so the optional clause keeps its own precedence.
679
+ listQb.whereGroup(function (g) {
680
+ g.whereNull("expiresAt").orWhereOp("expiresAt", ">=", nowForExpiry);
681
+ });
609
682
  }
610
- sql += " ORDER BY createdAt DESC";
611
- var rows = await clusterStorage.execute(sql, params);
683
+ listQb.orderBy("createdAt", "desc");
684
+ var listBuilt = listQb.toSql();
685
+ var rows = await clusterStorage.execute(listBuilt.sql, listBuilt.params);
612
686
  var list = (rows.rows || []).map(_scrubRecord);
613
687
  _emitEvent("apikey.list", 1, { namespace: namespace, count: list.length });
614
688
  // Read-access audit: "who listed whose keys at time T" — gated by
@@ -635,10 +709,8 @@ function create(opts) {
635
709
  async function getById(idHex, getOpts) {
636
710
  if (typeof idHex !== "string" || idHex.length === 0) return null;
637
711
  var compositeId = _composedId(namespace, idHex);
638
- var row = await clusterStorage.executeOne(
639
- _selectAll() + " WHERE id = ?",
640
- [compositeId]
641
- );
712
+ var getBuilt = _selectBuilder().where("id", compositeId).toSql();
713
+ var row = await clusterStorage.executeOne(getBuilt.sql, getBuilt.params);
642
714
  var record = _scrubRecord(row);
643
715
  _emitEvent("apikey.get", 1,
644
716
  { namespace: namespace, found: record !== null });
@@ -659,13 +731,24 @@ function create(opts) {
659
731
  // Compliance auditors expect "key X was purged at time T" — a count-
660
732
  // only audit is too coarse for forensic reconstruction. Cost is one
661
733
  // extra round-trip per purge call which runs on a schedule (not
662
- // request-rate), so the cost is irrelevant.
663
- var idRows = await clusterStorage.execute(
664
- "SELECT id FROM " + Q_TABLE + " WHERE namespace = ? AND " +
665
- "((revokedAt IS NOT NULL AND revokedAt < ?) OR " +
666
- " (expiresAt IS NOT NULL AND expiresAt < ?))",
667
- [namespace, threshold, threshold]
668
- );
734
+ // request-rate), so the cost is irrelevant. The purge predicate
735
+ // (namespace match + an OR of the two "past-threshold" age groups) is
736
+ // applied identically to the SELECT and the DELETE via _applyPurgeWhere.
737
+ function _applyPurgeWhere(qb) {
738
+ return qb
739
+ .where("namespace", namespace)
740
+ .whereGroup(function (g) {
741
+ g.whereGroup(function (a) {
742
+ a.whereNotNull("revokedAt").where("revokedAt", "<", threshold);
743
+ }).orWhereGroup(function (b2) {
744
+ b2.whereNotNull("expiresAt").where("expiresAt", "<", threshold);
745
+ });
746
+ });
747
+ }
748
+ var purgeSelBuilt = _applyPurgeWhere(
749
+ sql.select(TABLE, _sqlOpts()).columns(["id"]) // allow:hand-rolled-sql — bare logical name for clusterStorage rewrite
750
+ ).toSql();
751
+ var idRows = await clusterStorage.execute(purgeSelBuilt.sql, purgeSelBuilt.params);
669
752
  var purgedCompositeIds = (idRows.rows || []).map(function (r) { return r.id; });
670
753
 
671
754
  if (purgedCompositeIds.length === 0) {
@@ -673,12 +756,10 @@ function create(opts) {
673
756
  return 0;
674
757
  }
675
758
 
676
- var result = await clusterStorage.execute(
677
- "DELETE FROM " + Q_TABLE + " WHERE namespace = ? AND " +
678
- "((revokedAt IS NOT NULL AND revokedAt < ?) OR " +
679
- " (expiresAt IS NOT NULL AND expiresAt < ?))",
680
- [namespace, threshold, threshold]
681
- );
759
+ var purgeDelBuilt = _applyPurgeWhere(
760
+ sql.delete(TABLE, _sqlOpts()) // allow:hand-rolled-sql bare logical name for clusterStorage rewrite
761
+ ).toSql();
762
+ var result = await clusterStorage.execute(purgeDelBuilt.sql, purgeDelBuilt.params);
682
763
  var count = result.rowCount || purgedCompositeIds.length;
683
764
 
684
765
  _emit("apikey.purge", {
@@ -167,6 +167,30 @@ function fsyncDir(dirPath) {
167
167
  function _fsync(fd) { return fsync(fd); }
168
168
  function _fsyncDir(dirPath) { return fsyncDir(dirPath); }
169
169
 
170
+ // Exclusive, no-follow create of the sibling temp file that every
171
+ // atomic write stages bytes into before the rename. CWE-377
172
+ // (insecure temporary file) / CWE-59 (symlink-following): the legacy
173
+ // "w" flag is O_WRONLY|O_CREAT|O_TRUNC — it happily opens (and
174
+ // truncates, or writes through) a file an attacker pre-created at the
175
+ // temp path, including a symlink pointing at a victim file the process
176
+ // can write but the attacker can't. O_EXCL makes the open fail with
177
+ // EEXIST if anything already exists at tmpPath, so a planted file /
178
+ // symlink / FIFO is refused instead of followed; O_NOFOLLOW rejects a
179
+ // symlink in the final path component on platforms that define it
180
+ // (Windows leaves it undefined, hence the `|| 0`). The temp name
181
+ // already carries a CSPRNG token (generateToken), so EEXIST is a
182
+ // hostile-collision signal, not a benign retry. The fd is returned for
183
+ // the caller to write + fsync; mode is applied at create time so the
184
+ // bytes are never world-readable even briefly.
185
+ function _openExclTemp(tmpPath, fileMode) {
186
+ return nodeFs.openSync(
187
+ tmpPath,
188
+ nodeFs.constants.O_WRONLY | nodeFs.constants.O_CREAT |
189
+ nodeFs.constants.O_EXCL | (nodeFs.constants.O_NOFOLLOW || 0),
190
+ fileMode
191
+ );
192
+ }
193
+
170
194
  /**
171
195
  * @primitive b.atomicFile.ensureDir
172
196
  * @signature b.atomicFile.ensureDir(dirPath, mode)
@@ -392,6 +416,34 @@ function conflictPath(originalPath, opts) {
392
416
  * );
393
417
  * // → { bytesWritten: 7, hash: "<sha3-512 hex>" }
394
418
  */
419
+ // Synchronous bounded sleep (writeSync is a sync primitive, so no await).
420
+ // Uses Atomics.wait on a throwaway shared buffer; falls back to a short spin
421
+ // if SharedArrayBuffer is unavailable.
422
+ function _sleepSync(ms) {
423
+ try { Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms); return; }
424
+ catch (_e) { /* fall through to spin */ }
425
+ var end = Date.now() + ms;
426
+ while (Date.now() < end) { /* spin */ }
427
+ }
428
+
429
+ // Atomic rename with a bounded retry on Windows-transient lock errors. On
430
+ // Windows a rename target is briefly held by AV / the search indexer / a
431
+ // file-sync client (Dropbox, OneDrive), surfacing as EPERM / EACCES / EBUSY
432
+ // even though the freshly-written temp file is fine; the lock clears in a few
433
+ // ms. POSIX rename is atomic and never hits this, so the first attempt
434
+ // succeeds there. Surface the error if it is not transient or persists.
435
+ function _renameWithRetry(from, to) {
436
+ var delays = [0, 5, 15, 40, 100];
437
+ for (var i = 0; i < delays.length; i += 1) {
438
+ if (delays[i] > 0) _sleepSync(delays[i]);
439
+ try { nodeFs.renameSync(from, to); return; }
440
+ catch (e) {
441
+ var transient = e && (e.code === "EPERM" || e.code === "EACCES" || e.code === "EBUSY");
442
+ if (!transient || i === delays.length - 1) throw e;
443
+ }
444
+ }
445
+ }
446
+
395
447
  function writeSync(filepath, data, opts) {
396
448
  opts = Object.assign({}, DEFAULTS, opts || {});
397
449
  var buf = safeBuffer.toBuffer(data, {
@@ -406,7 +458,7 @@ function writeSync(filepath, data, opts) {
406
458
  var tmpPath = filepath + ".tmp-" + generateToken(C.BYTES.bytes(8));
407
459
  var renamed = false;
408
460
  try {
409
- var fd = nodeFs.openSync(tmpPath, "w", opts.fileMode);
461
+ var fd = _openExclTemp(tmpPath, opts.fileMode);
410
462
  try {
411
463
  var pos = 0;
412
464
  while (pos < buf.length) {
@@ -416,7 +468,7 @@ function writeSync(filepath, data, opts) {
416
468
  } finally {
417
469
  try { nodeFs.closeSync(fd); } catch (_e) { /* already closed? */ }
418
470
  }
419
- nodeFs.renameSync(tmpPath, filepath);
471
+ _renameWithRetry(tmpPath, filepath);
420
472
  renamed = true;
421
473
  _fsyncDir(dir);
422
474
  } finally {
@@ -530,7 +582,7 @@ async function write(filepath, data, opts) {
530
582
  var tmpPath = filepath + ".tmp-" + generateToken(C.BYTES.bytes(8));
531
583
  var renamed = false;
532
584
  try {
533
- var fd = nodeFs.openSync(tmpPath, "w", opts.fileMode);
585
+ var fd = _openExclTemp(tmpPath, opts.fileMode);
534
586
  try {
535
587
  var pos = 0;
536
588
  while (pos < buf.length) {
@@ -659,9 +711,15 @@ function _readSyncCore(filepath, opts) {
659
711
  // can't swap the file between size-check and read because the fd
660
712
  // is anchored to the original inode. ENOENT surfaces from open()
661
713
  // rather than the previous existsSync() pre-check.
714
+ //
715
+ // The third argument pins an owner-only mode (0o600). The flag is
716
+ // read-only ("r" → O_RDONLY, no O_CREAT) so the mode is inert on
717
+ // disk, but specifying it keeps this open out of the insecure-temp-
718
+ // file class (CWE-377): the read can never create a world/group-
719
+ // accessible file even when `filepath` is rooted under a temp dir.
662
720
  var fd;
663
721
  try {
664
- fd = nodeFs.openSync(filepath, "r");
722
+ fd = nodeFs.openSync(filepath, "r", 0o600);
665
723
  } catch (openErr) {
666
724
  if (openErr && openErr.code === "ENOENT") {
667
725
  var e = new AtomicFileError("file not found: " + filepath, "atomic-file/not-found");
@@ -35,8 +35,23 @@
35
35
  */
36
36
  var canonicalJson = require("./canonical-json");
37
37
  var C = require("./constants");
38
+ var clusterStorage = require("./cluster-storage");
39
+ var frameworkSchema = require("./framework-schema");
40
+ var sql = require("./sql");
38
41
  var { sha3Hash } = require("./crypto");
39
42
 
43
+ // b.sql opts for the chain read SQL these primitives compose. The reader
44
+ // (queryAllAsync / queryOneAsync, normally clusterStorage.execute*) rewrites
45
+ // the bare framework table name + translates `?` placeholders at dispatch,
46
+ // but the IDENTIFIER QUOTING + ORDER-BY column reference are baked into the
47
+ // b.sql output at build time — so they must carry the ACTIVE backend dialect
48
+ // (clusterStorage.dialect() — "sqlite" single-node, "postgres" | "mysql" in
49
+ // cluster mode). Defaulting to "sqlite" double-quotes `monotonicCounter`,
50
+ // which MySQL reads as a STRING LITERAL: `ORDER BY '<constant>'` imposes no
51
+ // ordering, so verifyChain walks the rows out of order and falsely reports a
52
+ // chain break. Backtick-quoting on MySQL makes it an identifier again.
53
+ function _sqlOpts() { return { dialect: clusterStorage.dialect() }; }
54
+
40
55
  // SHA3-512 outputs 64 bytes; routed through C.BYTES so the file's byte
41
56
  // arithmetic has one source of truth. Hex-encoded width is twice the
42
57
  // byte count.
@@ -140,11 +155,20 @@ function computeRowHash(prevHash, rowFields, nonce) {
140
155
  * // → { prevHash: "<128-char hex>", counter: 4217 }
141
156
  */
142
157
  async function getChainTip(queryOneAsync, tableName) {
143
- var row = await queryOneAsync(
144
- 'SELECT rowHash, monotonicCounter FROM "' + tableName + '" ' +
145
- "ORDER BY monotonicCounter DESC LIMIT 1"
146
- );
158
+ // Emit a BARE logical table name — the operator-supplied reader routes
159
+ // through clusterStorage, which rewrites bare framework names to the
160
+ // configured-prefix form and placeholderizes. b.sql quotes the camelCase
161
+ // columns + runs the output validator.
162
+ var built = sql.select(tableName, _sqlOpts())
163
+ .columns(["rowHash", "monotonicCounter"])
164
+ .orderBy("monotonicCounter", "desc")
165
+ .limit(1)
166
+ .toSql();
167
+ var row = await queryOneAsync(built.sql, built.params);
147
168
  if (!row) return { prevHash: ZERO_HASH, counter: 0 };
169
+ // Normalize driver shape (Postgres returns BIGINT monotonicCounter as a
170
+ // string) so callers get a numeric counter on every backend.
171
+ frameworkSchema.coerceRow(row);
148
172
  return { prevHash: row.rowHash, counter: row.monotonicCounter };
149
173
  }
150
174
 
@@ -186,10 +210,15 @@ async function verifyChain(queryAllAsync, tableName, opts) {
186
210
  if (tableName === "audit_log") {
187
211
  var anchor;
188
212
  try {
189
- anchor = await queryAllAsync(
190
- "SELECT lastPurgedCounter, lastPurgedRowHash FROM _blamejs_audit_purge_anchor " +
191
- "WHERE scope = 'audit'"
192
- );
213
+ // External-only table whose LOGICAL name IS the `_blamejs_`-prefixed
214
+ // name (self-mapped in LOCAL_TO_EXTERNAL), passed bare so the reader's
215
+ // clusterStorage rewrites it; the 'audit' scope binds as a ? param.
216
+ // allow:hand-rolled-sql — bare logical key.
217
+ var anchorBuilt = sql.select("_blamejs_audit_purge_anchor", _sqlOpts()) // allow:hand-rolled-sql
218
+ .columns(["lastPurgedCounter", "lastPurgedRowHash"])
219
+ .where("scope", "audit")
220
+ .toSql();
221
+ anchor = await queryAllAsync(anchorBuilt.sql, anchorBuilt.params);
193
222
  } catch (_e) {
194
223
  // Anchor table may not exist on a deployment that has never been
195
224
  // through a purge. Treat as no anchor.
@@ -201,9 +230,16 @@ async function verifyChain(queryAllAsync, tableName, opts) {
201
230
  }
202
231
  }
203
232
 
204
- var rows = await queryAllAsync(
205
- 'SELECT * FROM "' + tableName + '" ORDER BY monotonicCounter ASC'
206
- );
233
+ var rowsBuilt = sql.select(tableName, _sqlOpts())
234
+ .orderBy("monotonicCounter", "asc")
235
+ .toSql();
236
+ var rows = await queryAllAsync(rowsBuilt.sql, rowsBuilt.params);
237
+ // Normalize driver shape before hashing: node-postgres returns BIGINT
238
+ // columns (recordedAt / monotonicCounter) as strings, which would hash
239
+ // differently from the numbers the chain-writer signed — the chain only
240
+ // verified on SQLite without this. coerceRow makes the recompute
241
+ // type-stable across backends (no-op on already-numeric SQLite rows).
242
+ rows = frameworkSchema.coerceRows(rows);
207
243
  if (skipBeforeCounter > 0) {
208
244
  rows = rows.filter(function (r) {
209
245
  return Number(r.monotonicCounter) > skipBeforeCounter;
@@ -63,6 +63,7 @@ var nodePath = require("node:path");
63
63
  var nodeCrypto = require("node:crypto");
64
64
  var atomicFile = require("./atomic-file");
65
65
  var { sha3Hash } = require("./crypto");
66
+ var frameworkFiles = require("./framework-files");
66
67
  var { defineClass } = require("./framework-error");
67
68
  var { boot } = require("./log");
68
69
  var safeBuffer = require("./safe-buffer");
@@ -118,11 +119,73 @@ var log = boot("audit-sign");
118
119
  function resolvePaths(dataDir) {
119
120
  return {
120
121
  dataDir: dataDir,
121
- plaintext: nodePath.join(dataDir, "audit-sign.key"),
122
- sealed: nodePath.join(dataDir, "audit-sign.key.sealed"),
122
+ plaintext: nodePath.join(dataDir, frameworkFiles.fileName("auditSignKey")),
123
+ sealed: nodePath.join(dataDir, frameworkFiles.fileName("auditSignKey") + ".sealed"),
124
+ // Unsealed registry of rotated-out PUBLIC keys (public keys are not
125
+ // secret). It lets verify-time code (b.audit.verifyCheckpoints) resolve
126
+ // the public key for a checkpoint signed under a now-rotated key WITHOUT
127
+ // the old passphrase, so a rotation does not strand historical checkpoints.
128
+ publicHistory: nodePath.join(dataDir, "audit-sign.pubkeys.json"),
123
129
  };
124
130
  }
125
131
 
132
+ // Append a rotated-out public key to the unsealed public-key history. Public
133
+ // keys carry no secret, so storing them in the clear is safe and is what
134
+ // makes passphrase-free historical verification possible. De-duplicated by
135
+ // fingerprint; best-effort (a write failure must not abort the rotation, the
136
+ // sealed private-key history is the durable archive of record).
137
+ function _appendPublicHistory(entry) {
138
+ if (!paths || !paths.publicHistory) return;
139
+ var list = [];
140
+ try {
141
+ if (nodeFs.existsSync(paths.publicHistory)) {
142
+ var parsed = safeJson.parse(atomicFile.readSync(paths.publicHistory));
143
+ if (Array.isArray(parsed)) list = parsed;
144
+ }
145
+ } catch (_e) { list = []; } // corrupt registry — rebuild from this entry
146
+ for (var i = 0; i < list.length; i += 1) {
147
+ if (list[i] && list[i].fingerprint === entry.fingerprint) return; // already recorded
148
+ }
149
+ list.push(entry);
150
+ try {
151
+ atomicFile.writeSync(paths.publicHistory, JSON.stringify(list, null, 2), { fileMode: 0o600 });
152
+ } catch (_e) { /* best-effort */ }
153
+ }
154
+
155
+ /**
156
+ * @primitive b.auditSign.getPublicKeyByFingerprint
157
+ * @signature b.auditSign.getPublicKeyByFingerprint(fingerprint)
158
+ * @since 0.14.29
159
+ * @status stable
160
+ * @related b.auditSign.getPublicKey, b.auditSign.verify, b.auditSign.rotateSigningKey
161
+ *
162
+ * Resolve the audit-signing public key (SPKI PEM) for a fingerprint: the
163
+ * live key, or a rotated-out key recorded in the unsealed public-key history
164
+ * that `rotateSigningKey` maintains. Returns `null` when no key matches. Only
165
+ * public material is consulted, so no passphrase is needed - this is what
166
+ * lets `b.audit.verifyCheckpoints` verify a checkpoint signed under a
167
+ * now-rotated key without stranding history.
168
+ *
169
+ * @example
170
+ * var pem = b.auditSign.getPublicKeyByFingerprint(checkpoint.publicKeyFingerprint);
171
+ * // -> "-----BEGIN PUBLIC KEY-----\n..." (or null if the key is unknown)
172
+ */
173
+ function getPublicKeyByFingerprint(fp) {
174
+ _requireInit();
175
+ if (fp === keys.fingerprint) return keys.publicKey;
176
+ if (!paths || !paths.publicHistory || !nodeFs.existsSync(paths.publicHistory)) return null;
177
+ var list;
178
+ try { list = safeJson.parse(atomicFile.readSync(paths.publicHistory)); }
179
+ catch (_e) { return null; }
180
+ if (!Array.isArray(list)) return null;
181
+ for (var i = 0; i < list.length; i += 1) {
182
+ if (list[i] && list[i].fingerprint === fp && typeof list[i].publicKey === "string") {
183
+ return list[i].publicKey;
184
+ }
185
+ }
186
+ return null;
187
+ }
188
+
126
189
  function _computeFingerprint(publicKeyPem) {
127
190
  return sha3Hash(publicKeyPem);
128
191
  }
@@ -677,6 +740,17 @@ async function rotateSigningKey(rotOpts) {
677
740
  catch (_e) { /* history copy is best-effort */ }
678
741
  }
679
742
 
743
+ // Record the rotated-out PUBLIC key (unsealed) so b.audit.verifyCheckpoints
744
+ // can verify a checkpoint signed under it after rotation without the old
745
+ // passphrase. Without this the public key only lives inside the sealed
746
+ // history archive and verification of pre-rotation checkpoints is stranded.
747
+ _appendPublicHistory({
748
+ fingerprint: prevFingerprint,
749
+ publicKey: prevPublicKey,
750
+ algorithm: prevAlgorithm,
751
+ rotatedAt: new Date().toISOString(),
752
+ });
753
+
680
754
  // Persist the new keypair through the same path as boot — sealed
681
755
  // mode re-wraps with the operator's passphrase; plaintext mode
682
756
  // writes JSON. We don't accept a passphrase override here; the
@@ -740,6 +814,7 @@ module.exports = {
740
814
  reSignAll: reSignAll,
741
815
  getPublicKey: getPublicKey,
742
816
  getPublicKeyFingerprint: getPublicKeyFingerprint,
817
+ getPublicKeyByFingerprint: getPublicKeyByFingerprint,
743
818
  getMode: getMode,
744
819
  getAlgorithm: getAlgorithm,
745
820
  DEFAULT_SIGNING_ALG: DEFAULT_SIGNING_ALG,