/** * Append-only JSONL sink for security events. * * Persistence matters here: in-memory arrays per guard are erased on process * crash, which means a successful exfiltration followed by a forced restart * would also erase the forensic trail. Each event is written to disk * synchronously; reads merge the file with any in-memory caller-supplied * events, dedup by `id`, and return chronologically sorted output. * * Known limitation: hash-chaining (see `verifyChain()`) has no external * anchor for "how many lines should exist, and what did they contain" — * `hashLine()` uses no secret, so anyone with filesystem write access to this * file can edit, reorder, delete, insert, or truncate ANY suffix of it and * then correctly recompute every `previousHash` forward from that point, * producing a chain that verifies successfully end to end. This isn't * limited to deleting the most recent line(s) (e.g. `sed -i '$d'`, now * blocked for this file specifically via `PROTECTED_INFRASTRUCTURE_PATTERNS` * in command-guard.ts, but not for an attacker operating outside the guard * entirely, such as a compromised host process editing the file directly) — * it applies equally to rewriting an arbitrary earlier line and everything * after it. Local hash-chaining alone cannot close this; the actual * mitigation is shipping or exporting the log to a location the principal * being audited cannot write to (see `audit-exporter.ts`). */ import type { SecurityEvent } from './security-event.types.js'; export declare class JsonlAuditSink { readonly filePath: string; private writtenIds; constructor(filePath: string); /** * Each appended line embeds a hash of the previous line, so deleting, * reordering, or editing any prior line breaks the chain from that point * forward — detectable via `verifyChain()` even if the file itself * remains syntactically valid JSONL. The previous hash is recomputed from * disk on every call rather than cached in memory: a cached value goes * stale the moment another process (or another sink instance in this one) * appends to the same file, which would otherwise make ordinary * concurrent CLI usage indistinguishable from real tampering. */ append(event: SecurityEvent): void; /** * Recomputes the hash chain from the persisted file and confirms every * line's embedded `previousHash` matches the hash of the line before it. * Returns `false` if any line was deleted, reordered, or edited. */ readAll(): SecurityEvent[]; /** * Legacy lines from before hash-chaining existed have no `previousHash` * field at all — they're skipped from the equality check (not defaulted to * `GENESIS_HASH`, which only holds for the very first line) so a * pre-existing multi-line log doesn't spuriously fail verification on * every process start. Once a chained line has been seen, a later line * reverting to the legacy (no-`previousHash`) shape is itself treated as * tampering — e.g. an attacker replacing a chained line to hide a removal. */ verifyChain(): boolean; private computeLastHashFromFile; private ensureDir; private preloadWrittenIds; } export declare function getAuditSink(): JsonlAuditSink; export declare function resetAuditSink(): void; export declare function setAuditSink(sink: JsonlAuditSink | null): void; //# sourceMappingURL=audit-sink.d.ts.map