@blamejs/blamejs-shop 0.4.31 → 0.4.33

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 (343) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +1 -1
  3. package/lib/asset-manifest.json +1 -1
  4. package/lib/vendor/MANIFEST.json +400 -282
  5. package/lib/vendor/blamejs/.github/workflows/ci.yml +34 -3
  6. package/lib/vendor/blamejs/.github/workflows/npm-publish.yml +21 -4
  7. package/lib/vendor/blamejs/.gitignore +6 -0
  8. package/lib/vendor/blamejs/CHANGELOG.md +28 -0
  9. package/lib/vendor/blamejs/MIGRATING.md +55 -0
  10. package/lib/vendor/blamejs/README.md +8 -6
  11. package/lib/vendor/blamejs/SECURITY.md +19 -3
  12. package/lib/vendor/blamejs/api-snapshot.json +2190 -664
  13. package/lib/vendor/blamejs/docker/caddy/localstack.Caddyfile +19 -0
  14. package/lib/vendor/blamejs/docker/init/generate-certs.sh +1 -1
  15. package/lib/vendor/blamejs/docker/otel/config.yaml +42 -0
  16. package/lib/vendor/blamejs/docker/otel/export/.gitkeep +0 -0
  17. package/lib/vendor/blamejs/docker/postgres/initdb/10-replication.sh +15 -0
  18. package/lib/vendor/blamejs/docker/postgres/replica-entrypoint.sh +38 -0
  19. package/lib/vendor/blamejs/docker/toxiproxy/toxiproxy.json +14 -0
  20. package/lib/vendor/blamejs/docker-compose.test.yml +209 -0
  21. package/lib/vendor/blamejs/examples/wiki/lib/page-generator.js +132 -0
  22. package/lib/vendor/blamejs/examples/wiki/lib/source-comment-block-validator.js +221 -61
  23. package/lib/vendor/blamejs/examples/wiki/lib/source-doc-parser.js +144 -9
  24. package/lib/vendor/blamejs/examples/wiki/test/e2e.js +99 -0
  25. package/lib/vendor/blamejs/fuzz/guard-sql.fuzz.js +36 -0
  26. package/lib/vendor/blamejs/index.js +4 -0
  27. package/lib/vendor/blamejs/lib/agent-envelope-mac.js +104 -0
  28. package/lib/vendor/blamejs/lib/agent-event-bus.js +105 -4
  29. package/lib/vendor/blamejs/lib/agent-posture-chain.js +8 -42
  30. package/lib/vendor/blamejs/lib/ai-content-detect.js +9 -10
  31. package/lib/vendor/blamejs/lib/api-key.js +158 -77
  32. package/lib/vendor/blamejs/lib/atomic-file.js +62 -4
  33. package/lib/vendor/blamejs/lib/audit-chain.js +47 -11
  34. package/lib/vendor/blamejs/lib/audit-sign.js +77 -2
  35. package/lib/vendor/blamejs/lib/audit-tools.js +79 -51
  36. package/lib/vendor/blamejs/lib/audit.js +259 -123
  37. package/lib/vendor/blamejs/lib/auth/elevation-grant.js +6 -2
  38. package/lib/vendor/blamejs/lib/auth/oauth.js +66 -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 +36 -7
  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 +210 -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/credential-hash.js +9 -0
  55. package/lib/vendor/blamejs/lib/crypto-field.js +916 -156
  56. package/lib/vendor/blamejs/lib/db-declare-row-policy.js +35 -22
  57. package/lib/vendor/blamejs/lib/db-file-lifecycle.js +3 -2
  58. package/lib/vendor/blamejs/lib/db-query.js +882 -260
  59. package/lib/vendor/blamejs/lib/db-schema.js +228 -44
  60. package/lib/vendor/blamejs/lib/db.js +249 -99
  61. package/lib/vendor/blamejs/lib/dsr.js +385 -55
  62. package/lib/vendor/blamejs/lib/error-page.js +14 -1
  63. package/lib/vendor/blamejs/lib/external-db-migrate.js +239 -137
  64. package/lib/vendor/blamejs/lib/external-db.js +549 -34
  65. package/lib/vendor/blamejs/lib/file-upload.js +52 -7
  66. package/lib/vendor/blamejs/lib/framework-error.js +20 -1
  67. package/lib/vendor/blamejs/lib/framework-files.js +73 -0
  68. package/lib/vendor/blamejs/lib/framework-schema.js +695 -394
  69. package/lib/vendor/blamejs/lib/gate-contract.js +659 -1
  70. package/lib/vendor/blamejs/lib/guard-agent-registry.js +26 -44
  71. package/lib/vendor/blamejs/lib/guard-all.js +1 -0
  72. package/lib/vendor/blamejs/lib/guard-auth.js +42 -112
  73. package/lib/vendor/blamejs/lib/guard-cidr.js +33 -154
  74. package/lib/vendor/blamejs/lib/guard-csv.js +46 -113
  75. package/lib/vendor/blamejs/lib/guard-domain.js +34 -157
  76. package/lib/vendor/blamejs/lib/guard-dsn.js +27 -43
  77. package/lib/vendor/blamejs/lib/guard-email.js +47 -69
  78. package/lib/vendor/blamejs/lib/guard-envelope.js +19 -32
  79. package/lib/vendor/blamejs/lib/guard-event-bus-payload.js +24 -42
  80. package/lib/vendor/blamejs/lib/guard-event-bus-topic.js +25 -43
  81. package/lib/vendor/blamejs/lib/guard-filename.js +42 -106
  82. package/lib/vendor/blamejs/lib/guard-graphql.js +42 -123
  83. package/lib/vendor/blamejs/lib/guard-html.js +53 -108
  84. package/lib/vendor/blamejs/lib/guard-idempotency-key.js +24 -42
  85. package/lib/vendor/blamejs/lib/guard-image.js +46 -103
  86. package/lib/vendor/blamejs/lib/guard-imap-command.js +18 -32
  87. package/lib/vendor/blamejs/lib/guard-jmap.js +16 -30
  88. package/lib/vendor/blamejs/lib/guard-json.js +38 -108
  89. package/lib/vendor/blamejs/lib/guard-jsonpath.js +38 -171
  90. package/lib/vendor/blamejs/lib/guard-jwt.js +49 -179
  91. package/lib/vendor/blamejs/lib/guard-list-id.js +25 -41
  92. package/lib/vendor/blamejs/lib/guard-list-unsubscribe.js +27 -43
  93. package/lib/vendor/blamejs/lib/guard-mail-compose.js +24 -42
  94. package/lib/vendor/blamejs/lib/guard-mail-move.js +26 -44
  95. package/lib/vendor/blamejs/lib/guard-mail-query.js +28 -46
  96. package/lib/vendor/blamejs/lib/guard-mail-reply.js +24 -42
  97. package/lib/vendor/blamejs/lib/guard-mail-sieve.js +24 -42
  98. package/lib/vendor/blamejs/lib/guard-managesieve-command.js +17 -31
  99. package/lib/vendor/blamejs/lib/guard-markdown.js +37 -104
  100. package/lib/vendor/blamejs/lib/guard-message-id.js +26 -45
  101. package/lib/vendor/blamejs/lib/guard-mime.js +39 -151
  102. package/lib/vendor/blamejs/lib/guard-oauth.js +54 -135
  103. package/lib/vendor/blamejs/lib/guard-pdf.js +45 -101
  104. package/lib/vendor/blamejs/lib/guard-pop3-command.js +21 -31
  105. package/lib/vendor/blamejs/lib/guard-posture-chain.js +24 -42
  106. package/lib/vendor/blamejs/lib/guard-regex.js +33 -107
  107. package/lib/vendor/blamejs/lib/guard-saga-config.js +24 -42
  108. package/lib/vendor/blamejs/lib/guard-shell.js +42 -172
  109. package/lib/vendor/blamejs/lib/guard-smtp-command.js +48 -54
  110. package/lib/vendor/blamejs/lib/guard-snapshot-envelope.js +24 -42
  111. package/lib/vendor/blamejs/lib/guard-sql.js +1491 -0
  112. package/lib/vendor/blamejs/lib/guard-stream-args.js +24 -43
  113. package/lib/vendor/blamejs/lib/guard-svg.js +47 -65
  114. package/lib/vendor/blamejs/lib/guard-template.js +35 -172
  115. package/lib/vendor/blamejs/lib/guard-tenant-id.js +26 -45
  116. package/lib/vendor/blamejs/lib/guard-time.js +32 -154
  117. package/lib/vendor/blamejs/lib/guard-trace-context.js +25 -44
  118. package/lib/vendor/blamejs/lib/guard-uuid.js +32 -153
  119. package/lib/vendor/blamejs/lib/guard-xml.js +38 -113
  120. package/lib/vendor/blamejs/lib/guard-yaml.js +51 -163
  121. package/lib/vendor/blamejs/lib/http-client.js +37 -9
  122. package/lib/vendor/blamejs/lib/inbox.js +120 -107
  123. package/lib/vendor/blamejs/lib/legal-hold.js +121 -50
  124. package/lib/vendor/blamejs/lib/log-stream-cloudwatch.js +47 -31
  125. package/lib/vendor/blamejs/lib/log-stream-otlp.js +32 -18
  126. package/lib/vendor/blamejs/lib/mail-auth.js +236 -0
  127. package/lib/vendor/blamejs/lib/mail-crypto-smime.js +2 -6
  128. package/lib/vendor/blamejs/lib/mail-dkim.js +1 -0
  129. package/lib/vendor/blamejs/lib/mail-greylist.js +2 -6
  130. package/lib/vendor/blamejs/lib/mail-helo.js +2 -6
  131. package/lib/vendor/blamejs/lib/mail-journal.js +85 -64
  132. package/lib/vendor/blamejs/lib/mail-rbl.js +2 -6
  133. package/lib/vendor/blamejs/lib/mail-scan.js +2 -6
  134. package/lib/vendor/blamejs/lib/mail-server-jmap.js +117 -12
  135. package/lib/vendor/blamejs/lib/mail-server-mx.js +276 -7
  136. package/lib/vendor/blamejs/lib/mail-spam-score.js +2 -6
  137. package/lib/vendor/blamejs/lib/mail-store.js +293 -154
  138. package/lib/vendor/blamejs/lib/mail.js +8 -4
  139. package/lib/vendor/blamejs/lib/middleware/body-parser.js +71 -25
  140. package/lib/vendor/blamejs/lib/middleware/csrf-protect.js +19 -8
  141. package/lib/vendor/blamejs/lib/middleware/dpop.js +10 -1
  142. package/lib/vendor/blamejs/lib/middleware/fetch-metadata.js +17 -7
  143. package/lib/vendor/blamejs/lib/middleware/idempotency-key.js +75 -51
  144. package/lib/vendor/blamejs/lib/middleware/rate-limit.js +102 -32
  145. package/lib/vendor/blamejs/lib/middleware/security-headers.js +21 -5
  146. package/lib/vendor/blamejs/lib/migrations.js +108 -66
  147. package/lib/vendor/blamejs/lib/network-heartbeat.js +7 -0
  148. package/lib/vendor/blamejs/lib/network-proxy.js +24 -1
  149. package/lib/vendor/blamejs/lib/nonce-store.js +31 -9
  150. package/lib/vendor/blamejs/lib/object-store/azure-blob-bucket-ops.js +9 -4
  151. package/lib/vendor/blamejs/lib/object-store/azure-blob.js +57 -3
  152. package/lib/vendor/blamejs/lib/object-store/gcs.js +4 -1
  153. package/lib/vendor/blamejs/lib/object-store/sigv4-bucket-ops.js +5 -2
  154. package/lib/vendor/blamejs/lib/object-store/sigv4.js +38 -6
  155. package/lib/vendor/blamejs/lib/observability-otlp-exporter.js +9 -1
  156. package/lib/vendor/blamejs/lib/observability.js +124 -0
  157. package/lib/vendor/blamejs/lib/otel-export.js +12 -3
  158. package/lib/vendor/blamejs/lib/outbox.js +184 -83
  159. package/lib/vendor/blamejs/lib/parsers/safe-xml.js +47 -7
  160. package/lib/vendor/blamejs/lib/pqc-agent.js +44 -0
  161. package/lib/vendor/blamejs/lib/pubsub-cluster.js +42 -20
  162. package/lib/vendor/blamejs/lib/queue-local.js +225 -140
  163. package/lib/vendor/blamejs/lib/queue-redis.js +9 -1
  164. package/lib/vendor/blamejs/lib/queue-sqs.js +6 -0
  165. package/lib/vendor/blamejs/lib/queue.js +7 -0
  166. package/lib/vendor/blamejs/lib/redact.js +68 -11
  167. package/lib/vendor/blamejs/lib/redis-client.js +160 -31
  168. package/lib/vendor/blamejs/lib/request-helpers.js +7 -0
  169. package/lib/vendor/blamejs/lib/retention.js +117 -42
  170. package/lib/vendor/blamejs/lib/router.js +212 -5
  171. package/lib/vendor/blamejs/lib/safe-dns.js +29 -45
  172. package/lib/vendor/blamejs/lib/safe-ical.js +18 -33
  173. package/lib/vendor/blamejs/lib/safe-icap.js +27 -43
  174. package/lib/vendor/blamejs/lib/safe-sieve.js +21 -40
  175. package/lib/vendor/blamejs/lib/safe-sql.js +212 -3
  176. package/lib/vendor/blamejs/lib/safe-url.js +170 -3
  177. package/lib/vendor/blamejs/lib/safe-vcard.js +18 -33
  178. package/lib/vendor/blamejs/lib/scheduler.js +47 -12
  179. package/lib/vendor/blamejs/lib/seeders.js +122 -74
  180. package/lib/vendor/blamejs/lib/session-stores.js +42 -14
  181. package/lib/vendor/blamejs/lib/session.js +175 -77
  182. package/lib/vendor/blamejs/lib/sql.js +3842 -0
  183. package/lib/vendor/blamejs/lib/sse.js +26 -0
  184. package/lib/vendor/blamejs/lib/ssrf-guard.js +169 -4
  185. package/lib/vendor/blamejs/lib/static.js +177 -34
  186. package/lib/vendor/blamejs/lib/subject.js +96 -49
  187. package/lib/vendor/blamejs/lib/vault/index.js +3 -2
  188. package/lib/vendor/blamejs/lib/vault/passphrase-ops.js +3 -2
  189. package/lib/vendor/blamejs/lib/vault/rotate.js +168 -108
  190. package/lib/vendor/blamejs/lib/vault-aad.js +6 -0
  191. package/lib/vendor/blamejs/lib/vendor-data.js +2 -0
  192. package/lib/vendor/blamejs/lib/websocket.js +35 -5
  193. package/lib/vendor/blamejs/lib/worker-pool.js +11 -0
  194. package/lib/vendor/blamejs/package.json +2 -2
  195. package/lib/vendor/blamejs/release-notes/v0.14.x.json +1503 -0
  196. package/lib/vendor/blamejs/release-notes/v0.15.0.json +77 -0
  197. package/lib/vendor/blamejs/release-notes/v0.15.1.json +22 -0
  198. package/lib/vendor/blamejs/release-notes/v0.15.2.json +22 -0
  199. package/lib/vendor/blamejs/release-notes/v0.15.3.json +39 -0
  200. package/lib/vendor/blamejs/release-notes/v0.15.4.json +39 -0
  201. package/lib/vendor/blamejs/release-notes/v0.15.5.json +22 -0
  202. package/lib/vendor/blamejs/release-notes/v0.15.6.json +59 -0
  203. package/lib/vendor/blamejs/release-notes/v0.15.7.json +43 -0
  204. package/lib/vendor/blamejs/scripts/check-services.js +21 -0
  205. package/lib/vendor/blamejs/scripts/gen-migrating.js +67 -0
  206. package/lib/vendor/blamejs/scripts/release.js +398 -38
  207. package/lib/vendor/blamejs/test/00-primitives.js +168 -0
  208. package/lib/vendor/blamejs/test/10-state.js +140 -14
  209. package/lib/vendor/blamejs/test/20-db.js +65 -2
  210. package/lib/vendor/blamejs/test/helpers/db.js +9 -0
  211. package/lib/vendor/blamejs/test/helpers/drivers.js +27 -15
  212. package/lib/vendor/blamejs/test/helpers/services.js +21 -0
  213. package/lib/vendor/blamejs/test/integration/audit-actor-binding-pg.test.js +246 -0
  214. package/lib/vendor/blamejs/test/integration/audit-chain-external-db.test.js +517 -0
  215. package/lib/vendor/blamejs/test/integration/audit-stack-mysql.test.js +639 -0
  216. package/lib/vendor/blamejs/test/integration/audit-stack-postgres.test.js +832 -0
  217. package/lib/vendor/blamejs/test/integration/backup-restore-objectstore.test.js +453 -0
  218. package/lib/vendor/blamejs/test/integration/data-layer-cluster-mysql.test.js +649 -0
  219. package/lib/vendor/blamejs/test/integration/data-layer-cluster-pg.test.js +770 -0
  220. package/lib/vendor/blamejs/test/integration/data-layer-mysql-privacy.test.js +630 -0
  221. package/lib/vendor/blamejs/test/integration/data-layer-mysql.test.js +610 -0
  222. package/lib/vendor/blamejs/test/integration/data-layer-pg.test.js +577 -0
  223. package/lib/vendor/blamejs/test/integration/data-layer-postgres.test.js +771 -0
  224. package/lib/vendor/blamejs/test/integration/db-layer-mysql.test.js +549 -0
  225. package/lib/vendor/blamejs/test/integration/db-layer-postgres.test.js +598 -0
  226. package/lib/vendor/blamejs/test/integration/distributed-scheduler-fencing-pg.test.js +602 -0
  227. package/lib/vendor/blamejs/test/integration/external-db-postgres.test.js +576 -0
  228. package/lib/vendor/blamejs/test/integration/framework-schema-mysql.test.js +353 -0
  229. package/lib/vendor/blamejs/test/integration/log-stream-cloudwatch.test.js +224 -0
  230. package/lib/vendor/blamejs/test/integration/mail-crypto-smime.test.js +142 -17
  231. package/lib/vendor/blamejs/test/integration/network-heartbeat.test.js +25 -10
  232. package/lib/vendor/blamejs/test/integration/object-store-azure.test.js +101 -0
  233. package/lib/vendor/blamejs/test/integration/object-store-gcs.test.js +239 -0
  234. package/lib/vendor/blamejs/test/integration/object-store-sigv4.test.js +35 -16
  235. package/lib/vendor/blamejs/test/integration/object-store-worm-lock.test.js +291 -0
  236. package/lib/vendor/blamejs/test/integration/pubsub.test.js +14 -0
  237. package/lib/vendor/blamejs/test/integration/queue-sqs.test.js +322 -0
  238. package/lib/vendor/blamejs/test/integration/redis-reconnect-toxiproxy.test.js +300 -0
  239. package/lib/vendor/blamejs/test/integration/sql-fts5-catalog-sqlite.test.js +154 -0
  240. package/lib/vendor/blamejs/test/integration/tls-classical-downgrade-audit.test.js +71 -0
  241. package/lib/vendor/blamejs/test/layer-0-primitives/agent-event-bus.test.js +175 -12
  242. package/lib/vendor/blamejs/test/layer-0-primitives/atomic-file-exclusive-temp.test.js +216 -0
  243. package/lib/vendor/blamejs/test/layer-0-primitives/audit-checkpoint-false-rollback.test.js +203 -0
  244. package/lib/vendor/blamejs/test/layer-0-primitives/audit-query-self-log.test.js +126 -0
  245. package/lib/vendor/blamejs/test/layer-0-primitives/audit-safeemit-redacts-secrets.test.js +196 -0
  246. package/lib/vendor/blamejs/test/layer-0-primitives/audit-signing-key-rotation.test.js +197 -0
  247. package/lib/vendor/blamejs/test/layer-0-primitives/audit-verifybundle-tamper.test.js +209 -0
  248. package/lib/vendor/blamejs/test/layer-0-primitives/azure-blob-key-encoding.test.js +121 -0
  249. package/lib/vendor/blamejs/test/layer-0-primitives/backup-residency-posture.test.js +168 -0
  250. package/lib/vendor/blamejs/test/layer-0-primitives/backup-scheduletest-drill.test.js +318 -0
  251. package/lib/vendor/blamejs/test/layer-0-primitives/break-glass.test.js +233 -7
  252. package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +1196 -14
  253. package/lib/vendor/blamejs/test/layer-0-primitives/compliance.test.js +229 -0
  254. package/lib/vendor/blamejs/test/layer-0-primitives/credential-hash.test.js +18 -0
  255. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-derived-hash.test.js +24 -7
  256. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-dual-read-migrate.test.js +165 -0
  257. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-per-row-key.test.js +350 -0
  258. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-unseal-rate-cap.test.js +27 -9
  259. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-upgrade-dialect.test.js +76 -0
  260. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-interop-oracles.test.js +392 -0
  261. package/lib/vendor/blamejs/test/layer-0-primitives/csrf-protect.test.js +159 -0
  262. package/lib/vendor/blamejs/test/layer-0-primitives/db-column-gate.test.js +180 -1
  263. package/lib/vendor/blamejs/test/layer-0-primitives/db-query-cross-schema.test.js +5 -2
  264. package/lib/vendor/blamejs/test/layer-0-primitives/db-query-sealed-field-in.test.js +101 -0
  265. package/lib/vendor/blamejs/test/layer-0-primitives/db-raw-residency-gate.test.js +128 -0
  266. package/lib/vendor/blamejs/test/layer-0-primitives/db-schema-drift.test.js +38 -5
  267. package/lib/vendor/blamejs/test/layer-0-primitives/db-schema-reconcile-emittable.test.js +127 -0
  268. package/lib/vendor/blamejs/test/layer-0-primitives/db-stream-and-payload-shape.test.js +267 -0
  269. package/lib/vendor/blamejs/test/layer-0-primitives/db-worm.test.js +150 -0
  270. package/lib/vendor/blamejs/test/layer-0-primitives/defineguard-default-gate-posture-caps.test.js +30 -0
  271. package/lib/vendor/blamejs/test/layer-0-primitives/dpop-middleware-replaystore-required.test.js +46 -0
  272. package/lib/vendor/blamejs/test/layer-0-primitives/dsr.test.js +218 -0
  273. package/lib/vendor/blamejs/test/layer-0-primitives/erase-posture-vacuum.test.js +210 -0
  274. package/lib/vendor/blamejs/test/layer-0-primitives/external-db-hardening.test.js +4 -1
  275. package/lib/vendor/blamejs/test/layer-0-primitives/external-db-migrate.test.js +48 -2
  276. package/lib/vendor/blamejs/test/layer-0-primitives/federation-vc-suite.test.js +237 -5
  277. package/lib/vendor/blamejs/test/layer-0-primitives/fetch-metadata.test.js +20 -9
  278. package/lib/vendor/blamejs/test/layer-0-primitives/file-upload-content-safety-skip-audit.test.js +193 -0
  279. package/lib/vendor/blamejs/test/layer-0-primitives/guard-csv.test.js +90 -0
  280. package/lib/vendor/blamejs/test/layer-0-primitives/http-client-stream.test.js +85 -0
  281. package/lib/vendor/blamejs/test/layer-0-primitives/idempotency-key.test.js +10 -6
  282. package/lib/vendor/blamejs/test/layer-0-primitives/inbox.test.js +15 -4
  283. package/lib/vendor/blamejs/test/layer-0-primitives/legal-hold.test.js +146 -0
  284. package/lib/vendor/blamejs/test/layer-0-primitives/mail-auth.test.js +189 -0
  285. package/lib/vendor/blamejs/test/layer-0-primitives/mail-journal.test.js +3 -1
  286. package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-jmap.test.js +123 -4
  287. package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-mx.test.js +207 -2
  288. package/lib/vendor/blamejs/test/layer-0-primitives/mail-store.test.js +74 -0
  289. package/lib/vendor/blamejs/test/layer-0-primitives/oauth-callback.test.js +43 -0
  290. package/lib/vendor/blamejs/test/layer-0-primitives/otel-export.test.js +133 -0
  291. package/lib/vendor/blamejs/test/layer-0-primitives/otlp-attr-redaction.test.js +101 -0
  292. package/lib/vendor/blamejs/test/layer-0-primitives/outbox-inflight-reaper.test.js +136 -0
  293. package/lib/vendor/blamejs/test/layer-0-primitives/parsers-standalone.test.js +83 -0
  294. package/lib/vendor/blamejs/test/layer-0-primitives/passkey-real-vectors.test.js +429 -0
  295. package/lib/vendor/blamejs/test/layer-0-primitives/pqc-agent-curve.test.js +21 -11
  296. package/lib/vendor/blamejs/test/layer-0-primitives/queue-byo-db.test.js +40 -0
  297. package/lib/vendor/blamejs/test/layer-0-primitives/redact-dlp.test.js +83 -0
  298. package/lib/vendor/blamejs/test/layer-0-primitives/redis-client.test.js +113 -0
  299. package/lib/vendor/blamejs/test/layer-0-primitives/retention-dryrun-no-vacuum.test.js +99 -0
  300. package/lib/vendor/blamejs/test/layer-0-primitives/retention-floor.test.js +59 -0
  301. package/lib/vendor/blamejs/test/layer-0-primitives/router-use-path-scope.test.js +255 -0
  302. package/lib/vendor/blamejs/test/layer-0-primitives/safe-url-canonicalize.test.js +362 -0
  303. package/lib/vendor/blamejs/test/layer-0-primitives/safe-xml.test.js +143 -0
  304. package/lib/vendor/blamejs/test/layer-0-primitives/saml-subjectconfirmation-notonorafter.test.js +287 -0
  305. package/lib/vendor/blamejs/test/layer-0-primitives/scheduler-watchdog-stale-settle.test.js +71 -0
  306. package/lib/vendor/blamejs/test/layer-0-primitives/sd-jwt-vc-ecdsa-p1363.test.js +79 -0
  307. package/lib/vendor/blamejs/test/layer-0-primitives/sd-jwt-vc.test.js +50 -0
  308. package/lib/vendor/blamejs/test/layer-0-primitives/security-headers.test.js +31 -4
  309. package/lib/vendor/blamejs/test/layer-0-primitives/session-extensions.test.js +45 -0
  310. package/lib/vendor/blamejs/test/layer-0-primitives/sigv4-bucket-ops.test.js +49 -0
  311. package/lib/vendor/blamejs/test/layer-0-primitives/sql.test.js +595 -0
  312. package/lib/vendor/blamejs/test/layer-0-primitives/sse-backpressure.test.js +91 -0
  313. package/lib/vendor/blamejs/test/layer-0-primitives/ssrf-guard.test.js +69 -0
  314. package/lib/vendor/blamejs/test/layer-0-primitives/static.test.js +194 -2
  315. package/lib/vendor/blamejs/test/layer-0-primitives/websocket-extension-header.test.js +88 -0
  316. package/lib/vendor/blamejs/test/layer-0-primitives/worker-pool-recycle-race.test.js +66 -0
  317. package/lib/vendor/blamejs/test/layer-1-state/api-key.test.js +84 -0
  318. package/lib/vendor/blamejs/test/layer-5-integration/external-db-residency.test.js +638 -0
  319. package/lib/vendor/blamejs/test/layer-5-integration/guard-host-integration.test.js +21 -0
  320. package/lib/vendor/blamejs/test/smoke.js +79 -21
  321. package/package.json +2 -2
  322. package/lib/vendor/blamejs/release-notes/v0.14.0.json +0 -43
  323. package/lib/vendor/blamejs/release-notes/v0.14.1.json +0 -60
  324. package/lib/vendor/blamejs/release-notes/v0.14.10.json +0 -54
  325. package/lib/vendor/blamejs/release-notes/v0.14.11.json +0 -72
  326. package/lib/vendor/blamejs/release-notes/v0.14.12.json +0 -95
  327. package/lib/vendor/blamejs/release-notes/v0.14.13.json +0 -52
  328. package/lib/vendor/blamejs/release-notes/v0.14.14.json +0 -31
  329. package/lib/vendor/blamejs/release-notes/v0.14.16.json +0 -45
  330. package/lib/vendor/blamejs/release-notes/v0.14.17.json +0 -57
  331. package/lib/vendor/blamejs/release-notes/v0.14.18.json +0 -127
  332. package/lib/vendor/blamejs/release-notes/v0.14.19.json +0 -61
  333. package/lib/vendor/blamejs/release-notes/v0.14.2.json +0 -18
  334. package/lib/vendor/blamejs/release-notes/v0.14.20.json +0 -73
  335. package/lib/vendor/blamejs/release-notes/v0.14.21.json +0 -98
  336. package/lib/vendor/blamejs/release-notes/v0.14.22.json +0 -91
  337. package/lib/vendor/blamejs/release-notes/v0.14.3.json +0 -18
  338. package/lib/vendor/blamejs/release-notes/v0.14.4.json +0 -18
  339. package/lib/vendor/blamejs/release-notes/v0.14.5.json +0 -18
  340. package/lib/vendor/blamejs/release-notes/v0.14.6.json +0 -60
  341. package/lib/vendor/blamejs/release-notes/v0.14.7.json +0 -77
  342. package/lib/vendor/blamejs/release-notes/v0.14.8.json +0 -27
  343. package/lib/vendor/blamejs/release-notes/v0.14.9.json +0 -40
@@ -49,6 +49,7 @@ var requestHelpers = require("./request-helpers");
49
49
  var safeAsync = require("./safe-async");
50
50
  var safeJson = require("./safe-json");
51
51
  var safeSql = require("./safe-sql");
52
+ var sql = require("./sql");
52
53
  var totp = require("./totp");
53
54
  var validateOpts = require("./validate-opts");
54
55
  var { defineClass } = require("./framework-error");
@@ -75,6 +76,10 @@ var DEK_BYTES = C.BYTES.bytes(32);
75
76
  var GRANT_ID_BYTES = C.BYTES.bytes(16);
76
77
 
77
78
  var DEFAULT_GRANT_TTL_MS = C.TIME.minutes(15);
79
+ // Replay-step retention. A TOTP code is only valid inside the verifier's
80
+ // drift window (minutes); retaining the highest-accepted step for an hour
81
+ // guarantees any in-window replay attempt arrives after the floor is set.
82
+ var REPLAY_STEP_TTL_MS = C.TIME.hours(1);
78
83
  var DEFAULT_MAX_ROWS = 1; // operator-locked: row-by-row auth
79
84
  var DEFAULT_REASON_MIN_LEN = 12;
80
85
  var DEFAULT_LOCKED_BEHAVIOR = "throw"; // or "redact"
