DobermanDocs
Menu

Concepts Storage, redaction and audit

Storage, redaction and audit

This page explains where Doberman's decision history lives, what it contains, and how you can forward it somewhere else.

View as Markdown

On this page

Every decision Doberman makes gets written to a local SQLite database under .doberman/ in the repo it protects. That directory is never committed (add it to .gitignore if you haven't already), and nothing leaves your machine unless you wire up a sink. doberman log, doberman tui, and doberman status all read from this one local store.

Redaction happens before the write#

The redaction boundary sits before the database, not after. By the time a row reaches storage, it has already been reduced to a path class, a reason code, a verdict, and an HMAC-SHA256 fingerprint where a value needs to be recognized again without being stored. The key behind that fingerprint is generated locally, kept at 0600 (or in your OS keyring), and never checked in or logged. A raw secret, a full file path outside its class, a prompt, or file contents never reach the log at all.

The same database holds more than the decision log: single-use, action-bound approvals (approvals.py), the MCP tool-schema pins that catch a downstream server changing its tool definitions out from under you (tool_pins.py), sticky session-taint state (taint.py, cleared with the gated doberman taint clear), and the cost and device metrics that back doberman session-summary.

The audit sink seam#

Everything above stays local by default. If you want a copy elsewhere, Doberman ships a plugin seam: an AuditSink registered through the doberman.audit_sinks entry point receives the same already-redacted rows the local database gets, nothing more. Two sinks ship built in: a webhook sink and an OpenTelemetry sink, both config-gated and off unless you turn them on.

flowchart LR
  D["Decision"] --> RED["redact:<br/>path classes · reason codes ·<br/>HMAC fingerprints only"]
  RED --> LOG["log.py<br/>record_decision / read_decisions"]
  LOG --> DB[("db.py<br/>SQLite")]
  FP["fingerprint.py<br/>keyed HMAC-SHA256"] --> RED
  TA["taint.py<br/>secret-egress taint state"] --> DB
  AP["approvals.py<br/>single-use, action-bound"] --> DB
  TP["tool_pins.py<br/>MCP schema TOFU pins"] --> DB
  CO["cost.py / device_metrics.py<br/>heartbeat.py"] --> DB
  LOG --> SK["sinks.py<br/>AuditSink seam (entry points)"]
  SK --> WH["WebhookAuditSink"]
  SK --> OT2["OtelAuditSink"]
  classDef amber fill:#b45309,color:#fff,stroke:#b45309
  class RED amber

The redaction boundary sits before the write; sinks and the local database receive the same already-redacted rows.

Logging never takes part in the decision: a sink that is slow, unreachable, or misconfigured cannot stall or crash the decision path, and nothing in storage can alter a verdict after the engine has decided it. If you need a plugin seam beyond the two built-in sinks, see Plugins; to wire up the OpenTelemetry sink specifically, see Audit and OpenTelemetry.

Doberman v0.18.3 · Apache-2.0 · Defense-in-depth, not airtight.

navigate open esc close