@blamejs/core 0.8.42 → 0.8.49

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 (222) hide show
  1. package/CHANGELOG.md +93 -0
  2. package/README.md +10 -10
  3. package/index.js +52 -0
  4. package/lib/a2a.js +159 -34
  5. package/lib/acme.js +762 -0
  6. package/lib/ai-pref.js +166 -43
  7. package/lib/api-key.js +108 -47
  8. package/lib/api-snapshot.js +157 -40
  9. package/lib/app-shutdown.js +113 -77
  10. package/lib/archive.js +337 -40
  11. package/lib/arg-parser.js +697 -0
  12. package/lib/asyncapi.js +99 -55
  13. package/lib/atomic-file.js +465 -104
  14. package/lib/audit-chain.js +123 -34
  15. package/lib/audit-daily-review.js +389 -0
  16. package/lib/audit-sign.js +302 -56
  17. package/lib/audit-tools.js +412 -63
  18. package/lib/audit.js +656 -35
  19. package/lib/auth/jwt-external.js +17 -0
  20. package/lib/auth/oauth.js +7 -0
  21. package/lib/auth-bot-challenge.js +505 -0
  22. package/lib/auth-header.js +92 -25
  23. package/lib/backup/bundle.js +26 -0
  24. package/lib/backup/index.js +512 -89
  25. package/lib/backup/manifest.js +168 -7
  26. package/lib/break-glass.js +415 -39
  27. package/lib/budr.js +103 -30
  28. package/lib/bundler.js +86 -66
  29. package/lib/cache.js +192 -72
  30. package/lib/chain-writer.js +65 -40
  31. package/lib/circuit-breaker.js +56 -33
  32. package/lib/cli-helpers.js +106 -75
  33. package/lib/cli.js +6 -30
  34. package/lib/cloud-events.js +99 -32
  35. package/lib/cluster-storage.js +162 -37
  36. package/lib/cluster.js +340 -49
  37. package/lib/codepoint-class.js +66 -0
  38. package/lib/compliance.js +424 -24
  39. package/lib/config-drift.js +111 -46
  40. package/lib/config.js +94 -40
  41. package/lib/consent.js +165 -18
  42. package/lib/constants.js +1 -0
  43. package/lib/content-credentials.js +153 -48
  44. package/lib/cookies.js +154 -62
  45. package/lib/credential-hash.js +133 -61
  46. package/lib/crypto-field.js +702 -18
  47. package/lib/crypto-hpke.js +256 -0
  48. package/lib/crypto.js +744 -22
  49. package/lib/csv.js +178 -35
  50. package/lib/daemon.js +456 -0
  51. package/lib/dark-patterns.js +186 -55
  52. package/lib/db-query.js +79 -2
  53. package/lib/db.js +1431 -60
  54. package/lib/ddl-change-control.js +523 -0
  55. package/lib/deprecate.js +195 -40
  56. package/lib/dev.js +82 -39
  57. package/lib/dora.js +67 -48
  58. package/lib/dr-runbook.js +368 -0
  59. package/lib/dsr.js +142 -11
  60. package/lib/dual-control.js +91 -56
  61. package/lib/events.js +120 -41
  62. package/lib/external-db-migrate.js +192 -2
  63. package/lib/external-db.js +795 -50
  64. package/lib/fapi2.js +122 -1
  65. package/lib/fda-21cfr11.js +395 -0
  66. package/lib/fdx.js +132 -2
  67. package/lib/file-type.js +87 -0
  68. package/lib/file-upload.js +93 -0
  69. package/lib/flag.js +82 -20
  70. package/lib/forms.js +132 -29
  71. package/lib/framework-error.js +169 -0
  72. package/lib/framework-schema.js +163 -35
  73. package/lib/gate-contract.js +849 -175
  74. package/lib/graphql-federation.js +68 -7
  75. package/lib/guard-all.js +172 -55
  76. package/lib/guard-archive.js +286 -124
  77. package/lib/guard-auth.js +194 -21
  78. package/lib/guard-cidr.js +190 -28
  79. package/lib/guard-csv.js +397 -51
  80. package/lib/guard-domain.js +213 -91
  81. package/lib/guard-email.js +236 -29
  82. package/lib/guard-filename.js +307 -75
  83. package/lib/guard-graphql.js +263 -30
  84. package/lib/guard-html.js +310 -116
  85. package/lib/guard-image.js +243 -30
  86. package/lib/guard-json.js +260 -54
  87. package/lib/guard-jsonpath.js +235 -23
  88. package/lib/guard-jwt.js +284 -30
  89. package/lib/guard-markdown.js +204 -22
  90. package/lib/guard-mime.js +190 -26
  91. package/lib/guard-oauth.js +277 -28
  92. package/lib/guard-pdf.js +251 -27
  93. package/lib/guard-regex.js +226 -18
  94. package/lib/guard-shell.js +229 -26
  95. package/lib/guard-svg.js +177 -10
  96. package/lib/guard-template.js +232 -21
  97. package/lib/guard-time.js +195 -29
  98. package/lib/guard-uuid.js +189 -30
  99. package/lib/guard-xml.js +259 -36
  100. package/lib/guard-yaml.js +241 -44
  101. package/lib/honeytoken.js +63 -27
  102. package/lib/html-balance.js +83 -0
  103. package/lib/http-client.js +486 -59
  104. package/lib/http-message-signature.js +582 -0
  105. package/lib/i18n.js +102 -49
  106. package/lib/iab-mspa.js +112 -32
  107. package/lib/iab-tcf.js +107 -2
  108. package/lib/inbox.js +90 -52
  109. package/lib/keychain.js +865 -0
  110. package/lib/legal-hold.js +374 -0
  111. package/lib/local-db-thin.js +320 -0
  112. package/lib/log-stream.js +281 -51
  113. package/lib/log.js +184 -86
  114. package/lib/mail-bounce.js +107 -62
  115. package/lib/mail.js +295 -58
  116. package/lib/mcp.js +108 -27
  117. package/lib/metrics.js +98 -89
  118. package/lib/middleware/age-gate.js +36 -0
  119. package/lib/middleware/ai-act-disclosure.js +37 -0
  120. package/lib/middleware/api-encrypt.js +45 -0
  121. package/lib/middleware/assetlinks.js +40 -0
  122. package/lib/middleware/asyncapi-serve.js +35 -0
  123. package/lib/middleware/attach-user.js +40 -0
  124. package/lib/middleware/bearer-auth.js +40 -0
  125. package/lib/middleware/body-parser.js +230 -0
  126. package/lib/middleware/bot-disclose.js +34 -0
  127. package/lib/middleware/bot-guard.js +39 -0
  128. package/lib/middleware/compression.js +37 -0
  129. package/lib/middleware/cookies.js +32 -0
  130. package/lib/middleware/cors.js +40 -0
  131. package/lib/middleware/csp-nonce.js +40 -0
  132. package/lib/middleware/csp-report.js +34 -0
  133. package/lib/middleware/csrf-protect.js +43 -0
  134. package/lib/middleware/daily-byte-quota.js +53 -85
  135. package/lib/middleware/db-role-for.js +40 -0
  136. package/lib/middleware/dpop.js +40 -0
  137. package/lib/middleware/error-handler.js +37 -14
  138. package/lib/middleware/fetch-metadata.js +39 -0
  139. package/lib/middleware/flag-context.js +34 -0
  140. package/lib/middleware/gpc.js +33 -0
  141. package/lib/middleware/headers.js +35 -0
  142. package/lib/middleware/health.js +46 -0
  143. package/lib/middleware/host-allowlist.js +30 -0
  144. package/lib/middleware/network-allowlist.js +38 -0
  145. package/lib/middleware/openapi-serve.js +34 -0
  146. package/lib/middleware/rate-limit.js +160 -18
  147. package/lib/middleware/request-id.js +36 -18
  148. package/lib/middleware/request-log.js +37 -0
  149. package/lib/middleware/require-aal.js +29 -0
  150. package/lib/middleware/require-auth.js +32 -0
  151. package/lib/middleware/require-bound-key.js +41 -0
  152. package/lib/middleware/require-content-type.js +32 -0
  153. package/lib/middleware/require-methods.js +27 -0
  154. package/lib/middleware/require-mtls.js +33 -0
  155. package/lib/middleware/require-step-up.js +37 -0
  156. package/lib/middleware/security-headers.js +44 -0
  157. package/lib/middleware/security-txt.js +38 -0
  158. package/lib/middleware/span-http-server.js +37 -0
  159. package/lib/middleware/sse.js +36 -0
  160. package/lib/middleware/trace-log-correlation.js +33 -0
  161. package/lib/middleware/trace-propagate.js +32 -0
  162. package/lib/middleware/tus-upload.js +90 -0
  163. package/lib/middleware/web-app-manifest.js +53 -0
  164. package/lib/mtls-ca.js +100 -70
  165. package/lib/network-byte-quota.js +308 -0
  166. package/lib/network-heartbeat.js +135 -0
  167. package/lib/network-tls.js +534 -4
  168. package/lib/network.js +103 -0
  169. package/lib/notify.js +114 -43
  170. package/lib/ntp-check.js +192 -51
  171. package/lib/observability.js +145 -47
  172. package/lib/openapi.js +90 -44
  173. package/lib/outbox.js +99 -1
  174. package/lib/pagination.js +168 -86
  175. package/lib/parsers/index.js +16 -5
  176. package/lib/permissions.js +93 -40
  177. package/lib/pqc-agent.js +84 -8
  178. package/lib/pqc-software.js +94 -60
  179. package/lib/process-spawn.js +95 -21
  180. package/lib/pubsub.js +96 -66
  181. package/lib/queue.js +375 -54
  182. package/lib/redact.js +793 -21
  183. package/lib/render.js +139 -47
  184. package/lib/request-helpers.js +485 -121
  185. package/lib/restore-bundle.js +142 -39
  186. package/lib/restore-rollback.js +136 -45
  187. package/lib/retention.js +178 -50
  188. package/lib/retry.js +116 -33
  189. package/lib/router.js +475 -23
  190. package/lib/safe-async.js +543 -94
  191. package/lib/safe-buffer.js +337 -41
  192. package/lib/safe-json.js +467 -62
  193. package/lib/safe-jsonpath.js +285 -0
  194. package/lib/safe-schema.js +631 -87
  195. package/lib/safe-sql.js +221 -59
  196. package/lib/safe-url.js +278 -46
  197. package/lib/sandbox-worker.js +135 -0
  198. package/lib/sandbox.js +358 -0
  199. package/lib/scheduler.js +135 -70
  200. package/lib/self-update.js +647 -0
  201. package/lib/session-device-binding.js +431 -0
  202. package/lib/session.js +259 -49
  203. package/lib/slug.js +138 -26
  204. package/lib/ssrf-guard.js +316 -56
  205. package/lib/storage.js +433 -70
  206. package/lib/subject.js +405 -23
  207. package/lib/template.js +148 -8
  208. package/lib/tenant-quota.js +545 -0
  209. package/lib/testing.js +440 -53
  210. package/lib/time.js +291 -23
  211. package/lib/tls-exporter.js +239 -0
  212. package/lib/tracing.js +90 -74
  213. package/lib/uuid.js +97 -22
  214. package/lib/vault/index.js +284 -22
  215. package/lib/vault/seal-pem-file.js +66 -0
  216. package/lib/watcher.js +368 -0
  217. package/lib/webhook.js +196 -63
  218. package/lib/websocket.js +393 -68
  219. package/lib/wiki-concepts.js +338 -0
  220. package/lib/worker-pool.js +464 -0
  221. package/package.json +3 -3
  222. package/sbom.cyclonedx.json +7 -7