@@ -82,6 +87,34 @@ var DEFAULT_AUDIT_REASON = "cleartext";
82
87
  var ALLOWED_FACTORS = ["totp", "passkey"];
83
88
  var ALLOWED_REASON_STORAGE = ["cleartext", "hmac", "both"];
84
89
 
90
+ // cryptoField REGISTRY KEYS for the two break-glass framework tables. These
91
+ // are the names db.js's FRAMEWORK_SCHEMA registered the tables under, so
92
+ // seal / unseal / computeDerived must key off the byte-identical literal —
93
+ // resolving them through frameworkSchema.tableName would diverge the seal-side
94
+ // key from the registration under a custom prefix and break decryption. (SQL
95
+ // composed via b.sql passes the SAME bare logical name so clusterStorage can
96
+ // rewrite the table reference; these constants cover only the cryptoField
97
+ // keying.) allow:hand-rolled-sql — cryptoField registry keys, not SQL text.
98
+ var POLICIES_TABLE = "_blamejs_break_glass_policies"; // allow:hand-rolled-sql
99
+ var GRANTS_TABLE = "_blamejs_break_glass_grants"; // allow:hand-rolled-sql
100
+
101
+ // b.sql opts for every statement break-glass dispatches through
102
+ // clusterStorage. Thread the ACTIVE backend dialect (clusterStorage.dialect()
103
+ // — "sqlite" single-node, "postgres" | "mysql" in cluster mode) so the
104
+ // emitted identifier quoting + dialect idioms (ON CONFLICT vs ON DUPLICATE
105
+ // KEY) match the backend the SQL dispatches to. Defaulting to "sqlite" works
106
+ // on Postgres only by accident (both double-quote identifiers) and emits the
107
+ // wrong quoting on MySQL. clusterStorage.execute still rewrites framework
108
+ // table names + translates `?` placeholders at dispatch; this controls only
109
+ // the builder-side quoting + idiom selection.
110
+ // _sqlOpts() — framework tables (policies / grants); name resolved bare,
111
+ // clusterStorage rewrites the prefix.
112
+ // _appSqlOpts() — the operator's glass-locked app table; quoteName so b.sql
113
+ // quotes the (validated) identifier, and it is NOT
114
+ // framework-rewritten.
115
+ function _sqlOpts() { return { dialect: clusterStorage.dialect() }; }
116
+ function _appSqlOpts() { return { dialect: clusterStorage.dialect(), quoteName: true }; }
117
+
85
118
  // In-memory policy cache. Cluster-shared via the policies table; the
