Skip to content

feat(server): add audit event schema and JSONL sink - #24

Merged
plusky merged 1 commit into
mainfrom
audit-sink
Jul 28, 2026
Merged

feat(server): add audit event schema and JSONL sink#24
plusky merged 1 commit into
mainfrom
audit-sink

Conversation

@plusky

@plusky plusky commented Jul 28, 2026

Copy link
Copy Markdown
Owner

What

A new self-contained audit module in the bugwarden crate: strict TOML configuration, the versioned closed-struct JSONL event schema (tool_call / initialize / audit_gap), and an append-only AuditSink (0600 files, 0700 parent, size rotation, gap records for lost events, torn-line self-healing, file-order == seq-order under one lock). Plus a fully commented examples/audit.toml and 28 unit tests. Deliberately not wired into the request path yet — server integration (--audit-config, fail-mode enforcement, per-tool recording) is the next PR, so each stays reviewable on its own.

Why

The guard's design goal is that the MCP client cannot tell a hidden bug from a nonexistent one (I2), never sees filtered counts (I3), and never learns rule names (I1). The operator needs exactly that record: what was asked, what the guard decided, and what was withheld. The audit stream carries those server-side-only facts, which is why it goes to an operator-owned local file and is never exposed through any MCP surface, never mixed into stderr diagnostics.

Invariants touched: I12 — the schema has no field that could carry the API key (plus a canary regression test); I1/I2/I3 motivate the boundary (operator file only). Client-visible behavior is unchanged by this PR — nothing is wired.

Verification

  • cargo fmt --check, cargo clippy --workspace --all-targets --locked -- -D warnings, cargo test --workspace --all-targets --locked (195 → 223 tests), cargo deny check, typos — all green.
  • Adversarial review before opening (per AGENTS.md), mutation-based: 16 gutting mutations applied to the real module. First-round verdict REJECT: 13 killed, 3 survived — three documented guarantees (rotation size bound, rotation-failure gap reason, open-time size accounting) had no coverage — plus torn-line behavior on partial writes, an fsync-failure seq caveat, and overclaiming docs. All addressed: +7 tests, torn-line self-healing (a lone \n heals the stream after a partial write), rotate_keep capped at 10000, docs corrected to state exactly what is enforced. The formerly-surviving mutations were then independently re-applied and are now killed (weakened rotation bound → 4 tests fail; zeroed open-time size → dedicated test fails).
  • Accepted, documented behaviors: under fsync = true a sync failure after a successful write can leave a duplicate seq on disk and over-report the gap (conservative: over-report, never under-report); at most one torn line per outage, and parsers skip empty lines; exactly one live sink per path (concurrent sinks corrupt rotation).

The guard hides bugs from the MCP client by design; the audit stream
is the operator's own record of what was asked and what the guard
decided. This adds the self-contained module: strict TOML configuration
(fail mode, fsync, size rotation, suppressed-id elision), the versioned
closed-struct JSONL event schema (tool_call / initialize / audit_gap),
and an append-only sink with 0600 files, gap records for lost events,
torn-line self-healing, and file-order == seq-order under one lock.
Wiring into the request path follows separately; nothing is exposed
through any MCP surface.

examples/audit.toml documents every knob; 28 unit tests pin the wire
format, rotation bounds, permissions, gap semantics, and the canary
that the Bugzilla API key can never reach a record (I12).
@plusky
plusky merged commit bfb6ce4 into main Jul 28, 2026
10 checks passed
@plusky
plusky deleted the audit-sink branch July 28, 2026 10:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant