Audit Tools

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.

Four core operations on top of the live audit_log chain:

archive(opts) Bundle rows older than before into a PQC-encrypted archive with chain proof + a covering signed checkpoint. Live rows are untouched until a separate purge() call. exportSlice(opts) Auditor-shaped slice (date range / action filter) with chain proof — deliver evidence to an external auditor without surrendering the whole log. verifyBundle(opts) Round-trip integrity: decrypt the bundle, walk chain math across the contained rows, verify the covering checkpoint's ML-DSA signature (archive bundles only). purge(opts) Confirmation-gated deletion of live rows already captured in a verified archive bundle. Inserts a purge-anchor so b.audit.verify() keeps working post-purge.

Bundle layout (POSIX-flat directory; matches the backup-bundle shape so operators see one mental model for "encrypted blamejs bundle"):

/manifest.json Canonical-JSON manifest (format / kind / range / rowCount / per-blob salts / framework version; archive bundles also carry the covering checkpoint summary). /rows.enc PQC-encrypted JSONL of audit rows in sealed form so rowHash stays computable from disk bytes byte-for-byte. /checkpoint.enc Archive-only. PQC-encrypted JSON of the covering audit_checkpoints row.

kind="archive" bundles always include a covering checkpoint (atMonotonicCounter >= lastCounter) so the off-chain signature tamper-evidences the whole archive. kind="export" bundles are auditor evidence; the chain math is self-contained, with the upstream signature anchor optional.

b.auditTools.withRecordedAtIso(row) #

0.7.30

Surface recordedAt as ISO-8601 / RFC 3339 (with explicit Z) alongside the framework's primary Unix-ms integer. Auditors comparing rows against external SIEM events expect ISO; the chain hash is unaffected because the canonical wire form used for hashing doesn't include the derived recordedAtIso field.

Returns a shallow copy with recordedAtIso added when recordedAt is a finite number / bigint; otherwise returns the input unchanged.

var row = { _id: "evt-1", recordedAt: 1762560000000, action: "auth.login" };
var formatted = b.auditTools.withRecordedAtIso(row);
// → { _id: "evt-1", recordedAt: 1762560000000,
//     recordedAtIso: "2025-11-08T00:00:00.000Z", action: "auth.login" }

b.auditTools.archive(opts) #

0.7.30hipaapci-dssgdprsoc2sox-404
{
  out:        string,         // fresh directory path (omit when returnBytes)
  returnBytes:boolean,        // true → return { manifest, files } in memory, no disk
  before:     number|Date|string,  // archive rows recordedAt < this
  passphrase: Buffer|string,  // bundle-encryption passphrase
}

Bundle every audit row older than opts.before into a PQC-encrypted archive (XChaCha20-Poly1305 + Argon2id-derived key) containing a chain proof and the covering ML-DSA-87 checkpoint. Live rows are untouched — call b.auditTools.purge separately once the archive is verified.

Refuses if opts.out exists, no rows match, or no signed checkpoint covers the slice (run b.audit.checkpoint() first).

Pass returnBytes: true instead of out for the bundle as an in-memory { filename: Buffer } map (rows.enc + checkpoint.enc + manifest.json) — the read-only / serverless path. out and returnBytes are mutually exclusive.

var ninetyDaysAgo = Date.now() - 90 * 24 * 60 * 60 * 1000;
var result = await b.auditTools.archive({
  out:        "/var/audit/2026-Q1.bundle",
  before:     ninetyDaysAgo,
  passphrase: process.env.AUDIT_BUNDLE_PASSPHRASE,
});
// → { rowCount: 14823, range: { firstCounter: 1, lastCounter: 14823, ... },
//     manifestPath: "/var/audit/2026-Q1.bundle/manifest.json", ... }

b.auditTools.exportSlice(opts) #

0.7.30hipaapci-dssgdprsoc2
{
  out:        string,                // fresh directory path (omit when returnBytes)
  returnBytes:boolean,               // true → return { manifest, files } in memory, no disk
  from:       number|Date|string,    // recordedAt >= this (inclusive)
  to:         number|Date|string,    // recordedAt <= this (inclusive)
  action:     string,                // exact action match (optional)
  passphrase: Buffer|string,         // bundle-encryption passphrase
}

Auditor-shaped slice — bundle the audit rows in [from, to] (optionally filtered by exact action) into a PQC-encrypted directory carrying chain-proof material. Refuses non-contiguous slices because chain verification cannot ground a sequence with gaps in monotonicCounter.

Use date-range filters that cover every row in the range; an action filter that drops intermediate counters is rejected with audit-tools/non-contiguous.

Pass returnBytes: true instead of out to get the bundle as an in-memory { filename: Buffer } map (rows.enc + manifest.json) with no filesystem touch — the read-only / serverless path; ship it to object storage or over the wire. out and returnBytes are mutually exclusive.

var bundle = await b.auditTools.exportSlice({
  out:        "/tmp/audit-2026-q1.bundle",
  from:       "2026-01-01T00:00:00Z",
  to:         "2026-03-31T23:59:59Z",
  passphrase: process.env.AUDIT_BUNDLE_PASSPHRASE,
});
// → { rowCount: 4218, manifest: { kind: "export", ... }, ... }

b.auditTools.verifyBundle(opts) #

0.7.30hipaapci-dssgdprsoc2sox-404
{
  in:                          string,               // bundle directory
  passphrase:                  Buffer|string,        // decryption passphrase
  verifyCheckpointSignature:   boolean,              // default true
  verifySignature:             function(checkpoint), // override the default verifier
  includeRows:                 boolean,              // attach decrypted rows to result
}