86
119
  // cache short-circuits the DB roundtrip on the unsealRow hot path.
87
120
  // Populated on first access per-table; invalidated on policy.set/delete.
@@ -153,10 +186,14 @@ async function _ensureDek(table) {
153
186
  // DEK is vault-sealed and stored in the policy row's `dekSealed`
154
187
  // column. Generated lazily on first use of cryptographic-mode for
155
188
  // the table. Cached in-memory after first read.
156
- var rows = await clusterStorage.executeAll(
157
- "SELECT dekSealed FROM _blamejs_break_glass_policies WHERE tableName = ?",
158
- [table]
159
- );
189
+ // The policy table is external-only; its LOGICAL name IS the
190
+ // `_blamejs_`-prefixed name (self-mapped in LOCAL_TO_EXTERNAL), passed
191
+ // bare to b.sql so clusterStorage rewrites + placeholderizes.
192
+ var dekReadBuilt = sql.select("_blamejs_break_glass_policies", _sqlOpts()) // allow:hand-rolled-sql
193
+ .columns(["dekSealed"])
194
+ .where("tableName", table)
195
+ .toSql();
196
+ var rows = await clusterStorage.executeAll(dekReadBuilt.sql, dekReadBuilt.params);
160
197
  if (!rows || rows.length === 0) {
161
198
  throw new BreakGlassError("breakglass/policy-not-set",
162
199
  "_ensureDek: no policy for table '" + table + "'", true);
@@ -168,10 +205,11 @@ async function _ensureDek(table) {
168
205
  } else {
169
206
  dek = generateBytes(DEK_BYTES);
170
207
  var sealedDek = vault().seal(dek.toString("base64"));
171
- await clusterStorage.execute(
172
- "UPDATE _blamejs_break_glass_policies SET dekSealed = ? WHERE tableName = ?",
173
- [sealedDek, table]
174
- );
208
+ var dekUpdBuilt = sql.update("_blamejs_break_glass_policies", _sqlOpts()) // allow:hand-rolled-sql
209
+ .set({ dekSealed: sealedDek })
210
+ .where("tableName", table)
211
+ .toSql();
212
+ await clusterStorage.execute(dekUpdBuilt.sql, dekUpdBuilt.params);
175
213
  }
176
214
  dekCache.set(table, dek);
177
215
  return dek;
@@ -343,14 +381,16 @@ async function migrate(table, opts) {
343
381
  var lastId = "";
344
382
  // Iterate via _id-keyset paging so we don't load the whole table into memory.
345
383
  while (true) {
346
- // table is already validated as a safe identifier shape via
347
- // _validatePolicySet wrap in "..." per the framework's
348
- // identifier-quoting convention.
349
- var qTable = '"' + table + '"';
350
- var rows = await clusterStorage.executeAll(
351
- "SELECT * FROM " + qTable + " WHERE _id > ? ORDER BY _id ASC LIMIT ?",
352
- [lastId, batchSize]
353
- );
384
+ // `table` is an operator app table (already validated as a safe
385
+ // identifier via _validatePolicySet). quoteName:true makes b.sql quote
386
+ // the name (reserved-word / case-sensitive safe); it is NOT a framework
387
+ // table, so clusterStorage's resolveTables leaves it untouched.
388
+ var pageBuilt = sql.select(table, _appSqlOpts())
389
+ .whereOp("_id", ">", lastId)
390
+ .orderBy("_id", "asc")
391
+ .limit(batchSize)
392
+ .toSql();
393
+ var rows = await clusterStorage.executeAll(pageBuilt.sql, pageBuilt.params);
354
394
  if (!rows || rows.length === 0) break;
355
395
  for (var i = 0; i < rows.length; i++) {
356
396
  totalRows++;
@@ -374,16 +414,16 @@ async function migrate(table, opts) {
374
414
  // the cell ciphertext stays as a literal string, not double-sealed.
375
415
  var setCols = Object.keys(update).filter(function (k) { return k !== "_id"; });
376
416
  if (setCols.length > 0) {
377
- // Column names came from the validated policy.columns
378
- // also wrap each in "..." for the same identifier-quoting
379
- // convention.
380
- var setSql = setCols.map(function (k) { return '"' + k + '" = ?'; }).join(", ");
381
- var vals = setCols.map(function (k) { return update[k]; });
382
- vals.push(row._id);
383
- await clusterStorage.execute(
384
- "UPDATE " + qTable + " SET " + setSql + " WHERE _id = ?",
385
- vals
386
- );
417
+ // Column names came from the validated policy.columns. b.sql
418
+ // quotes every SET target + binds every value; the operator app
419
+ // table is quoted (quoteName) and not framework-rewritten.
420
+ var setMap = {};
421
+ for (var sc = 0; sc < setCols.length; sc++) setMap[setCols[sc]] = update[setCols[sc]];
422
+ var updBuilt = sql.update(table, _appSqlOpts())
423
+ .set(setMap)
424
+ .where("_id", row._id)
425
+ .toSql();
426
+ await clusterStorage.execute(updBuilt.sql, updBuilt.params);
387
427
  migratedRows++;
388
428
  }
389
429
  } else {
@@ -658,17 +698,20 @@ async function policySet(table, opts, callerOpts) {
658
698
  auditReasonStorage: validated.auditReasonStorage,
659
699
  updatedAt: Date.now(),
660
700
  };
661
- var sealed = cryptoField.sealRow("_blamejs_break_glass_policies", policyRow);
662
- // UPSERT both Postgres and SQLite support ON CONFLICT.
701
+ var sealed = cryptoField.sealRow(POLICIES_TABLE, policyRow);
702
+ // UPSERT via b.sql ON CONFLICT(tableName) DO UPDATE (Postgres + SQLite).
703
+ // BARE logical framework table — clusterStorage rewrites + placeholderizes;
704
+ // b.sql quotes every column + binds every sealed value. The conflict key
705
+ // (tableName) is excluded from the DO UPDATE set.
663
706
  var keys = Object.keys(sealed);
664
- var cols = keys.join(", ");
665
- var qs = keys.map(function () { return "?"; }).join(", ");
666
- var setSql = keys.filter(function (k) { return k !== "tableName"; })
667
- .map(function (k) { return k + " = excluded." + k; }).join(", ");
668
- var sql = "INSERT INTO _blamejs_break_glass_policies (" + cols + ") " +
669
- "VALUES (" + qs + ") " +
670
- "ON CONFLICT (tableName) DO UPDATE SET " + setSql;
671
- await clusterStorage.execute(sql, keys.map(function (k) { return sealed[k]; }));
707
+ var setCols = keys.filter(function (k) { return k !== "tableName"; });
708
+ var policyBuilt = sql.upsert("_blamejs_break_glass_policies", _sqlOpts()) // allow:hand-rolled-sql
709
+ .columns(keys)
710
+ .values(sealed)
711
+ .onConflict(["tableName"])
712
+ .doUpdateFromExcluded(setCols)
713
+ .toSql();
714
+ await clusterStorage.execute(policyBuilt.sql, policyBuilt.params);
672
715
  policyCache.delete(table);
673
716
 
674
717
  audit.safeEmit({
@@ -711,15 +754,15 @@ async function policyGet(table) {
711
754
  _requireInit();
712
755
  if (typeof table !== "string" || table.length === 0) return null;
713
756
  if (policyCache.has(table)) return policyCache.get(table);
714
- var rows = await clusterStorage.executeAll(
715
- "SELECT * FROM _blamejs_break_glass_policies WHERE tableName = ?",
716
- [table]
717
- );
757
+ var getBuilt = sql.select("_blamejs_break_glass_policies", _sqlOpts()) // allow:hand-rolled-sql
758
+ .where("tableName", table)
759
+ .toSql();
760
+ var rows = await clusterStorage.executeAll(getBuilt.sql, getBuilt.params);
718
761
  if (!rows || rows.length === 0) {
719
762
  policyCache.set(table, null);
720
763
  return null;
721
764
  }
722
- var unsealed = cryptoField.unsealRow("_blamejs_break_glass_policies", rows[0]);
765
+ var unsealed = cryptoField.unsealRow(POLICIES_TABLE, rows[0]);
723
766
  var policy = {
724
767
  table: unsealed.tableName,
725
768
  columns: safeJson.parse(unsealed.columnsJson, { maxBytes: C.BYTES.kib(64) }),
@@ -763,9 +806,11 @@ async function policyGet(table) {
763
806
  */
764
807
  async function policyList() {
765
808
  _requireInit();
766
- var rows = await clusterStorage.executeAll(
767
- "SELECT tableName FROM _blamejs_break_glass_policies ORDER BY tableName"
768
- );
809
+ var listBuilt = sql.select("_blamejs_break_glass_policies", _sqlOpts()) // allow:hand-rolled-sql
810
+ .columns(["tableName"])
811
+ .orderBy("tableName", "asc")
812
+ .toSql();
813
+ var rows = await clusterStorage.executeAll(listBuilt.sql, listBuilt.params);
769
814
  var out = [];
770
815
  for (var i = 0; i < (rows || []).length; i++) {
771
816
  var p = await policyGet(rows[i].tableName);
@@ -797,10 +842,10 @@ async function policyDelete(table, callerOpts) {
797
842
  throw new BreakGlassError("breakglass/bad-policy",
798
843
  "policy.delete: table must be a non-empty string");
799
844
  }
800
- await clusterStorage.execute(
801
- "DELETE FROM _blamejs_break_glass_policies WHERE tableName = ?",
802
- [table]
803
- );
845
+ var delBuilt = sql.delete("_blamejs_break_glass_policies", _sqlOpts()) // allow:hand-rolled-sql
846
+ .where("tableName", table)
847
+ .toSql();
848
+ await clusterStorage.execute(delBuilt.sql, delBuilt.params);
804
849
  policyCache.delete(table);
805
850
  audit.safeEmit({
806
851
  action: "breakglass.policy.delete",
@@ -817,10 +862,58 @@ function _verifyTotpFactor(factor) {
817
862
  if (!factor || typeof factor !== "object") return { ok: false };
818
863
  if (typeof factor.secret !== "string" || factor.secret.length === 0) return { ok: false };
819
864
  if (typeof factor.code !== "string" || factor.code.length === 0) return { ok: false };
820
- var verified = totp.verify(factor.secret, factor.code);
865
+ // factor.now threads a deterministic test clock into totp.verify. The
866
+ // replay floor is NOT applied here: acceptance reserves the matched step
867
+ // atomically in _reserveTotpStep, so two concurrent grants presenting the
868
+ // same in-window code cannot both pass (a read-then-commit floor races —
869
+ // both reads observe the old floor before either commits). totp.verify
870
+ // returns the step the code matches (a fixed value for a given code within
871
+ // the drift window) or false; the reserve then floors replays of that step.
872
+ var vopts = {};
873
+ if (typeof factor.now === "number") vopts.now = factor.now;
874
+ var verified = totp.verify(factor.secret, factor.code, vopts);
821
875
  return { ok: verified !== false, step: verified };
822
876
  }
823
877
 
878
+ // Replay-step cache key. Keyed by BOTH the actorId AND a non-reversible
879
+ // fingerprint of the TOTP secret. Keying on actorId alone would falsely
880
+ // reject a legitimate second grant when two distinct credentials accept a
881
+ // code at the same TOTP step (the step number is a wall-clock counter, not
882
+ // per-credential) — the secret fingerprint disambiguates them. The secret
883
+ // never reaches the cache in any reversible form.
884
+ function _replayStepKey(actorId, secret) {
885
+ return "totp-step:" + actorId + ":" + sha3Hash(secret);
886
+ }
887
+
888
+ // Atomically reserve the accepted TOTP step for (actorId, secret): advance
889
+ // the stored replay floor to `step` only when `step` is strictly above the
890
+ // current floor, and report whether THIS caller won the reservation. The
891
+ // compare-and-advance is one atomic cache update, so two concurrent grant()
892
+ // calls presenting the same in-window code cannot both pass — the first wins
893
+ // and raises the floor to `step`, the second observes step <= floor and is
894
+ // refused. (A separate read-then-commit sequence let both reads see the old
895
+ // floor before either committed, so both verified — the replay this closes.)
896
+ // The TTL outlives the verify drift window many times over so a replayed code
897
+ // stays floored until it expires.
898
+ //
899
+ // Fails CLOSED (returns false) on a cache fault: a grant cannot proceed
900
+ // without a working factor cache regardless — the lockout check at the top of
901
+ // grant() already gates on the same cache — so refusing here can only reject,
902
+ // never loosen replay protection.
903
+ async function _reserveTotpStep(actorId, secret, step) {
904
+ _ensureFactorLockout();
905
+ if (typeof step !== "number") return false;
906
+ var won = false;
907
+ try {
908
+ await _factorLockoutCache.update(_replayStepKey(actorId, secret), function (prior) {
909
+ if (typeof prior === "number" && step <= prior) { won = false; return { value: prior }; }
910
+ won = true;
911
+ return { value: step };
912
+ }, { ttlMs: REPLAY_STEP_TTL_MS });
913
+ } catch (_e) { return false; }
914
+ return won;
915
+ }
916
+
824
917
  // Passkey factor — operator presents a WebAuthn assertion plus the
825
918
  // challenge/origin/RPID + the previously-enrolled credential record.
826
919
  // Phishing-resistant; the private key lives on the YubiKey, not in
@@ -990,8 +1083,20 @@ async function grant(opts) {
990
1083
  }
991
1084
 
992
1085
  var factorOk = false;
1086
+ var totpSecret = null;
993
1087
  if (factorType === "totp") {
994
- factorOk = _verifyTotpFactor(opts.factor).ok;
1088
+ totpSecret = opts.factor && opts.factor.secret;
1089
+ // Verify the code, then atomically reserve the step it matched as the act
1090
+ // of acceptance. The reserve advances the per-(actor,secret) replay floor
1091
+ // in one compare-and-set, so a code already redeemed inside the drift
1092
+ // window — including by a concurrent grant for the same credential — is
1093
+ // refused. (A read-then-commit floor raced: both grants read the old
1094
+ // floor before either committed, so both passed.)
1095
+ var totpResult = _verifyTotpFactor(opts.factor);
1096
+ if (totpResult.ok && typeof totpResult.step === "number" &&
1097
+ typeof totpSecret === "string" && totpSecret.length > 0) {
1098
+ factorOk = await _reserveTotpStep(actorId, totpSecret, totpResult.step);
1099
+ }
995
1100
  } else if (factorType === "passkey") {
996
1101
  factorOk = (await _verifyPasskeyFactor(opts.factor)).ok;
997
1102
  }
@@ -1036,14 +1141,13 @@ async function grant(opts) {
1036
1141
  ip: ipFromReq,
1037
1142
  kwGrantHalf: null,
1038
1143
  };
1039
- var sealed = cryptoField.sealRow("_blamejs_break_glass_grants", grantRow);
1040
- var keys = Object.keys(sealed);
1041
- var cols = keys.join(", ");
1042
- var qs = keys.map(function () { return "?"; }).join(", ");
1043
- await clusterStorage.execute(
1044
- "INSERT INTO _blamejs_break_glass_grants (" + cols + ") VALUES (" + qs + ")",
1045
- keys.map(function (k) { return sealed[k]; })
1046
- );
1144
+ var sealed = cryptoField.sealRow(GRANTS_TABLE, grantRow);
1145
+ // BARE logical framework table — clusterStorage rewrites + placeholderizes;
1146
+ // b.sql quotes every column + binds every sealed value.
1147
+ var grantInsBuilt = sql.insert("_blamejs_break_glass_grants", _sqlOpts()) // allow:hand-rolled-sql
1148
+ .values(sealed)
1149
+ .toSql();
1150
+ await clusterStorage.execute(grantInsBuilt.sql, grantInsBuilt.params);
1047
1151
 
1048
1152
  // Audit
1049
1153
  var reasonForAudit = _reasonForAudit(reason, policy.auditReasonStorage);
@@ -1086,6 +1190,78 @@ function _reasonForAudit(reason, mode) {
1086
1190
  return out;
1087
1191
  }
1088
1192
 
1193
+ // Enforce the grant's IP / session bindings at redemption. policy.set
1194
+ // documents pinIp / sessionPin as default-ON, and grant() captures
1195
+ // grantRow.ip / grantRow.sessionId at mint time — but without this gate
1196
+ // the bindings are stored-and-never-enforced (a grant minted from IP-A
1197
+ // would redeem from IP-B). Called BEFORE the SELECT-then-increment so a
1198
+ // mismatch does not consume a grant.
1199
+ //
1200
+ // FAIL-CLOSED: when a pin is requested but the binding was captured null
1201
+ // (e.g. an Express-shaped req whose IP requestHelpers.clientIp couldn't
1202
+ // read at mint time), the redemption is REFUSED rather than silently
1203
+ // skipped — a `grantRow.ip != null` short-circuit would defeat the pin
1204
+ // for exactly the requests whose binding capture failed.
1205
+ function _enforceGrantPins(policy, grantRow, redeemReq, actorFor) {
1206
+ if (!policy) return;
1207
+ if (policy.pinIp) {
1208
+ if (grantRow.ip == null) {
1209
+ audit.safeEmit({
1210
+ action: "breakglass.unsealrow",
1211
+ outcome: "denied",
1212
+ actor: actorFor(grantRow),
1213
+ reason: "grant-ip-binding-missing",
1214
+ metadata: { grantId: grantRow._id, table: grantRow.scopeTable },
1215
+ });
1216
+ throw new BreakGlassError("breakglass/grant-ip-mismatch",
1217
+ "unsealRow: grant " + grantRow._id + " has pinIp on but no IP was " +
1218
+ "captured at mint (fail-closed) — re-mint from a request whose client " +
1219
+ "IP the framework can resolve", true);
1220
+ }
1221
+ var redeemIp = requestHelpers.clientIp(redeemReq, { trustProxy: _trustProxy });
1222
+ if (redeemIp !== grantRow.ip) {
1223
+ audit.safeEmit({
1224
+ action: "breakglass.unsealrow",
1225
+ outcome: "denied",
1226
+ actor: actorFor(grantRow),
1227
+ reason: "grant-ip-mismatch",
1228
+ metadata: { grantId: grantRow._id, table: grantRow.scopeTable },
1229
+ });
1230
+ throw new BreakGlassError("breakglass/grant-ip-mismatch",
1231
+ "unsealRow: grant " + grantRow._id + " is pinned to its issuing IP " +
1232
+ "and this redemption arrived from a different address", true);
1233
+ }
1234
+ }
1235
+ if (policy.sessionPin) {
1236
+ if (grantRow.sessionId == null) {
1237
+ audit.safeEmit({
1238
+ action: "breakglass.unsealrow",
1239
+ outcome: "denied",
1240
+ actor: actorFor(grantRow),
1241
+ reason: "grant-session-binding-missing",
1242
+ metadata: { grantId: grantRow._id, table: grantRow.scopeTable },
1243
+ });
1244
+ throw new BreakGlassError("breakglass/grant-session-mismatch",
1245
+ "unsealRow: grant " + grantRow._id + " has sessionPin on but no " +
1246
+ "session id was captured at mint (fail-closed) — re-mint from a " +
1247
+ "request carrying req.session.id", true);
1248
+ }
1249
+ var redeemSession = (redeemReq && redeemReq.session && redeemReq.session.id) || null;
1250
+ if (redeemSession !== grantRow.sessionId) {
1251
+ audit.safeEmit({
1252
+ action: "breakglass.unsealrow",
1253
+ outcome: "denied",
1254
+ actor: actorFor(grantRow),
1255
+ reason: "grant-session-mismatch",
1256
+ metadata: { grantId: grantRow._id, table: grantRow.scopeTable },
1257
+ });
1258
+ throw new BreakGlassError("breakglass/grant-session-mismatch",
1259
+ "unsealRow: grant " + grantRow._id + " is pinned to its issuing " +
1260
+ "session and this redemption arrived from a different session", true);
1261
+ }
1262
+ }
1263
+ }
1264
+
1089
1265
  // ---- Use a grant ----
1090
1266
 
1091
1267
  /**
@@ -1137,16 +1313,16 @@ async function unsealRow(grantHandle, table, rowId, opts) {
1137
1313
  throw new BreakGlassError("breakglass/bad-grant-opts",
1138
1314
  "unsealRow: rowId is required");
1139
1315
  }
1140
- var grantRows = await clusterStorage.executeAll(
1141
- "SELECT * FROM _blamejs_break_glass_grants WHERE _id = ?",
1142
- [grantHandle.id]
1143
- );
1316
+ var grantReadBuilt = sql.select("_blamejs_break_glass_grants", _sqlOpts()) // allow:hand-rolled-sql
1317
+ .where("_id", grantHandle.id)
1318
+ .toSql();
1319
+ var grantRows = await clusterStorage.executeAll(grantReadBuilt.sql, grantReadBuilt.params);
1144
1320
  if (!grantRows || grantRows.length === 0) {
1145
1321
  throw new BreakGlassError("breakglass/grant-revoked",
1146
1322
  "unsealRow: grant " + grantHandle.id + " not found (deleted or never issued)", true);
1147
1323
  }
1148
1324
  var sealedGrant = grantRows[0];
1149
- var grantRow = cryptoField.unsealRow("_blamejs_break_glass_grants", sealedGrant);
1325
+ var grantRow = cryptoField.unsealRow(GRANTS_TABLE, sealedGrant);
1150
1326
 
1151
1327
  // Table mismatch
1152
1328
  if (grantRow.scopeTable !== table) {
@@ -1195,35 +1371,53 @@ async function unsealRow(grantHandle, table, rowId, opts) {
1195
1371
  grantRow.maxRowsPerGrant + " allowed rows", true);
1196
1372
  }
1197
1373
 
1374
+ // IP / session pin enforcement — BEFORE the SELECT-then-increment so a
1375
+ // pin mismatch does not consume the grant. Fail-closed when a requested
1376
+ // pin's binding was captured null (see _enforceGrantPins). The policy is
1377
+ // fetched once here and reused for the Model-A/B unseal dispatch below.
1378
+ var policy = await policyGet(table);
1379
+ _enforceGrantPins(policy, grantRow, opts.req, _actorFor);
1380
+
1198
1381
  // SELECT-before-increment — fetch the target row FIRST. If the row
1199
1382
  // doesn't exist (operator typo, race with row-deletion, etc.), the
1200
1383
  // grant should not be consumed. Without this ordering, a single
1201
1384
  // typo against `maxRowsPerGrant: 1` (the default) exhausts the
1202
1385
  // grant and forces the operator to re-do the step-up ceremony.
1203
- var rows = await clusterStorage.executeAll(
1204
- "SELECT * FROM " + '"' + table + '"' + " WHERE _id = ?",
1205
- [String(rowId)]
1206
- );
1386
+ // Operator app table (validated identifier) — quoteName quotes it; it is
1387
+ // not framework-rewritten.
1388
+ var rowReadBuilt = sql.select(table, _appSqlOpts())
1389
+ .where("_id", String(rowId))
1390
+ .toSql();
1391
+ var rows = await clusterStorage.executeAll(rowReadBuilt.sql, rowReadBuilt.params);
1207
1392
  if (!rows || rows.length === 0) {
1208
1393
  throw new BreakGlassError("breakglass/row-not-found",
1209
1394
  "unsealRow: " + table + "[" + rowId + "] not found", true);
1210
1395
  }
1211
1396
 
1212
- // Increment rowsConsumed (atomic UPDATE with WHERE rowsConsumed < cap
1213
- // so concurrent unseals can't both pass the runtime check above).
1214
- var updateRes = await clusterStorage.execute(
1215
- "UPDATE _blamejs_break_glass_grants " +
1216
- "SET rowsConsumed = rowsConsumed + 1 " +
1217
- "WHERE _id = ? AND rowsConsumed < maxRowsPerGrant AND " +
1218
- "(revokedAt IS NULL) AND expiresAt > ?",
1219
- [grantHandle.id, Date.now()]
1220
- );
1397
+ // Increment rowsConsumed (atomic UPDATE with WHERE rowsConsumed < cap so
1398
+ // concurrent unseals can't both pass the runtime check above). The
1399
+ // rowsConsumed+1 RHS + the rowsConsumed<maxRowsPerGrant column comparison
1400
+ // are guarded raw fragments (b.guardSql + placeholder/literal scan). The
1401
+ // identifier quoting in those raw fragments is dialect-aware (backticks on
1402
+ // MySQL, double-quotes on PG/SQLite) so the column references resolve as
1403
+ // identifiers, not string literals, on the active backend.
1404
+ var incDialect = clusterStorage.dialect();
1405
+ var incBuilt = sql.update("_blamejs_break_glass_grants", _sqlOpts()) // allow:hand-rolled-sql
1406
+ .setRaw("rowsConsumed", safeSql.quoteIdentifier("rowsConsumed", incDialect) + " + 1", [])
1407
+ .where("_id", grantHandle.id)
1408
+ .whereRaw(safeSql.quoteIdentifier("rowsConsumed", incDialect) + " < " +
1409
+ safeSql.quoteIdentifier("maxRowsPerGrant", incDialect), [])
1410
+ .whereNull("revokedAt")
1411
+ .whereOp("expiresAt", ">", Date.now())
1412
+ .toSql();
1413
+ var updateRes = await clusterStorage.execute(incBuilt.sql, incBuilt.params);
1221
1414
  // executeAll-style result; some backends return rowsAffected, others a count.
1222
1415
  // Re-query to confirm the increment landed and get the post-increment counter.
1223
- var postRows = await clusterStorage.executeAll(
1224
- "SELECT rowsConsumed, revokedAt, expiresAt FROM _blamejs_break_glass_grants WHERE _id = ?",
1225
- [grantHandle.id]
1226
- );
1416
+ var postReadBuilt = sql.select("_blamejs_break_glass_grants", _sqlOpts()) // allow:hand-rolled-sql
1417
+ .columns(["rowsConsumed", "revokedAt", "expiresAt"])
1418
+ .where("_id", grantHandle.id)
1419
+ .toSql();
1420
+ var postRows = await clusterStorage.executeAll(postReadBuilt.sql, postReadBuilt.params);
1227
1421
  if (!postRows || postRows.length === 0) {
1228
1422
  throw new BreakGlassError("breakglass/grant-revoked",
1229
1423
  "unsealRow: grant " + grantHandle.id + " disappeared during unseal", true);
@@ -1237,7 +1431,8 @@ async function unsealRow(grantHandle, table, rowId, opts) {
1237
1431
  "unsealRow: grant " + grantHandle.id + " was exhausted by a concurrent read", true);
1238
1432
  }
1239
1433
  void updateRes;
1240
- var policy = await policyGet(table);
1434
+ // policy was fetched above for the pin enforcement; reuse it for the
1435
+ // Model-A vs Model-B (cryptographic) unseal dispatch.
1241
1436
  var unsealedRow;
1242
1437
  if (policy && policy.cryptographic) {
1243
1438
  // Snapshot the raw glass-locked column ciphertexts BEFORE
@@ -1325,11 +1520,14 @@ async function revoke(grantId, opts) {
1325
1520
  }
1326
1521
  opts = opts || {};
1327
1522
  var nowMs = Date.now();
1328
- await clusterStorage.execute(
1329
- "UPDATE _blamejs_break_glass_grants SET revokedAt = ? " +
1330
- "WHERE _id = ? AND revokedAt IS NULL",
1331
- [nowMs, grantId]
1332
- );
1523
+ // revokedAt IS NULL keeps the revoke idempotent (already-revoked grants
1524
+ // keep their original timestamp).
1525
+ var revBuilt = sql.update("_blamejs_break_glass_grants", _sqlOpts()) // allow:hand-rolled-sql
1526
+ .set({ revokedAt: nowMs })
1527
+ .where("_id", grantId)
1528
+ .whereNull("revokedAt")
1529
+ .toSql();
1530
+ await clusterStorage.execute(revBuilt.sql, revBuilt.params);
1333
1531
  audit.safeEmit({
1334
1532
  action: "breakglass.grant.revoked",
1335
1533
  outcome: "success",
@@ -1372,19 +1570,25 @@ async function listActive(opts) {
1372
1570
  // Use cryptoField's computeDerived so the hash matches the table's
1373
1571
  // hashNamespace prefix — raw sha3Hash would produce a different value.
1374
1572
  var derived = cryptoField.computeDerived(
1375
- "_blamejs_break_glass_grants", "issuedToActorId", actorId
1573
+ GRANTS_TABLE, "issuedToActorId", actorId
1376
1574
  );
1377
1575
  if (!derived) return [];
1378
1576
  var nowMs = Date.now();
1379
- var rows = await clusterStorage.executeAll(
1380
- "SELECT * FROM _blamejs_break_glass_grants " +
1381
- "WHERE issuedToActorHash = ? AND (revokedAt IS NULL) AND expiresAt > ? AND rowsConsumed < maxRowsPerGrant " +
1382
- "ORDER BY issuedAt DESC",
1383
- [derived.value, nowMs]
1384
- );
1577
+ // rowsConsumed < maxRowsPerGrant is a column-to-column comparison (guarded
1578
+ // raw fragment); every other predicate is structured.
1579
+ var laDialect = clusterStorage.dialect();
1580
+ var laBuilt = sql.select("_blamejs_break_glass_grants", _sqlOpts()) // allow:hand-rolled-sql
1581
+ .where("issuedToActorHash", derived.value)
1582
+ .whereNull("revokedAt")
1583
+ .whereOp("expiresAt", ">", nowMs)
1584
+ .whereRaw(safeSql.quoteIdentifier("rowsConsumed", laDialect) + " < " +
1585
+ safeSql.quoteIdentifier("maxRowsPerGrant", laDialect), [])
1586
+ .orderBy("issuedAt", "desc")
1587
+ .toSql();
1588
+ var rows = await clusterStorage.executeAll(laBuilt.sql, laBuilt.params);
1385
1589
  var out = [];
1386
1590
  for (var i = 0; i < (rows || []).length; i++) {
1387
- var u = cryptoField.unsealRow("_blamejs_break_glass_grants", rows[i]);
1591
+ var u = cryptoField.unsealRow(GRANTS_TABLE, rows[i]);
1388
1592
  out.push({
1389
1593
  id: u._id,
1390
1594
  scopeTable: u.scopeTable,
@@ -1427,6 +1631,12 @@ async function listActive(opts) {
1427
1631
  * distinct `breakglass.grant.bypass` audit row so post-incident review
1428
1632
  * separates operator-initiated reads from scheduled-job reads.
1429
1633
  *
1634
+ * This path is service-to-service: it consumes NO grant row, so the
1635
+ * `pinIp` / `sessionPin` grant bindings enforced by `unsealRow` do not
1636
+ * apply here. A grant that was minted with those pins is not redeemable
1637
+ * through this surface — the bypass is gated solely by the
1638
+ * `serviceAccountBypass` allowlist + required-role check.
1639
+ *
1430
1640
  * @opts
1431
1641
  * reason: string, // operator-supplied reason recorded into the audit row
1432
1642
  *
@@ -1494,11 +1704,12 @@ async function unsealRowAsService(req, table, rowId, opts) {
1494
1704
  }
1495
1705
 
1496
1706
  // Fetch + unseal the row (Model A or Model B path, same as
1497
- // operator-initiated unsealRow).
1498
- var rows = await clusterStorage.executeAll(
1499
- "SELECT * FROM " + '"' + table + '"' + " WHERE _id = ?",
1500
- [String(rowId)]
1501
- );
1707
+ // operator-initiated unsealRow). Operator app table — quoteName quotes it;
1708
+ // it is not framework-rewritten.
1709
+ var svcRowBuilt = sql.select(table, _appSqlOpts())
1710
+ .where("_id", String(rowId))
1711
+ .toSql();
1712
+ var rows = await clusterStorage.executeAll(svcRowBuilt.sql, svcRowBuilt.params);
1502
1713
  if (!rows || rows.length === 0) {
1503
1714
  throw new BreakGlassError("breakglass/row-not-found",
1504
1715
  "unsealRowAsService: " + table + "[" + rowId + "] not found", true);
@@ -1572,24 +1783,22 @@ async function listActiveAll(opts) {
1572
1783
  _requireInit();
1573
1784
  opts = opts || {};
1574
1785
  var nowMs = Date.now();
1575
- var clauses = ["(revokedAt IS NULL)", "expiresAt > ?", "rowsConsumed < maxRowsPerGrant"];
1576
- var params = [nowMs];
1577
- if (opts.table) {
1578
- clauses.push("scopeTable = ?");
1579
- params.push(opts.table);
1580
- }
1581
- if (opts.since) {
1582
- clauses.push("issuedAt >= ?");
1583
- params.push(opts.since);
1584
- }
1585
- var rows = await clusterStorage.executeAll(
1586
- "SELECT * FROM _blamejs_break_glass_grants WHERE " + clauses.join(" AND ") +
1587
- " ORDER BY issuedAt DESC",
1588
- params
1589
- );
1786
+ // rowsConsumed < maxRowsPerGrant is a column-to-column comparison (guarded
1787
+ // raw fragment); the rest are structured predicates.
1788
+ var laaDialect = clusterStorage.dialect();
1789
+ var laaQb = sql.select("_blamejs_break_glass_grants", _sqlOpts()) // allow:hand-rolled-sql
1790
+ .whereNull("revokedAt")
1791
+ .whereOp("expiresAt", ">", nowMs)
1792
+ .whereRaw(safeSql.quoteIdentifier("rowsConsumed", laaDialect) + " < " +
1793
+ safeSql.quoteIdentifier("maxRowsPerGrant", laaDialect), []);
1794
+ if (opts.table) laaQb.where("scopeTable", opts.table);
1795
+ if (opts.since) laaQb.whereOp("issuedAt", ">=", opts.since);
1796
+ laaQb.orderBy("issuedAt", "desc");
1797
+ var laaBuilt = laaQb.toSql();
1798
+ var rows = await clusterStorage.executeAll(laaBuilt.sql, laaBuilt.params);
1590
1799
  var out = [];
1591
1800
  for (var i = 0; i < (rows || []).length; i++) {
1592
- var u = cryptoField.unsealRow("_blamejs_break_glass_grants", rows[i]);
1801
+ var u = cryptoField.unsealRow(GRANTS_TABLE, rows[i]);
1593
1802
  out.push({
1594
1803
  id: u._id,
1595
1804
  issuedToActorId: u.issuedToActorId,
@@ -1646,31 +1855,28 @@ async function revokeAll(criteria, opts) {
1646
1855
  "revokeAll: at least one of { actorId, table } is required (refusing to mass-revoke without scope)");
1647
1856
  }
1648
1857
  opts = opts || {};
1649
- var clauses = ["revokedAt IS NULL"];
1650
- var params = [];
1651
- if (criteria.actorId) {
1652
- var derived = cryptoField.computeDerived(
1653
- "_blamejs_break_glass_grants", "issuedToActorId", criteria.actorId
1654
- );
1655
- if (derived) {
1656
- clauses.push("issuedToActorHash = ?");
1657
- params.push(derived.value);
1658
- }
1659
- }
1660
- if (criteria.table) {
1661
- clauses.push("scopeTable = ?");
1662
- params.push(criteria.table);
1858
+ // The SELECT (snapshot ids) and UPDATE (apply revoke) share one predicate
1859
+ // set; applyRevokeCriteria replays it onto either builder so the WHERE can
1860
+ // never drift between the two.
1861
+ var derived = criteria.actorId
1862
+ ? cryptoField.computeDerived(GRANTS_TABLE, "issuedToActorId", criteria.actorId)
1863
+ : null;
1864
+ function applyRevokeCriteria(qb) {
1865
+ qb.whereNull("revokedAt");
1866
+ if (criteria.actorId && derived) qb.where("issuedToActorHash", derived.value);
1867
+ if (criteria.table) qb.where("scopeTable", criteria.table);
1868
+ return qb;
1663
1869
  }
1664
1870
  // Snapshot the to-be-revoked grant ids first so audit captures specifics.
1665
- var ids = await clusterStorage.executeAll(
1666
- "SELECT _id FROM _blamejs_break_glass_grants WHERE " + clauses.join(" AND "),
1667
- params
1668
- );
1871
+ var idSelBuilt = applyRevokeCriteria(
1872
+ sql.select("_blamejs_break_glass_grants", _sqlOpts()).columns(["_id"]) // allow:hand-rolled-sql
1873
+ ).toSql();
1874
+ var ids = await clusterStorage.executeAll(idSelBuilt.sql, idSelBuilt.params);
1669
1875
  var nowMs = Date.now();
1670
- await clusterStorage.execute(
1671
- "UPDATE _blamejs_break_glass_grants SET revokedAt = ? WHERE " + clauses.join(" AND "),
1672
- [nowMs].concat(params)
1673
- );
1876
+ var revAllBuilt = applyRevokeCriteria(
1877
+ sql.update("_blamejs_break_glass_grants", _sqlOpts()).set({ revokedAt: nowMs }) // allow:hand-rolled-sql
1878
+ ).toSql();
1879
+ await clusterStorage.execute(revAllBuilt.sql, revAllBuilt.params);
1674
1880
  audit.safeEmit({
1675
1881
  action: "breakglass.admin.revokeall",
1676
1882
  outcome: "success",
@@ -1691,11 +1897,12 @@ async function revokeAll(criteria, opts) {
1691
1897
  async function _sweepExpired(opts) {
1692
1898
  opts = opts || {};
1693
1899
  var nowMs = Date.now();
1694
- var expired = await clusterStorage.executeAll(
1695
- "SELECT _id, issuedToActorId, scopeTable, rowsConsumed FROM _blamejs_break_glass_grants " +
1696
- "WHERE revokedAt IS NULL AND expiresAt <= ?",
1697
- [nowMs]
1698
- );
1900
+ var expiredBuilt = sql.select("_blamejs_break_glass_grants", _sqlOpts()) // allow:hand-rolled-sql
1901
+ .columns(["_id", "issuedToActorId", "scopeTable", "rowsConsumed"])
1902
+ .whereNull("revokedAt")
1903
+ .whereOp("expiresAt", "<=", nowMs)
1904
+ .toSql();
1905
+ var expired = await clusterStorage.executeAll(expiredBuilt.sql, expiredBuilt.params);
1699
1906
  for (var i = 0; i < (expired || []).length; i++) {
1700
1907
  var row = expired[i];
1701
1908
  audit.safeEmit({
@@ -1705,11 +1912,12 @@ async function _sweepExpired(opts) {
1705
1912
  metadata: { grantId: row._id, table: row.scopeTable, rowsConsumed: Number(row.rowsConsumed) },
1706
1913
  });
1707
1914
  }
1708
- await clusterStorage.execute(
1709
- "UPDATE _blamejs_break_glass_grants SET revokedAt = ? " +
1710
- "WHERE revokedAt IS NULL AND expiresAt <= ?",
1711
- [nowMs, nowMs]
1712
- );
1915
+ var sweepUpdBuilt = sql.update("_blamejs_break_glass_grants", _sqlOpts()) // allow:hand-rolled-sql
1916
+ .set({ revokedAt: nowMs })
1917
+ .whereNull("revokedAt")
1918
+ .whereOp("expiresAt", "<=", nowMs)
1919
+ .toSql();
1920
+ await clusterStorage.execute(sweepUpdBuilt.sql, sweepUpdBuilt.params);
1713
1921
  return { expired: (expired || []).length };
1714
1922
  }
1715
1923