@blamejs/core 0.15.12 → 0.15.14

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 (365) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/index.js +2 -0
  3. package/lib/a2a-tasks.js +45 -29
  4. package/lib/acme.js +6 -5
  5. package/lib/agent-event-bus.js +18 -4
  6. package/lib/agent-idempotency.js +7 -6
  7. package/lib/agent-orchestrator.js +2 -5
  8. package/lib/agent-saga.js +3 -5
  9. package/lib/agent-snapshot.js +32 -2
  10. package/lib/agent-tenant.js +2 -5
  11. package/lib/ai-adverse-decision.js +2 -15
  12. package/lib/ai-aedt-bias-audit.js +2 -1
  13. package/lib/ai-capability.js +1 -6
  14. package/lib/ai-content-detect.js +1 -3
  15. package/lib/ai-dp.js +1 -5
  16. package/lib/ai-frontier-protocol.js +1 -1
  17. package/lib/ai-input.js +2 -2
  18. package/lib/ai-model-manifest.js +1 -1
  19. package/lib/ai-output.js +16 -7
  20. package/lib/ai-pref.js +3 -8
  21. package/lib/ai-quota.js +3 -14
  22. package/lib/api-key.js +37 -28
  23. package/lib/api-snapshot.js +4 -1
  24. package/lib/app-shutdown.js +7 -1
  25. package/lib/archive-adapters.js +2 -4
  26. package/lib/archive-entry-policy.js +32 -0
  27. package/lib/archive-gz.js +9 -0
  28. package/lib/archive-read.js +14 -24
  29. package/lib/archive-tar-read.js +56 -24
  30. package/lib/archive.js +6 -12
  31. package/lib/arg-parser.js +7 -6
  32. package/lib/asn1-der.js +70 -22
  33. package/lib/asyncapi-traits.js +2 -6
  34. package/lib/atomic-file.js +312 -33
  35. package/lib/audit-chain.js +183 -54
  36. package/lib/audit-daily-review.js +36 -16
  37. package/lib/audit-emit.js +82 -0
  38. package/lib/audit-sign.js +258 -0
  39. package/lib/audit-tools.js +108 -23
  40. package/lib/audit.js +91 -2
  41. package/lib/auth/access-lock.js +7 -29
  42. package/lib/auth/bot-challenge.js +1 -1
  43. package/lib/auth/ciba.js +43 -4
  44. package/lib/auth/dpop.js +29 -36
  45. package/lib/auth/fido-mds3.js +14 -16
  46. package/lib/auth/jwt-external.js +26 -8
  47. package/lib/auth/jwt.js +15 -17
  48. package/lib/auth/lockout.js +24 -27
  49. package/lib/auth/oauth.js +67 -45
  50. package/lib/auth/oid4vci.js +55 -32
  51. package/lib/auth/oid4vp.js +3 -2
  52. package/lib/auth/openid-federation.js +6 -6
  53. package/lib/auth/passkey.js +4 -4
  54. package/lib/auth/password.js +8 -2
  55. package/lib/auth/saml.js +70 -40
  56. package/lib/auth/sd-jwt-vc-holder.js +2 -14
  57. package/lib/auth/sd-jwt-vc-issuer.js +2 -14
  58. package/lib/auth/sd-jwt-vc.js +25 -5
  59. package/lib/auth/status-list.js +21 -9
  60. package/lib/auth/step-up.js +15 -13
  61. package/lib/auth-bot-challenge.js +27 -39
  62. package/lib/backup/bundle.js +18 -20
  63. package/lib/backup/crypto.js +23 -8
  64. package/lib/backup/index.js +55 -67
  65. package/lib/backup/manifest.js +7 -1
  66. package/lib/bounded-map.js +112 -1
  67. package/lib/breach-deadline.js +1 -11
  68. package/lib/break-glass.js +41 -22
  69. package/lib/cache.js +7 -18
  70. package/lib/cbor.js +36 -12
  71. package/lib/cdn-cache-control.js +15 -12
  72. package/lib/cert.js +8 -13
  73. package/lib/chain-writer.js +162 -47
  74. package/lib/cli.js +10 -2
  75. package/lib/client-hints.js +7 -9
  76. package/lib/cloud-events.js +43 -33
  77. package/lib/cluster-storage.js +9 -41
  78. package/lib/cms-codec.js +2 -1
  79. package/lib/codepoint-class.js +84 -1
  80. package/lib/compliance-ai-act-logging.js +1 -6
  81. package/lib/compliance-ai-act-transparency.js +2 -4
  82. package/lib/compliance-eaa.js +2 -12
  83. package/lib/compliance-sanctions-fetcher.js +21 -28
  84. package/lib/compliance-sanctions.js +11 -21
  85. package/lib/compliance.js +3 -10
  86. package/lib/config-drift.js +24 -20
  87. package/lib/content-credentials.js +11 -8
  88. package/lib/content-digest.js +21 -12
  89. package/lib/cookies.js +15 -13
  90. package/lib/cose.js +39 -11
  91. package/lib/cra-report.js +1 -11
  92. package/lib/crdt.js +2 -1
  93. package/lib/crypto-field.js +34 -33
  94. package/lib/crypto.js +138 -7
  95. package/lib/csp.js +239 -3
  96. package/lib/daemon.js +46 -42
  97. package/lib/data-act.js +46 -13
  98. package/lib/db-file-lifecycle.js +14 -6
  99. package/lib/db-query.js +75 -49
  100. package/lib/db.js +66 -27
  101. package/lib/dbsc.js +5 -6
  102. package/lib/ddl-change-control.js +18 -14
  103. package/lib/deprecate.js +4 -5
  104. package/lib/did.js +6 -2
  105. package/lib/dora.js +43 -13
  106. package/lib/dr-runbook.js +1 -1
  107. package/lib/dsa.js +2 -1
  108. package/lib/dsr.js +30 -20
  109. package/lib/early-hints.js +19 -0
  110. package/lib/eat.js +5 -1
  111. package/lib/external-db-migrate.js +21 -22
  112. package/lib/external-db.js +74 -30
  113. package/lib/fda-21cfr11.js +42 -13
  114. package/lib/fdx.js +22 -18
  115. package/lib/file-upload.js +80 -66
  116. package/lib/flag-evaluation-context.js +5 -8
  117. package/lib/flag-providers.js +6 -2
  118. package/lib/forms.js +1 -1
  119. package/lib/framework-error.js +12 -0
  120. package/lib/framework-schema.js +19 -0
  121. package/lib/fsm.js +7 -12
  122. package/lib/gate-contract.js +911 -39
  123. package/lib/gdpr-ropa.js +22 -22
  124. package/lib/graphql-federation.js +18 -5
  125. package/lib/guard-agent-registry.js +2 -1
  126. package/lib/guard-all.js +3 -2
  127. package/lib/guard-archive.js +9 -30
  128. package/lib/guard-auth.js +23 -80
  129. package/lib/guard-cidr.js +20 -96
  130. package/lib/guard-csv.js +135 -196
  131. package/lib/guard-domain.js +23 -106
  132. package/lib/guard-dsn.js +17 -14
  133. package/lib/guard-email.js +46 -53
  134. package/lib/guard-envelope.js +2 -2
  135. package/lib/guard-event-bus-topic.js +2 -1
  136. package/lib/guard-filename.js +12 -60
  137. package/lib/guard-graphql.js +28 -75
  138. package/lib/guard-html-wcag.js +15 -2
  139. package/lib/guard-html.js +74 -128
  140. package/lib/guard-idempotency-key.js +2 -1
  141. package/lib/guard-image.js +280 -77
  142. package/lib/guard-imap-command.js +9 -10
  143. package/lib/guard-jmap.js +1 -1
  144. package/lib/guard-json.js +101 -109
  145. package/lib/guard-jsonpath.js +20 -88
  146. package/lib/guard-jwt.js +32 -114
  147. package/lib/guard-list-id.js +2 -7
  148. package/lib/guard-list-unsubscribe.js +2 -7
  149. package/lib/guard-mail-compose.js +5 -6
  150. package/lib/guard-mail-move.js +3 -2
  151. package/lib/guard-mail-query.js +5 -3
  152. package/lib/guard-mail-sieve.js +6 -7
  153. package/lib/guard-managesieve-command.js +6 -5
  154. package/lib/guard-markdown.js +76 -110
  155. package/lib/guard-message-id.js +5 -6
  156. package/lib/guard-mime.js +20 -99
  157. package/lib/guard-oauth.js +19 -73
  158. package/lib/guard-pdf.js +65 -72
  159. package/lib/guard-pop3-command.js +13 -14
  160. package/lib/guard-posture-chain.js +2 -1
  161. package/lib/guard-regex.js +24 -99
  162. package/lib/guard-saga-config.js +2 -1
  163. package/lib/guard-shell.js +22 -87
  164. package/lib/guard-smtp-command.js +9 -12
  165. package/lib/guard-sql.js +15 -13
  166. package/lib/guard-stream-args.js +2 -1
  167. package/lib/guard-svg.js +103 -149
  168. package/lib/guard-template.js +23 -80
  169. package/lib/guard-tenant-id.js +2 -1
  170. package/lib/guard-text.js +592 -0
  171. package/lib/guard-time.js +27 -95
  172. package/lib/guard-uuid.js +21 -93
  173. package/lib/guard-xml.js +76 -106
  174. package/lib/guard-yaml.js +24 -60
  175. package/lib/html-balance.js +7 -3
  176. package/lib/http-client-cache.js +5 -12
  177. package/lib/http-client-cookie-jar.js +35 -16
  178. package/lib/http-client.js +233 -74
  179. package/lib/http-message-signature.js +3 -2
  180. package/lib/i18n-messageformat.js +5 -7
  181. package/lib/i18n.js +83 -26
  182. package/lib/iab-tcf.js +3 -2
  183. package/lib/importmap-integrity.js +41 -1
  184. package/lib/inbox.js +8 -8
  185. package/lib/incident-report.js +12 -27
  186. package/lib/ip-utils.js +49 -6
  187. package/lib/jobs.js +3 -2
  188. package/lib/json-patch.js +1 -1
  189. package/lib/json-path.js +24 -3
  190. package/lib/jtd.js +2 -2
  191. package/lib/keychain.js +6 -18
  192. package/lib/legal-hold.js +30 -23
  193. package/lib/log-stream-cloudwatch.js +44 -112
  194. package/lib/log-stream-otlp-grpc.js +17 -14
  195. package/lib/log-stream-otlp.js +16 -80
  196. package/lib/log-stream-webhook.js +20 -92
  197. package/lib/log.js +2 -2
  198. package/lib/mail-agent.js +2 -2
  199. package/lib/mail-arc-sign.js +8 -3
  200. package/lib/mail-arf.js +4 -3
  201. package/lib/mail-auth.js +43 -69
  202. package/lib/mail-bimi.js +35 -55
  203. package/lib/mail-bounce.js +3 -3
  204. package/lib/mail-crypto-pgp.js +3 -2
  205. package/lib/mail-crypto-smime.js +71 -6
  206. package/lib/mail-dav.js +8 -35
  207. package/lib/mail-deploy.js +15 -14
  208. package/lib/mail-dkim.js +19 -38
  209. package/lib/mail-greylist.js +17 -30
  210. package/lib/mail-helo.js +4 -7
  211. package/lib/mail-journal.js +13 -9
  212. package/lib/mail-mdn.js +12 -7
  213. package/lib/mail-rbl.js +7 -12
  214. package/lib/mail-scan.js +5 -7
  215. package/lib/mail-send-deliver.js +33 -20
  216. package/lib/mail-server-imap.js +32 -87
  217. package/lib/mail-server-jmap.js +39 -52
  218. package/lib/mail-server-managesieve.js +29 -83
  219. package/lib/mail-server-mx.js +33 -74
  220. package/lib/mail-server-net.js +177 -0
  221. package/lib/mail-server-pop3.js +30 -83
  222. package/lib/mail-server-rate-limit.js +30 -6
  223. package/lib/mail-server-registry.js +1 -1
  224. package/lib/mail-server-submission.js +34 -73
  225. package/lib/mail-server-tls.js +98 -2
  226. package/lib/mail-spam-score.js +9 -12
  227. package/lib/mail-store-fts.js +1 -1
  228. package/lib/mail-store.js +3 -2
  229. package/lib/mail.js +28 -13
  230. package/lib/markup-escape.js +31 -0
  231. package/lib/markup-tokenizer.js +78 -0
  232. package/lib/mcp-tool-registry.js +26 -23
  233. package/lib/mcp.js +7 -5
  234. package/lib/mdoc.js +36 -14
  235. package/lib/metrics.js +46 -33
  236. package/lib/middleware/age-gate.js +3 -23
  237. package/lib/middleware/api-encrypt.js +13 -24
  238. package/lib/middleware/assetlinks.js +2 -7
  239. package/lib/middleware/bearer-auth.js +2 -1
  240. package/lib/middleware/body-parser.js +41 -52
  241. package/lib/middleware/bot-disclose.js +7 -10
  242. package/lib/middleware/bot-guard.js +28 -28
  243. package/lib/middleware/clear-site-data.js +5 -1
  244. package/lib/middleware/compression.js +9 -0
  245. package/lib/middleware/cors.js +32 -23
  246. package/lib/middleware/csrf-protect.js +73 -23
  247. package/lib/middleware/daily-byte-quota.js +12 -30
  248. package/lib/middleware/deny-response.js +19 -1
  249. package/lib/middleware/fetch-metadata.js +35 -4
  250. package/lib/middleware/idempotency-key.js +4 -20
  251. package/lib/middleware/network-allowlist.js +61 -30
  252. package/lib/middleware/rate-limit.js +45 -44
  253. package/lib/middleware/scim-server.js +5 -3
  254. package/lib/middleware/security-headers.js +33 -7
  255. package/lib/middleware/security-txt.js +2 -6
  256. package/lib/middleware/speculation-rules.js +6 -3
  257. package/lib/middleware/tus-upload.js +14 -29
  258. package/lib/middleware/web-app-manifest.js +2 -7
  259. package/lib/migrations.js +4 -13
  260. package/lib/mime-parse.js +34 -17
  261. package/lib/module-loader.js +44 -0
  262. package/lib/money.js +105 -0
  263. package/lib/mtls-ca.js +125 -41
  264. package/lib/network-byte-quota.js +4 -18
  265. package/lib/network-dane.js +8 -6
  266. package/lib/network-dns-resolver.js +98 -7
  267. package/lib/network-dns.js +9 -2
  268. package/lib/network-dnssec.js +18 -17
  269. package/lib/network-heartbeat.js +3 -2
  270. package/lib/network-smtp-policy.js +52 -46
  271. package/lib/network-tls.js +67 -47
  272. package/lib/network-tsig.js +2 -2
  273. package/lib/nis2-report.js +2 -12
  274. package/lib/nist-crosswalk.js +1 -1
  275. package/lib/nonce-store.js +81 -3
  276. package/lib/ntp-check.js +28 -0
  277. package/lib/numeric-bounds.js +31 -8
  278. package/lib/object-store/azure-blob-bucket-ops.js +2 -6
  279. package/lib/object-store/azure-blob.js +6 -47
  280. package/lib/object-store/gcs-bucket-ops.js +4 -2
  281. package/lib/object-store/gcs.js +14 -75
  282. package/lib/object-store/http-put.js +2 -7
  283. package/lib/object-store/http-request.js +157 -0
  284. package/lib/object-store/local.js +37 -17
  285. package/lib/object-store/sigv4-bucket-ops.js +2 -1
  286. package/lib/object-store/sigv4.js +10 -79
  287. package/lib/observability-otlp-exporter.js +29 -29
  288. package/lib/observability.js +95 -0
  289. package/lib/openapi-paths-builder.js +7 -3
  290. package/lib/otel-export.js +7 -10
  291. package/lib/outbox.js +54 -41
  292. package/lib/pagination.js +11 -15
  293. package/lib/parsers/safe-env.js +5 -4
  294. package/lib/parsers/safe-ini.js +10 -4
  295. package/lib/parsers/safe-toml.js +11 -9
  296. package/lib/parsers/safe-xml.js +3 -3
  297. package/lib/parsers/safe-yaml.js +28 -9
  298. package/lib/pick.js +63 -5
  299. package/lib/pipl-cn.js +69 -59
  300. package/lib/privacy-pass.js +8 -6
  301. package/lib/problem-details.js +2 -5
  302. package/lib/pubsub.js +4 -8
  303. package/lib/queue-local.js +10 -3
  304. package/lib/redact.js +9 -4
  305. package/lib/render.js +4 -2
  306. package/lib/request-helpers.js +334 -29
  307. package/lib/resource-access-lock.js +3 -3
  308. package/lib/restore-bundle.js +46 -18
  309. package/lib/restore-rollback.js +10 -4
  310. package/lib/restore.js +24 -22
  311. package/lib/retention.js +23 -9
  312. package/lib/router.js +17 -4
  313. package/lib/safe-async.js +457 -0
  314. package/lib/safe-buffer.js +137 -6
  315. package/lib/safe-decompress.js +2 -1
  316. package/lib/safe-dns.js +2 -1
  317. package/lib/safe-ical.js +14 -28
  318. package/lib/safe-icap.js +1 -1
  319. package/lib/safe-json.js +51 -8
  320. package/lib/safe-jsonpath.js +6 -5
  321. package/lib/safe-mime.js +25 -29
  322. package/lib/safe-redirect.js +2 -5
  323. package/lib/safe-schema.js +7 -6
  324. package/lib/safe-sieve.js +1 -1
  325. package/lib/safe-sql.js +126 -0
  326. package/lib/safe-vcard.js +13 -27
  327. package/lib/sandbox-worker.js +15 -2
  328. package/lib/sandbox.js +4 -3
  329. package/lib/scheduler.js +22 -13
  330. package/lib/sec-cyber.js +82 -37
  331. package/lib/seeders.js +4 -11
  332. package/lib/self-update-standalone-verifier.js +16 -0
  333. package/lib/self-update.js +92 -69
  334. package/lib/session-device-binding.js +112 -66
  335. package/lib/session.js +219 -7
  336. package/lib/sql.js +228 -81
  337. package/lib/sse.js +6 -10
  338. package/lib/static.js +201 -111
  339. package/lib/storage.js +4 -2
  340. package/lib/structured-fields.js +275 -16
  341. package/lib/template.js +7 -5
  342. package/lib/tenant-quota.js +53 -38
  343. package/lib/tsa.js +16 -17
  344. package/lib/validate-opts.js +154 -8
  345. package/lib/vault/index.js +5 -0
  346. package/lib/vault/passphrase-ops.js +22 -26
  347. package/lib/vault/passphrase-source.js +8 -3
  348. package/lib/vault/rotate.js +13 -18
  349. package/lib/vault/seal-pem-file.js +34 -49
  350. package/lib/vault-aad.js +3 -2
  351. package/lib/vc.js +3 -8
  352. package/lib/vendor/MANIFEST.json +10 -10
  353. package/lib/vendor/public-suffix-list.dat +23 -10
  354. package/lib/vendor/public-suffix-list.data.js +5498 -5494
  355. package/lib/vex.js +35 -13
  356. package/lib/web-push-vapid.js +23 -20
  357. package/lib/webhook-dispatcher.js +706 -0
  358. package/lib/webhook.js +59 -19
  359. package/lib/websocket-channels.js +9 -7
  360. package/lib/websocket.js +8 -4
  361. package/lib/worm.js +3 -4
  362. package/lib/ws-client.js +65 -56
  363. package/lib/x509-chain.js +44 -0
  364. package/package.json +1 -1
  365. package/sbom.cdx.json +6 -6