@@ -1,54 +1,57 @@
1
1
  "use strict";
2
2
  /**
3
- * audit-tools — operator tooling on top of the audit chain.
4
- *
5
- * Four operations the compliance matrix calls for:
6
- *
7
- * archive(opts) Bundle audit rows older than `before` into a
8
- * PQC-encrypted archive with chain proof + a
9
- * covering signed checkpoint. Live rows untouched.
10
- * exportSlice(opts) Auditor-shaped slice (date range / subject /
11
- * action filter) with chain proof. Live rows
12
- * untouched. Purpose: deliver an evidence bundle
13
- * to an external auditor without surrendering the
14
- * whole log.
15
- * verifyBundle(opts) Round-trip integrity: decrypt the bundle, walk
16
- * the chain math across the contained rows,
17
- * verify the covering checkpoint signature
18
- * (archive bundles only).
19
- * purge(opts) Confirmation-gated deletion of live audit rows
20
- * already captured in a verified archive bundle.
21
- * Inserts a purge-anchor so live audit.verify()
22
- * keeps working post-purge the anchor's
23
- * lastRowHash becomes the new chain origin.
24
- *
25
- * Bundle layout (POSIX-flat directory; matches the backup-bundle shape
26
- * so operators see one mental model for "encrypted blamejs bundle"):
27
- *
28
- * <out>/manifest.json Canonical-JSON manifest. Includes format,
29
- * kind, range (firstCounter/lastCounter/
30
- * firstRecordedAt/lastRecordedAt/
31
- * firstRowHash/lastRowHash), rowCount, the
32
- * per-blob salts, the framework version, and
33
- * (archive only) a copy of the covering
34
- * checkpoint row plus its public-key
35
- * fingerprint.
36
- * <out>/rows.enc PQC-encrypted JSONL — one row per line,
37
- * monotonic-counter ASC, sealed form (rowHash
38
- * stays computable from disk bytes). Each
39
- * field in a sealed column is the on-disk
40
- * ciphertext, not the plaintext, so the chain
41
- * recomputes byte-for-byte.
42
- * <out>/checkpoint.enc Archive only. PQC-encrypted JSON of the
43
- * covering audit_checkpoints row.
44
- *
45
- * `kind="archive"` bundles always include a covering checkpoint
46
- * (atMonotonicCounter >= lastCounter) so the anchor signature
47
- * tamper-evidences the whole archive. `kind="export"` bundles are
48
- * auditor evidence; the chain math is self-contained but the
49
- * upstream signature anchor is optional (auditors typically follow
50
- * up with an `audit.verify` call against the live system to confirm
51
- * the slice still chains).
3
+ * @module b.auditTools
4
+ * @nav Observability
5
+ * @title Audit Tools
6
+ *
7
+ * @intro
8
+ * Operator-side audit-chain inspection / export verify chain
9
+ * integrity end-to-end, export RFC 8785 canonical-JSON slices,
10
+ * format rows for downstream SIEM (CADF / ISO 19395), and generate
11
+ * tamper-evident compliance-evidence bundles auditors can verify
12
+ * off-line.
13
+ *
14
+ * Four core operations on top of the live `audit_log` chain:
15
+ *
16
+ * archive(opts) Bundle rows older than `before` into a
17
+ * PQC-encrypted archive with chain proof + a
18
+ * covering signed checkpoint. Live rows are
19
+ * untouched until a separate `purge()` call.
20
+ * exportSlice(opts) Auditor-shaped slice (date range / action
21
+ * filter) with chain proof deliver evidence
22
+ * to an external auditor without surrendering
23
+ * the whole log.
24
+ * verifyBundle(opts) Round-trip integrity: decrypt the bundle,
25
+ * walk chain math across the contained rows,
26
+ * verify the covering checkpoint's ML-DSA
27
+ * signature (archive bundles only).
28
+ * purge(opts) Confirmation-gated deletion of live rows
29
+ * already captured in a verified archive
30
+ * bundle. Inserts a purge-anchor so
31
+ * `b.audit.verify()` keeps working post-purge.
32
+ *
33
+ * Bundle layout (POSIX-flat directory; matches the backup-bundle
34
+ * shape so operators see one mental model for "encrypted blamejs
35
+ * bundle"):
36
+ *
37
+ * <out>/manifest.json Canonical-JSON manifest (format / kind /
38
+ * range / rowCount / per-blob salts /
39
+ * framework version; archive bundles also
40
+ * carry the covering checkpoint summary).
41
+ * <out>/rows.enc PQC-encrypted JSONL of audit rows in
42
+ * sealed form so rowHash stays computable
43
+ * from disk bytes byte-for-byte.
44
+ * <out>/checkpoint.enc Archive-only. PQC-encrypted JSON of the
45
+ * covering audit_checkpoints row.
46
+ *
47
+ * `kind="archive"` bundles always include a covering checkpoint
48
+ * (atMonotonicCounter >= lastCounter) so the off-chain signature
49
+ * tamper-evidences the whole archive. `kind="export"` bundles are
50
+ * auditor evidence; the chain math is self-contained, with the
51
+ * upstream signature anchor optional.
52
+ *
53
+ * @card
54
+ * Operator-side audit-chain inspection / export — verify chain integrity end-to-end, export RFC 8785 canonical-JSON slices, format rows for downstream SIEM (CADF / ISO 19395), and generate tamper-evident compliance-evidence bundles auditors can verify off-line.
52
55
  */