Round-trip integrity check on a bundle directory: decrypt rows.enc, walk the prevHash → rowHash chain across the contained rows starting from the manifest's predecessorRowHash witness, confirm firstRowHash / lastRowHash match, and (archive only) verify the covering checkpoint's ML-DSA-87 signature against the locally-loaded audit-sign public key (or opts.verifySignature for cross-machine auditors).

Returns { ok: true, kind, rowsVerified, range, manifest } on success or { ok: false, reason, breakAt? } at the first break.

var result = await b.auditTools.verifyBundle({
  in:         "/var/audit/2026-Q1.bundle",
  passphrase: process.env.AUDIT_BUNDLE_PASSPHRASE,
});
if (!result.ok) {
  console.error("bundle integrity break:", result.reason);
  process.exit(1);
}
// → { ok: true, kind: "archive", rowsVerified: 14823, range: { ... } }

b.auditTools.purge(opts) #

0.7.30hipaapci-dssgdprsoc2sox-404
{
  confirm:          true,               // exact `true` required
  archive:          string,             // path to a verified archive bundle
  passphrase:       Buffer|string,      // bundle decryption passphrase
  verifySignature:  function(checkpoint),// auditor pubkey override
  dualControlGrant: object,             // required when audit_log is declared under b.db.declareRequireDualControl — from b.dualControl.consume({ action: "auditTools.purge" })
}

Confirmation-gated deletion of live audit rows already captured in a verified archive bundle. Refuses unless opts.confirm === true, the bundle verifies clean as kind="archive", and the bundle's firstCounter / predecessorRowHash match the next contiguous purge point on disk. Inserts a _blamejs_audit_purge_anchor row so b.audit.verify() keeps chaining post-purge — the anchor's lastPurgedRowHash becomes the new chain origin.

var result = await b.auditTools.purge({
  confirm:    true,
  archive:    "/var/audit/2026-Q1.bundle",
  passphrase: process.env.AUDIT_BUNDLE_PASSPHRASE,
});
// → { purged: true, rowsDeleted: 14823, lastPurgedCounter: 14823, ... }

b.auditTools.forensicSnapshot(opts) #

0.8.40hipaapci-dssgdprsoc2sox-404doranis2
{
  out:        string,               // fresh directory path (omit when returnBytes)
  returnBytes:boolean,              // true → return { ...manifest, files } in memory, no disk
  since:      number|Date|string,   // include rows recordedAt >= this (windowed since → now)
  passphrase: Buffer|string,        // bundle-encryption passphrase
  reason:     string,               // required incident-context reason
  incidentId: string,               // optional ticket / incident id
  actor:      { id, role },         // optional incident-commander identity
}

Post-compromise composer that bundles an audit slice (from since → now) plus operator-supplied incident metadata (incidentId, reason, actor) and runtime fingerprint (Node version / platform / pid / uptime) into a single tamper-evident artifact for legal / regulators / the IR team. Emits an audit.forensic_snapshot.composed audit event so the act of composing the snapshot is itself on-chain.

Pass returnBytes: true instead of out for the snapshot as an in-memory { filename: Buffer } map (the slice's rows.enc + manifest.json plus forensic-snapshot.json) — the read-only / serverless path. out and returnBytes are mutually exclusive.

var snap = await b.auditTools.forensicSnapshot({
  out:        "/forensics/2026-05-08-inc-42",
  since:      Date.now() - 7 * 24 * 60 * 60 * 1000,
  passphrase: process.env.AUDIT_BUNDLE_PASSPHRASE,
  incidentId: "inc-2026-05-08-42",
  reason:     "ATO investigation: 14 failed MFA from new geo, user u-42",
  actor:      { id: "alice@ops.example.com", role: "incident-commander" },
});
// → { snapshotKind: "forensic", incidentId: "inc-2026-05-08-42", ... }

b.auditTools.exportCadf(opts) #

0.7.30soc2pci-dssgdpr
{
  format:   "cadf",                // optional — defaults to "cadf"
  from:     number|Date|string,    // recordedAt >= this
  to:       number|Date|string,    // recordedAt <= this
  action:   string,                // exact action filter
}

Format an audit slice as a CADF event-batch (Cloud Auditing Data Federation, ISO/IEC 19395:2017 + DMTF) — the FedRAMP / OpenStack envelope cross-tenant SIEMs and CSP reporting tools expect for federated tooling. Maps blamejs fields onto CADF attributes (initiator / target / observer / outcome / reason) and embeds a blamejs:chain extension carrying monotonicCounter / prevHash / rowHash so auditors can correlate the envelope back to the chain.

Returns an object with events: [...] ready to ship as JSON.

var batch = await b.auditTools.exportCadf({
  from:   "2026-05-01T00:00:00Z",
  to:     "2026-05-08T00:00:00Z",
  action: "auth.login",
});
// → { typeURI: ".../event-batch", framework: "blamejs", events: [...] }

b.auditTools.exportAudit(opts) #

0.7.30soc2pci-dssgdpr
{
  format:   "cadf",                // selector — defaults to "cadf"
  from:     number|Date|string,    // recordedAt >= this
  to:       number|Date|string,    // recordedAt <= this
  action:   string,                // exact action filter
}

Format dispatcher for downstream-SIEM exports. Reads opts.format (default "cadf") and delegates to the matching formatter. Future envelope formats (CEF / OCSF / etc.) register here so callers stay on a stable signature even when the framework adds formats.

var batch = await b.auditTools.exportAudit({
  format: "cadf",
  from:   "2026-05-01T00:00:00Z",
  to:     "2026-05-08T00:00:00Z",
});
// → { typeURI: ".../event-batch", framework: "blamejs", events: [...] }

Last updated 2026-08-08T16:39:49.652Z by seeder.