@@ -38,6 +38,8 @@ var C = require("./constants");
38
38
  var clusterStorage = require("./cluster-storage");
39
39
  var frameworkSchema = require("./framework-schema");
40
40
  var sql = require("./sql");
41
+ var safeSql = require("./safe-sql");
42
+ var safeBuffer = require("./safe-buffer");
41
43
  var { sha3Hash } = require("./crypto");
42
44
 
43
45
  // b.sql opts for the chain read SQL these primitives compose. The reader
@@ -135,7 +137,7 @@ function computeRowHash(prevHash, rowFields, nonce) {
135
137
 
136
138
  /**
137
139
  * @primitive b.auditChain.getChainTip
138
- * @signature b.auditChain.getChainTip(queryOneAsync, tableName)
140
+ * @signature b.auditChain.getChainTip(queryOneAsync, tableName, opts?)
139
141
  * @since 0.4.0
140
142
  * @related b.auditChain.verifyChain, b.auditChain.computeRowHash
141
143
  *
@@ -146,6 +148,14 @@ function computeRowHash(prevHash, rowFields, nonce) {
146
148
  * drivers can use any await-able query function of the shape
147
149
  * `async (sql, params?) -> row | null`.
148
150
  *
151
+ * Pass `{ chainKey, keyValue }` to scope the tip to one partition of a
152
+ * multi-chain table (one chain per account / device / tenant) — the tip read
153
+ * filters `WHERE <chainKey> = ?` with the value bound, never interpolated.
154
+ *
155
+ * @opts
156
+ * chainKey: string, // partition column for a multi-chain table
157
+ * keyValue: any, // the partition value to scope the tip to (bound)
158
+ *
149
159
  * @example
150
160
  * async function queryOne(sql) {
151
161
  * var rows = await myDriver.query(sql);
@@ -154,16 +164,22 @@ function computeRowHash(prevHash, rowFields, nonce) {
154
164
  * var tip = await b.auditChain.getChainTip(queryOne, "audit_log");
155
165
  * // → { prevHash: "<128-char hex>", counter: 4217 }
156
166
  */
157
- async function getChainTip(queryOneAsync, tableName) {
167
+ async function getChainTip(queryOneAsync, tableName, opts) {
168
+ opts = opts || {};
158
169
  // Emit a BARE logical table name — the operator-supplied reader routes
159
170
  // through clusterStorage, which rewrites bare framework names to the
160
171
  // configured-prefix form and placeholderizes. b.sql quotes the camelCase
161
- // columns + runs the output validator.
162
- var built = sql.select(tableName, _sqlOpts())
172
+ // columns + runs the output validator. A chainKey scopes the tip to one
173
+ // partition; the key value binds as a ? placeholder.
174
+ var q = sql.select(tableName, _sqlOpts())
163
175
  .columns(["rowHash", "monotonicCounter"])
164
176
  .orderBy("monotonicCounter", "desc")
165
- .limit(1)
166
- .toSql();
177
+ .limit(1);
178
+ if (opts.chainKey) {
179
+ safeSql.validateIdentifier(opts.chainKey);
180
+ q = q.where(opts.chainKey, opts.keyValue);
181
+ }
182
+ var built = q.toSql();
167
183
  var row = await queryOneAsync(built.sql, built.params);
168
184
  if (!row) return { prevHash: ZERO_HASH, counter: 0 };
169
185
  // Normalize driver shape (Postgres returns BIGINT monotonicCounter as a
@@ -192,60 +208,32 @@ async function getChainTip(queryOneAsync, tableName) {
192
208
  * after a successful archive and lets the chain math survive deletion
193
209
  * of historical rows without the archive bundle as source of truth.
194
210
  *
211
+ * Pass `{ chainKey }` to verify a MULTI-chain table partitioned by a key
212
+ * column (one chain per account / device / tenant): each key's sub-chain is
213
+ * walked independently from `ZERO_HASH`, and the first break in any key returns
214
+ * `{ ok:false, chainKey, breakAt, ... }`. Under `chainKey`, `maxRows` is
215
+ * per-sub-chain and `maxChains` bounds the partition fan-out, failing closed
216
+ * when exceeded. The `audit_log` purge-anchor logic is single-chain-only and
217
+ * is skipped when a `chainKey` is given.
218
+ *
195
219
  * @opts
196
- * {
197
- * maxRows?: number, // stop after N rows (default: walk every row)
198
- * }
220
+ * maxRows: number, // stop after N rows per (sub-)chain (default: walk every row)
221
+ * chainKey: string, // partition column verify each sub-chain independently
222
+ * maxChains: number, // max partitions to verify under chainKey (default 100000; fails closed)
223
+ * from: number, // single-chain only: verify rows with monotonicCounter >= from, anchored at the predecessor's rowHash (incremental verify after a known-good checkpoint)
224
+ * to: number, // single-chain only: verify rows with monotonicCounter <= to
199
225
  *
200
226
  * @example
201
227
  * async function queryAll(sql) { return await myDriver.query(sql); }
202
228
  * var result = await b.auditChain.verifyChain(queryAll, "audit_log", {});
203
229
  * // → { ok: true, table: "audit_log", rowsVerified: 4217, lastHash: "<hex>" }
204
230
  */
205
- async function verifyChain(queryAllAsync, tableName, opts) {
206
- opts = opts || {};
207
-
208
- var prevHash = ZERO_HASH;
209
- var skipBeforeCounter = 0;
210
- if (tableName === "audit_log") {
211
- var anchor;
212
- try {
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);
222
- } catch (_e) {
223
- // Anchor table may not exist on a deployment that has never been
224
- // through a purge. Treat as no anchor.
225
- anchor = [];
226
- }
227
- if (Array.isArray(anchor) && anchor.length > 0) {
228
- prevHash = anchor[0].lastPurgedRowHash;
229
- skipBeforeCounter = Number(anchor[0].lastPurgedCounter);
230
- }
231
- }
232
-
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);
243
- if (skipBeforeCounter > 0) {
244
- rows = rows.filter(function (r) {
245
- return Number(r.monotonicCounter) > skipBeforeCounter;
246
- });
247
- }
248
-
231
+ // Walk one (sub-)chain forward from startPrevHash, recomputing each row's
232
+ // hash. Returns the same { ok, table, rowsVerified, lastHash | breakAt... }
233
+ // shape verifyChain documents. Shared by the single-chain path and each
234
+ // per-key partition.
235
+ function _walkRows(rows, tableName, startPrevHash, opts) {
236
+ var prevHash = startPrevHash;
249
237
  if (rows.length === 0) {
250
238
  return { ok: true, table: tableName, rowsVerified: 0, lastHash: prevHash };
251
239
  }
@@ -290,7 +278,148 @@ async function verifyChain(queryAllAsync, tableName, opts) {
290
278
 
291
279
  if (opts.maxRows && i >= opts.maxRows - 1) break;
292
280
  }
293
- return { ok: true, table: tableName, rowsVerified: rows.length, lastHash: prevHash };
281
+ // Report the count ACTUALLY walked, not rows.length under maxRows the walk
282
+ // stops early, so rows.length would over-report coverage (a caller reading
283
+ // rowsVerified to judge how much of the chain was checked must see the real
284
+ // number, not be told the whole table verified when only maxRows did).
285
+ var verifiedCount = opts.maxRows ? Math.min(rows.length, opts.maxRows) : rows.length;
286
+ return { ok: true, table: tableName, rowsVerified: verifiedCount, lastHash: prevHash };
287
+ }
288
+
289
+ async function verifyChain(queryAllAsync, tableName, opts) {
290
+ opts = opts || {};
291
+
292
+ // Multi-chain table: verify each partition independently. Each key's
293
+ // sub-chain anchors at ZERO_HASH and is walked in monotonic-counter order;
294
+ // the first break in ANY key returns { ok:false, chainKey, ... }. maxRows is
295
+ // per-sub-chain; maxChains bounds the partition fan-out (fails closed when
296
+ // exceeded). The audit_log purge-anchor logic is single-chain-only, so it is
297
+ // skipped under a chainKey.
298
+ if (opts.chainKey) {
299
+ safeSql.validateIdentifier(opts.chainKey);
300
+ var keysBuilt = sql.select(tableName, _sqlOpts())
301
+ .distinct()
302
+ .columns([opts.chainKey])
303
+ .orderBy(opts.chainKey, "asc")
304
+ .toSql();
305
+ // coerce so a Postgres INTEGER/BIGINT chainKey is type-stable in the
306
+ // reported break-shape and the per-key WHERE bind, matching SQLite.
307
+ var keyRows = frameworkSchema.coerceRows(await queryAllAsync(keysBuilt.sql, keysBuilt.params));
308
+ var maxChains = (typeof opts.maxChains === "number" && opts.maxChains > 0) ? opts.maxChains : 100000; // allow:numeric-opt-Infinity — partition fan-out cap; non-number / <=0 falls back to the default
309
+ if (keyRows.length > maxChains) {
310
+ return {
311
+ ok: false,
312
+ table: tableName,
313
+ rowsVerified: 0,
314
+ reason: "too many chains: " + keyRows.length + " partitions exceeds maxChains " + maxChains,
315
+ };
316
+ }
317
+ var totalVerified = 0;
318
+ var lastHashByKey = {};
319
+ for (var ki = 0; ki < keyRows.length; ki++) {
320
+ var keyValue = keyRows[ki][opts.chainKey];
321
+ var rowsBuiltK = sql.select(tableName, _sqlOpts())
322
+ .where(opts.chainKey, keyValue)
323
+ .orderBy("monotonicCounter", "asc")
324
+ .toSql();
325
+ var rowsK = frameworkSchema.coerceRows(await queryAllAsync(rowsBuiltK.sql, rowsBuiltK.params));
326
+ var resK = _walkRows(rowsK, tableName, ZERO_HASH, opts);
327
+ if (!resK.ok) { resK.chainKey = keyValue; return resK; }
328
+ totalVerified += resK.rowsVerified;
329
+ lastHashByKey[String(keyValue)] = resK.lastHash;
330
+ }
331
+ return {
332
+ ok: true,
333
+ table: tableName,
334
+ rowsVerified: totalVerified,
335
+ chains: keyRows.length,
336
+ lastHashByKey: lastHashByKey,
337
+ };
338
+ }
339
+
340
+ var prevHash = ZERO_HASH;
341
+ var skipBeforeCounter = 0;
342
+ if (tableName === "audit_log") {
343
+ var anchor;
344
+ try {
345
+ // External-only table whose LOGICAL name IS the `_blamejs_`-prefixed
346
+ // name (self-mapped in LOCAL_TO_EXTERNAL), passed bare so the reader's
347
+ // clusterStorage rewrites it; the 'audit' scope binds as a ? param.
348
+ // allow:hand-rolled-sql — bare logical key.
349
+ var anchorBuilt = sql.select("_blamejs_audit_purge_anchor", _sqlOpts()) // allow:hand-rolled-sql
350
+ .columns(["lastPurgedCounter", "lastPurgedRowHash"])
351
+ .where("scope", "audit")
352
+ .toSql();
353
+ anchor = await queryAllAsync(anchorBuilt.sql, anchorBuilt.params);
354
+ } catch (_e) {
355
+ // Anchor table may not exist on a deployment that has never been
356
+ // through a purge. Treat as no anchor.
357
+ anchor = [];
358
+ }
359
+ if (Array.isArray(anchor) && anchor.length > 0) {
360
+ var aHash = anchor[0].lastPurgedRowHash;
361
+ var aCounter = Number(anchor[0].lastPurgedCounter);
362
+ // A corrupted / tampered purge anchor (non-hex lastPurgedRowHash or a
363
+ // non-numeric lastPurgedCounter) must fail CLOSED with a clear reason.
364
+ // Passing a garbage prevHash into _walkRows → computeRowHash would THROW
365
+ // ("prevHash must be a 128-char hex"), turning a defensive verify into an
366
+ // uncaught exception; a NaN counter would skip nothing and surface as an
367
+ // opaque chain-break. Detect it here and return { ok:false }.
368
+ if (!safeBuffer.isHex(aHash, SHA3_512_HEX_LEN) || !isFinite(aCounter) || aCounter < 0) {
369
+ return { ok: false, table: tableName, rowsVerified: 0, reason: "corrupted purge anchor" };
370
+ }
371
+ prevHash = aHash;
372
+ skipBeforeCounter = aCounter;
373
+ }
374
+ }
375
+
376
+ // Incremental verify (b.audit.verify { from, to }): verify only rows whose
377
+ // monotonicCounter is in [from, to]. `from` must anchor on the rowHash of the
378
+ // row immediately BEFORE it, so the scoped walk chains correctly — otherwise
379
+ // the first in-range row's prevHash (= the predecessor's rowHash) wouldn't
380
+ // match ZERO_HASH and a good chain would falsely report a break.
381
+ var fromCounter = (opts.from != null && isFinite(Number(opts.from))) ? Number(opts.from) : null;
382
+ var toCounter = (opts.to != null && isFinite(Number(opts.to))) ? Number(opts.to) : null;
383
+
384
+ var rowsBuilt = sql.select(tableName, _sqlOpts())
385
+ .orderBy("monotonicCounter", "asc")
386
+ .toSql();
387
+ var rows = await queryAllAsync(rowsBuilt.sql, rowsBuilt.params);
388
+ // Normalize driver shape before hashing: node-postgres returns BIGINT
389
+ // columns (recordedAt / monotonicCounter) as strings, which would hash
390
+ // differently from the numbers the chain-writer signed — the chain only
391
+ // verified on SQLite without this. coerceRow makes the recompute
392
+ // type-stable across backends (no-op on already-numeric SQLite rows).
393
+ rows = frameworkSchema.coerceRows(rows);
394
+
395
+ // Resolve the incremental-verify anchor: the highest row strictly below
396
+ // `from` (derived from the already-read rows, no extra query). Raise
397
+ // skipBeforeCounter to it and adopt its rowHash as the chain anchor.
398
+ if (fromCounter != null && fromCounter > skipBeforeCounter + 1) {
399
+ var pred = null;
400
+ for (var pi = 0; pi < rows.length; pi++) {
401
+ var pc = Number(rows[pi].monotonicCounter);
402
+ if (pc < fromCounter && pc > skipBeforeCounter) pred = rows[pi]; else if (pc >= fromCounter) break;
403
+ }
404
+ if (pred) {
405
+ if (!safeBuffer.isHex(pred.rowHash, SHA3_512_HEX_LEN)) {
406
+ return { ok: false, table: tableName, rowsVerified: 0, reason: "incremental-verify anchor row has a corrupt rowHash" };
407
+ }
408
+ prevHash = pred.rowHash;
409
+ skipBeforeCounter = Math.max(skipBeforeCounter, Number(pred.monotonicCounter));
410
+ }
411
+ }
412
+
413
+ if (skipBeforeCounter > 0 || toCounter != null) {
414
+ rows = rows.filter(function (r) {
415
+ var c = Number(r.monotonicCounter);
416
+ if (c <= skipBeforeCounter) return false;
417
+ if (toCounter != null && c > toCounter) return false;
418
+ return true;
419
+ });
420
+ }
421
+
422
+ return _walkRows(rows, tableName, prevHash, opts);
294
423
  }
295
424
 
296
425
  module.exports = {
@@ -83,9 +83,15 @@ function _defaultClassify(event) {
83
83
  }
84
84
 
85
85
  function _severityAtLeast(severity, threshold) {
86
- var sIdx = SEVERITY_ORDER.indexOf(severity);
87
86
  var tIdx = SEVERITY_ORDER.indexOf(threshold);
88
- if (sIdx === -1 || tIdx === -1) return false;
87
+ if (tIdx === -1) return false; // unknown threshold (validated at config)
88
+ var sIdx = SEVERITY_ORDER.indexOf(severity);
89
+ // An UNKNOWN event severity — e.g. a custom classify(event) returning an
90
+ // unexpected value — must NOT silently drop the event from the review. Fail
91
+ // SAFE: treat it as meeting the threshold so the operator still sees the
92
+ // event (and notices their classify mis-returned) rather than missing a
93
+ // flagged event.
94
+ if (sIdx === -1) return true;
89
95
  return sIdx >= tIdx;
90
96
  }
91
97
 
@@ -164,20 +170,30 @@ function create(opts) {
164
170
  throw _err("auditDailyReview/audit-query-missing",
165
171
  "auditDailyReview.create: opts.audit.query must be a function");
166
172
  }
167
- validateOpts.optionalFunction(opts.notify,
168
- "auditDailyReview: notify", AuditDailyReviewError, "auditDailyReview/bad-notify");
169
- validateOpts.optionalFunction(opts.classify,
170
- "auditDailyReview: classify", AuditDailyReviewError, "auditDailyReview/bad-classify");
171
- validateOpts.optionalFunction(opts.now,
172
- "auditDailyReview: now", AuditDailyReviewError, "auditDailyReview/bad-now");
173
- validateOpts.optionalNonEmptyString(opts.posture,
174
- "auditDailyReview: posture", AuditDailyReviewError, "auditDailyReview/bad-posture");
175
- validateOpts.optionalNonEmptyString(opts.cron,
176
- "auditDailyReview: cron", AuditDailyReviewError, "auditDailyReview/bad-cron");
177
- validateOpts.optionalPositiveInt(opts.queryLimit,
178
- "auditDailyReview: queryLimit", AuditDailyReviewError, "auditDailyReview/bad-querylimit");
179
- validateOpts.optionalPositiveInt(opts.historyLimit,
180
- "auditDailyReview: historyLimit", AuditDailyReviewError, "auditDailyReview/bad-historylimit");
173
+ validateOpts.shape(opts, {
174
+ audit: { methods: ["query", "safeEmit"] },
175
+ notify: { rule: "optional-function", code: "auditDailyReview/bad-notify" },
176
+ classify: { rule: "optional-function", code: "auditDailyReview/bad-classify" },
177
+ now: { rule: "optional-function", code: "auditDailyReview/bad-now" },
178
+ posture: { rule: "optional-string", code: "auditDailyReview/bad-posture" },
179
+ cron: { rule: "optional-string", code: "auditDailyReview/bad-cron" },
180
+ severityThreshold: { rule: "optional-string", code: "auditDailyReview/bad-severity" },
181
+ queryLimit: { rule: "optional-positive-int", code: "auditDailyReview/bad-querylimit" },
182
+ historyLimit: { rule: "optional-positive-int", code: "auditDailyReview/bad-historylimit" },
183
+ // lookbackHours — positive finite number (hours, not bytes). Bespoke
184
+ // check preserves the auditDailyReview/bad-lookback code the message
185
+ // below depends on; a positive-int token would reject fractional hours.
186
+ lookbackHours: function (value, label) {
187
+ if (value === undefined || value === null) return;
188
+ if (typeof value !== "number" || !isFinite(value) || value <= 0) {
189
+ throw _err("auditDailyReview/bad-lookback",
190
+ "auditDailyReview.create: lookbackHours must be a positive finite number");
191
+ }
192
+ },
193
+ }, "auditDailyReview", AuditDailyReviewError, "auditDailyReview/bad-opts",
194
+ // scheduler is forwarded to the b.scheduler instance at start() and not
195
+ // validated locally at create-time (start() requires it on its own).
196
+ { allow: ["scheduler"] });
181
197
 
182
198
  // lookbackHours — default 24 per PCI DSS 4.0 daily cadence. Caller can
183
199
  // pass weekly / monthly via larger numbers.
@@ -237,6 +253,10 @@ function create(opts) {
237
253
  from: fromMs,
238
254
  to: startedAt,
239
255
  limit: queryLimit,
256
+ // Newest-first: if the window holds more than queryLimit events, keep
257
+ // the MOST RECENT (the actionable ones) — an ascending+limit query would
258
+ // keep the oldest and silently drop the newest from the review.
259
+ order: "desc",
240
260
  });
241
261
  } catch (e) {
242
262
  _emit("audit.daily_review.failed", {
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+
3
+ // audit-emit — the stateless drop-silent audit emitter with a metadata-first,
4
+ // success-default signature `(action, metadata, outcome?)`. Many primitives —
5
+ // the mail servers and DAV bridge, the A2A task store, the compliance posture
6
+ // tracker, the MCP tool registry, the idempotency-key middleware — emit audit
7
+ // events where the per-event detail (metadata) is almost always supplied and
8
+ // the outcome is almost always "this happened", so they pass metadata second
9
+ // and let outcome default to "success". They route through the drop-silent
10
+ // audit.safeEmit so a misbehaving sink never crashes the request. Unlike
11
+ // b.audit.namespaced (gated, action-prefixed, outcome-first), this is ungated
12
+ // and passes the action verbatim — matching what each of them hand-rolled.
13
+ //
14
+ // This module self-lazy-requires audit so a consumer can `require("./audit-emit")`
15
+ // at the top of the file without re-introducing the audit load cycle that the
16
+ // per-file `lazyRequire(() => require("./audit"))` was guarding against.
17
+
18
+ var lazyRequire = require("./lazy-require");
19
+ var audit = lazyRequire(function () { return require("./audit"); });
20
+
21
+ // emit(action, metadata, outcome?) — drop-silent audit emit. `outcome` defaults
22
+ // to "success"; `metadata` defaults to `{}`.
23
+ function emit(action, metadata, outcome) {
24
+ try {
25
+ audit().safeEmit({
26
+ action: action,
27
+ outcome: outcome || "success",
28
+ metadata: metadata || {},
29
+ });
30
+ } catch (_e) { /* drop-silent — audit best-effort */ }
31
+ }
32
+
33
+ // emitToSink(opts, action, outcome, metadata) — drop-silent audit emit to an
34
+ // OPERATOR-SUPPLIED sink threaded through `opts.audit`, the no-op-when-absent
35
+ // variant the archive reader / tar-reader / writer share verbatim: forward the
36
+ // event only when `opts.audit` exposes a `safeEmit`, otherwise stay silent.
37
+ // Distinct from emit() (which targets the framework's GLOBAL audit()) — here the
38
+ // caller carries the sink in opts, so a reader/writer with no audit configured
39
+ // emits nothing. (Callers whose payload carries extra top-level fields — e.g.
40
+ // http-client's `resource` — keep their own wrapper; this is the bare
41
+ // action/outcome/metadata shape.)
42
+ function emitToSink(opts, action, outcome, metadata) {
43
+ if (!opts || !opts.audit || typeof opts.audit.safeEmit !== "function") return;
44
+ try {
45
+ opts.audit.safeEmit({ action: action, outcome: outcome, metadata: metadata });
46
+ } catch (_e) { /* drop-silent — operator audit sink must never crash the caller */ }
47
+ }
48
+
49
+ // gatedReasonEmitter({ audit, sink?, extra? }) — build a gated drop-silent
50
+ // emitter `(action, info, outcome)` that records a structured top-level `reason`
51
+ // (hoisted from `info.reason`) alongside the audit metadata. The gated +
52
+ // reason-hoisting sibling of emit() / b.audit.namespaced, for the primitives
53
+ // (backup / restore / scheduler / config-drift / legal-hold) that emit a reason
54
+ // the audit chain surfaces as a first-class field. `audit` is the gate flag
55
+ // (false disables); `sink` is an optional operator-supplied audit target (the
56
+ // no-op-without-safeEmit guard the hand-rolled `auditInstance || audit()`
57
+ // pattern carried); `extra(info)` optionally returns more top-level event
58
+ // fields (e.g. legal-hold's `resource`). Routes through b.audit.namespaced, so
59
+ // the same redaction + drop-silent guarantees apply.
60
+ function gatedReasonEmitter(opts) {
61
+ opts = opts || {};
62
+ var ns = audit().namespaced(null, { audit: opts.audit, sink: opts.sink });
63
+ var extra = typeof opts.extra === "function" ? opts.extra : null;
64
+ return function (action, info, outcome) {
65
+ var fields = { reason: (info && info.reason) || null };
66
+ if (extra) {
67
+ var more = extra(info);
68
+ if (more) {
69
+ for (var k in more) {
70
+ if (Object.prototype.hasOwnProperty.call(more, k)) fields[k] = more[k];
71
+ }
72
+ }
73
+ }
74
+ ns(action, outcome, info, fields);
75
+ };
76
+ }
77
+
78
+ module.exports = {
79
+ emit: emit,
80
+ emitToSink: emitToSink,
81
+ gatedReasonEmitter: gatedReasonEmitter,
82
+ };