53
56
 
54
57
  var fs = require("fs");
@@ -153,6 +156,28 @@ function _rowToWireForm(row) {
153
156
  // unchanged AND _rowToWireForm (which the chain-hash canonicalizes
154
157
  // over) doesn't change its bytes — so chain verify continues to
155
158
  // match. Operators call this on retrieved rows for export.
159
+ /**
160
+ * @primitive b.auditTools.withRecordedAtIso
161
+ * @signature b.auditTools.withRecordedAtIso(row)
162
+ * @since 0.7.30
163
+ * @related b.auditTools.exportSlice, b.auditTools.exportCadf
164
+ *
165
+ * Surface `recordedAt` as ISO-8601 / RFC 3339 (with explicit `Z`)
166
+ * alongside the framework's primary Unix-ms integer. Auditors
167
+ * comparing rows against external SIEM events expect ISO; the chain
168
+ * hash is unaffected because the canonical wire form used for
169
+ * hashing doesn't include the derived `recordedAtIso` field.
170
+ *
171
+ * Returns a shallow copy with `recordedAtIso` added when
172
+ * `recordedAt` is a finite number / bigint; otherwise returns the
173
+ * input unchanged.
174
+ *
175
+ * @example
176
+ * var row = { _id: "evt-1", recordedAt: 1762560000000, action: "auth.login" };
177
+ * var formatted = b.auditTools.withRecordedAtIso(row);
178
+ * // → { _id: "evt-1", recordedAt: 1762560000000,
179
+ * // recordedAtIso: "2025-11-08T00:00:00.000Z", action: "auth.login" }
180
+ */
156
181
  function withRecordedAtIso(row) {
157
182
  if (!row) return row;
158
183
  var out = Object.assign({}, row);
@@ -396,6 +421,37 @@ async function _readBundle(inDir, passphrase) {
396
421
 
397
422
  // ---- Public ops ----
398
423
 
424
+ /**
425
+ * @primitive b.auditTools.archive
426
+ * @signature b.auditTools.archive(opts)
427
+ * @since 0.7.30
428
+ * @compliance hipaa, pci-dss, gdpr, soc2, sox-404
429
+ * @related b.auditTools.verifyBundle, b.auditTools.purge, b.audit.checkpoint
430
+ *
431
+ * Bundle every audit row older than `opts.before` into a
432
+ * PQC-encrypted archive (XChaCha20-Poly1305 + Argon2id-derived key)
433
+ * containing a chain proof and the covering ML-DSA-87 checkpoint.
434
+ * Live rows are untouched — call `b.auditTools.purge` separately
435
+ * once the archive is verified.
436
+ *
437
+ * Refuses if `opts.out` exists, no rows match, or no signed
438
+ * checkpoint covers the slice (run `b.audit.checkpoint()` first).
439
+ *
440
+ * @opts
441
+ * out: string, // fresh directory path for the bundle
442
+ * before: number|Date|string, // archive rows recordedAt < this
443
+ * passphrase: Buffer|string, // bundle-encryption passphrase
444
+ *
445
+ * @example
446
+ * var ninetyDaysAgo = Date.now() - 90 * 24 * 60 * 60 * 1000;
447
+ * var result = await b.auditTools.archive({
448
+ * out: "/var/audit/2026-Q1.bundle",
449
+ * before: ninetyDaysAgo,
450
+ * passphrase: process.env.AUDIT_BUNDLE_PASSPHRASE,
451
+ * });
452
+ * // → { rowCount: 14823, range: { firstCounter: 1, lastCounter: 14823, ... },
453
+ * // manifestPath: "/var/audit/2026-Q1.bundle/manifest.json", ... }
454
+ */
399
455
  async function archive(opts) {
400
456
  opts = opts || {};
401
457
  _requirePassphrase(opts.passphrase);
@@ -444,6 +500,39 @@ async function archive(opts) {
444
500
  };
445
501
  }
446
502
 
503
+ /**
504
+ * @primitive b.auditTools.exportSlice
505
+ * @signature b.auditTools.exportSlice(opts)
506
+ * @since 0.7.30
507
+ * @compliance hipaa, pci-dss, gdpr, soc2
508
+ * @related b.auditTools.archive, b.auditTools.verifyBundle, b.auditTools.exportCadf
509
+ *
510
+ * Auditor-shaped slice — bundle the audit rows in `[from, to]`
511
+ * (optionally filtered by exact `action`) into a PQC-encrypted
512
+ * directory carrying chain-proof material. Refuses non-contiguous
513
+ * slices because chain verification cannot ground a sequence with
514
+ * gaps in `monotonicCounter`.
515
+ *
516
+ * Use date-range filters that cover every row in the range; an
517
+ * action filter that drops intermediate counters is rejected with
518
+ * `audit-tools/non-contiguous`.
519
+ *
520
+ * @opts
521
+ * out: string, // fresh directory path
522
+ * from: number|Date|string, // recordedAt >= this (inclusive)
523
+ * to: number|Date|string, // recordedAt <= this (inclusive)
524
+ * action: string, // exact action match (optional)
525
+ * passphrase: Buffer|string, // bundle-encryption passphrase
526
+ *
527
+ * @example
528
+ * var bundle = await b.auditTools.exportSlice({
529
+ * out: "/tmp/audit-2026-q1.bundle",
530
+ * from: "2026-01-01T00:00:00Z",
531
+ * to: "2026-03-31T23:59:59Z",
532
+ * passphrase: process.env.AUDIT_BUNDLE_PASSPHRASE,
533
+ * });
534
+ * // → { rowCount: 4218, manifest: { kind: "export", ... }, ... }
535
+ */
447
536
  async function exportSlice(opts) {
448
537
  opts = opts || {};
449
538
  _requirePassphrase(opts.passphrase);
@@ -496,6 +585,42 @@ async function exportSlice(opts) {
496
585
  };
497
586
  }
498
587
 
588
+ /**
589
+ * @primitive b.auditTools.verifyBundle
590
+ * @signature b.auditTools.verifyBundle(opts)
591
+ * @since 0.7.30
592
+ * @compliance hipaa, pci-dss, gdpr, soc2, sox-404
593
+ * @related b.auditTools.archive, b.auditTools.exportSlice, b.auditTools.purge
594
+ *
595
+ * Round-trip integrity check on a bundle directory: decrypt
596
+ * `rows.enc`, walk the prevHash → rowHash chain across the contained
597
+ * rows starting from the manifest's `predecessorRowHash` witness,
598
+ * confirm `firstRowHash` / `lastRowHash` match, and (archive only)
599
+ * verify the covering checkpoint's ML-DSA-87 signature against the
600
+ * locally-loaded audit-sign public key (or `opts.verifySignature`
601
+ * for cross-machine auditors).
602
+ *
603
+ * Returns `{ ok: true, kind, rowsVerified, range, manifest }` on
604
+ * success or `{ ok: false, reason, breakAt? }` at the first break.
605
+ *
606
+ * @opts
607
+ * in: string, // bundle directory
608
+ * passphrase: Buffer|string, // decryption passphrase
609
+ * verifyCheckpointSignature: boolean, // default true
610
+ * verifySignature: function(checkpoint), // override the default verifier
611
+ * includeRows: boolean, // attach decrypted rows to result
612
+ *
613
+ * @example
614
+ * var result = await b.auditTools.verifyBundle({
615
+ * in: "/var/audit/2026-Q1.bundle",
616
+ * passphrase: process.env.AUDIT_BUNDLE_PASSPHRASE,
617
+ * });
618
+ * if (!result.ok) {
619
+ * console.error("bundle integrity break:", result.reason);
620
+ * process.exit(1);
621
+ * }
622
+ * // → { ok: true, kind: "archive", rowsVerified: 14823, range: { ... } }
623
+ */
499
624
  async function verifyBundle(opts) {
500
625
  opts = opts || {};
501
626
  _requirePassphrase(opts.passphrase);
@@ -590,6 +715,35 @@ function _defaultVerifyCheckpointSignature(checkpoint) {
590
715
  } catch (_e) { return false; }
591
716
  }
592
717
 
718
+ /**
719
+ * @primitive b.auditTools.purge
720
+ * @signature b.auditTools.purge(opts)
721
+ * @since 0.7.30
722
+ * @compliance hipaa, pci-dss, gdpr, soc2, sox-404
723
+ * @related b.auditTools.archive, b.auditTools.verifyBundle, b.audit.verify
724
+ *
725
+ * Confirmation-gated deletion of live audit rows already captured in
726
+ * a verified archive bundle. Refuses unless `opts.confirm === true`,
727
+ * the bundle verifies clean as `kind="archive"`, and the bundle's
728
+ * `firstCounter` / `predecessorRowHash` match the next contiguous
729
+ * purge point on disk. Inserts a `_blamejs_audit_purge_anchor` row
730
+ * so `b.audit.verify()` keeps chaining post-purge — the anchor's
731
+ * `lastPurgedRowHash` becomes the new chain origin.
732
+ *
733
+ * @opts
734
+ * confirm: true, // exact `true` required
735
+ * archive: string, // path to a verified archive bundle
736
+ * passphrase: Buffer|string, // bundle decryption passphrase
737
+ * verifySignature: function(checkpoint),// auditor pubkey override
738
+ *
739
+ * @example
740
+ * var result = await b.auditTools.purge({
741
+ * confirm: true,
742
+ * archive: "/var/audit/2026-Q1.bundle",
743
+ * passphrase: process.env.AUDIT_BUNDLE_PASSPHRASE,
744
+ * });
745
+ * // → { purged: true, rowsDeleted: 14823, lastPurgedCounter: 14823, ... }
746
+ */
593
747
  async function purge(opts) {
594
748
  opts = opts || {};
595
749
  if (opts.confirm !== true) {
@@ -683,20 +837,40 @@ async function _defaultApplyPurge(args) {
683
837
  };
684
838
  }
685
839
 
686
- // forensicSnapshot — post-compromise composer that bundles an audit
687
- // archive slice, current break-glass grants, the active incident
688
- // report (if any), and process-runtime metadata into a single signed
689
- // bundle. The operator passes this to legal / regulators / the IR
690
- // team as one tamper-evident artifact.
691
- //
692
- // var snap = await b.auditTools.forensicSnapshot({
693
- // out: "/forensics/2026-05-07-incident-42",
694
- // since: Date.now() - C.TIME.days(7),
695
- // passphrase: process.env.AUDIT_BUNDLE_PASSPHRASE,
696
- // incidentId: "inc-2026-05-07-42",
697
- // reason: "ATO investigation: 14 failed MFA from new geo, user u_42",
698
- // actor: { id: "alice@ops.example.com", role: "incident-commander" },
699
- // });
840
+ /**
841
+ * @primitive b.auditTools.forensicSnapshot
842
+ * @signature b.auditTools.forensicSnapshot(opts)
843
+ * @since 0.8.40
844
+ * @compliance hipaa, pci-dss, gdpr, soc2, sox-404, dora, nis2
845
+ * @related b.auditTools.exportSlice, b.auditTools.archive
846
+ *
847
+ * Post-compromise composer that bundles an audit slice (from
848
+ * `since` → now) plus operator-supplied incident metadata
849
+ * (incidentId, reason, actor) and runtime fingerprint (Node version
850
+ * / platform / pid / uptime) into a single tamper-evident artifact
851
+ * for legal / regulators / the IR team. Emits an
852
+ * `audit.forensic_snapshot.composed` audit event so the act of
853
+ * composing the snapshot is itself on-chain.
854
+ *
855
+ * @opts
856
+ * out: string, // fresh directory path
857
+ * since: number|Date|string, // include rows recordedAt >= this
858
+ * passphrase: Buffer|string, // bundle-encryption passphrase
859
+ * reason: string, // required incident-context reason
860
+ * incidentId: string, // optional ticket / incident id
861
+ * actor: { id, role }, // optional incident-commander identity
862
+ *
863
+ * @example
864
+ * var snap = await b.auditTools.forensicSnapshot({
865
+ * out: "/forensics/2026-05-08-inc-42",
866
+ * since: Date.now() - 7 * 24 * 60 * 60 * 1000,
867
+ * passphrase: process.env.AUDIT_BUNDLE_PASSPHRASE,
868
+ * incidentId: "inc-2026-05-08-42",
869
+ * reason: "ATO investigation: 14 failed MFA from new geo, user u-42",
870
+ * actor: { id: "alice@ops.example.com", role: "incident-commander" },
871
+ * });
872
+ * // → { snapshotKind: "forensic", incidentId: "inc-2026-05-08-42", ... }
873
+ */
700
874
  async function forensicSnapshot(opts) {
701
875
  opts = opts || {};
702
876
  _requirePassphrase(opts.passphrase);
@@ -750,9 +924,184 @@ async function forensicSnapshot(opts) {
750
924
  return Object.assign({}, manifest, { manifestPath: manifestPath });
751
925
  }
752
926
 
927
+ // CADF (Cloud Auditing Data Federation, ISO/IEC 19395:2017) is the
928
+ // OpenStack/FedRAMP-tier cloud-audit envelope auditors increasingly
929
+ // expect for federated tooling (cross-tenant SIEM, CSP reporting).
930
+ //
931
+ // We map blamejs audit fields onto CADF attributes:
932
+ //
933
+ // blamejs CADF
934
+ // ---------------------- ----------------------------------
935
+ // _id eventid (UUID-ish)
936
+ // action action (typed verb namespace)
937
+ // outcome outcome (success | failure | unknown | pending)
938
+ // actorUserId initiator.id (typed via initiator.typeURI)
939
+ // resourceKind+resourceId target.id + target.typeURI
940
+ // recordedAt eventTime (ISO-8601)
941
+ // reason reason.reasonCode + reason.policyType
942
+ // metadata attachments[] (operator-supplied free-form)
943
+ // prevHash/rowHash observer.id link to chain anchor
944
+ //
945
+ // CADF requires every event to declare its observer (the auditing
946
+ // system). We declare blamejs as the observer with a typeURI of
947
+ // service/audit. The framework version pins observer.id so an auditor
948
+ // can correlate envelope-level events back to a deployment.
949
+ function _toCadfOutcome(outcome) {
950
+ if (outcome === "success") return "success";
951
+ if (outcome === "failure" || outcome === "denied") return "failure";
952
+ if (outcome === "warning") return "unknown";
953
+ return outcome || "unknown";
954
+ }
955
+
956
+ function _toCadfEvent(row) {
957
+ var meta = null;
958
+ if (row.metadata) {
959
+ try { meta = typeof row.metadata === "string" ? jsonSafe.parse(row.metadata) : row.metadata; }
960
+ catch (_e) { meta = { raw: String(row.metadata) }; }
961
+ }
962
+ var ev = {
963
+ typeURI: "http://schemas.dmtf.org/cloud/audit/1.0/event",
964
+ eventType: "activity",
965
+ id: row._id,
966
+ eventTime: new Date(Number(row.recordedAt)).toISOString(),
967
+ action: row.action,
968
+ outcome: _toCadfOutcome(row.outcome),
969
+ initiator: {
970
+ id: row.actorUserIdHash || row.actorUserId || "unknown",
971
+ typeURI: "service/security/account/user",
972
+ addresses: row.actorIp ? [{ url: row.actorIp, name: "actorIp" }] : undefined,
973
+ name: row.actorSessionId || undefined,
974
+ },
975
+ target: {
976
+ id: row.resourceIdHash || row.resourceId || row.resourceKind || "n/a",
977
+ typeURI: row.resourceKind ? ("service/storage/" + row.resourceKind) : "service/security",
978
+ },
979
+ observer: {
980
+ id: "blamejs:" + (pkg.version || "unknown"),
981
+ typeURI: "service/security/audit",
982
+ name: "blamejs.audit",
983
+ },
984
+ reason: row.reason ? {
985
+ reasonCode: String(row.reason).slice(0, 256), // allow:raw-byte-literal — reason cap
986
+ policyType: "blamejs.audit-chain",
987
+ } : undefined,
988
+ attachments: meta ? [{
989
+ contentType: "application/json",
990
+ content: JSON.stringify(meta),
991
+ name: "blamejs.metadata",
992
+ }] : undefined,
993
+ // Custom CADF extension — anchors back into the audit chain.
994
+ "blamejs:chain": {
995
+ monotonicCounter: Number(row.monotonicCounter),
996
+ prevHash: row.prevHash,
997
+ rowHash: row.rowHash,
998
+ },
999
+ };
1000
+ return ev;
1001
+ }
1002
+
1003
+ /**
1004
+ * @primitive b.auditTools.exportCadf
1005
+ * @signature b.auditTools.exportCadf(opts)
1006
+ * @since 0.7.30
1007
+ * @compliance soc2, pci-dss, gdpr
1008
+ * @related b.auditTools.exportAudit, b.auditTools.exportSlice
1009
+ *
1010
+ * Format an audit slice as a CADF event-batch (Cloud Auditing Data
1011
+ * Federation, ISO/IEC 19395:2017 + DMTF) — the FedRAMP / OpenStack
1012
+ * envelope cross-tenant SIEMs and CSP reporting tools expect for
1013
+ * federated tooling. Maps blamejs fields onto CADF attributes
1014
+ * (initiator / target / observer / outcome / reason) and embeds a
1015
+ * `blamejs:chain` extension carrying `monotonicCounter` / prevHash /
1016
+ * rowHash so auditors can correlate the envelope back to the chain.
1017
+ *
1018
+ * Returns an object with `events: [...]` ready to ship as JSON.
1019
+ *
1020
+ * @opts
1021
+ * format: "cadf", // optional — defaults to "cadf"
1022
+ * from: number|Date|string, // recordedAt >= this
1023
+ * to: number|Date|string, // recordedAt <= this
1024
+ * action: string, // exact action filter
1025
+ *
1026
+ * @example
1027
+ * var batch = await b.auditTools.exportCadf({
1028
+ * from: "2026-05-01T00:00:00Z",
1029
+ * to: "2026-05-08T00:00:00Z",
1030
+ * action: "auth.login",
1031
+ * });
1032
+ * // → { typeURI: ".../event-batch", framework: "blamejs", events: [...] }
1033
+ */
1034
+ async function exportCadf(opts) {
1035
+ opts = opts || {};
1036
+ if (opts.format !== undefined && opts.format !== "cadf") {
1037
+ throw new AuditToolsError("audit-tools/bad-format",
1038
+ "audit.export: format must be 'cadf' for exportCadf");
1039
+ }
1040
+ var fromMs = _toMs(opts.from);
1041
+ var toMs = _toMs(opts.to);
1042
+ var readRows = opts.readRows || _defaultReadRows;
1043
+ var criteria = {};
1044
+ if (fromMs != null) criteria.fromMs = fromMs;
1045
+ if (toMs != null) criteria.toMs = toMs;
1046
+ if (opts.action) criteria.action = opts.action;
1047
+ var rows = await readRows(criteria);
1048
+ var events = new Array(rows.length);
1049
+ for (var i = 0; i < rows.length; i++) {
1050
+ events[i] = _toCadfEvent(rows[i]);
1051
+ }
1052
+ return {
1053
+ typeURI: "http://schemas.dmtf.org/cloud/audit/1.0/event-batch",
1054
+ framework: "blamejs",
1055
+ frameworkVersion: pkg.version,
1056
+ range: {
1057
+ from: fromMs != null ? new Date(fromMs).toISOString() : null,
1058
+ to: toMs != null ? new Date(toMs).toISOString() : null,
1059
+ },
1060
+ events: events,
1061
+ };
1062
+ }
1063
+
1064
+ // Operator-facing dispatcher — `b.audit.export({ format })`. Future
1065
+ // formats register here.
1066
+ /**
1067
+ * @primitive b.auditTools.exportAudit
1068
+ * @signature b.auditTools.exportAudit(opts)
1069
+ * @since 0.7.30
1070
+ * @compliance soc2, pci-dss, gdpr
1071
+ * @related b.auditTools.exportCadf, b.auditTools.exportSlice
1072
+ *
1073
+ * Format dispatcher for downstream-SIEM exports. Reads `opts.format`
1074
+ * (default `"cadf"`) and delegates to the matching formatter. Future
1075
+ * envelope formats (CEF / OCSF / etc.) register here so callers stay
1076
+ * on a stable signature even when the framework adds formats.
1077
+ *
1078
+ * @opts
1079
+ * format: "cadf", // selector — defaults to "cadf"
1080
+ * from: number|Date|string, // recordedAt >= this
1081
+ * to: number|Date|string, // recordedAt <= this
1082
+ * action: string, // exact action filter
1083
+ *
1084
+ * @example
1085
+ * var batch = await b.auditTools.exportAudit({
1086
+ * format: "cadf",
1087
+ * from: "2026-05-01T00:00:00Z",
1088
+ * to: "2026-05-08T00:00:00Z",
1089
+ * });
1090
+ * // → { typeURI: ".../event-batch", framework: "blamejs", events: [...] }
1091
+ */
1092
+ async function exportAudit(opts) {
1093
+ opts = opts || {};
1094
+ var format = opts.format || "cadf";
1095
+ if (format === "cadf") return await exportCadf(opts);
1096
+ throw new AuditToolsError("audit-tools/bad-format",
1097
+ "audit.export: format must be one of: cadf (got '" + format + "')");
1098
+ }
1099
+
753
1100
  module.exports = {
754
1101
  archive: archive,
755
1102
  exportSlice: exportSlice,
1103
+ exportAudit: exportAudit,
1104
+ exportCadf: exportCadf,
756
1105
  forensicSnapshot: forensicSnapshot,
757
1106
  verifyBundle: verifyBundle,
758
1107
  purge: purge,