Skip to content

dart audit

Juwon1405 edited this page Apr 30, 2026 · 3 revisions

dart-audit · SHA-256 chained audit log

Append-only, tamper-evident JSONL log. Records every MCP call (inputs, outputs, audit_id, timestamp, hash chain) so a human reviewer can replay the agent's reasoning end-to-end.


Why this exists

Forensic findings have a chain-of-custody requirement that ordinary software doesn't. If the agent claims "USB Kingston DataTraveler was inserted at 14:22:18 UTC", a reviewer must be able to verify, after the fact:

  1. The agent actually saw that artifact (audit recorded the read)
  2. The artifact has not been edited between the agent's read and the reviewer's verification (hash anchored)
  3. No log entry has been silently inserted, deleted, or reordered (chain unbroken)

A simple append-only log gives you (1). A SHA-256 chain — where each entry's hash includes the previous entry's hash — gives you (2) and (3) for free. Tampering with any entry breaks the chain at that point and every subsequent point.


API

from dart_audit import AuditLogger

logger = AuditLogger("audit/CASE-001.jsonl", run_id="run-abc")
logger.log(
    tool_name="get_amcache",
    inputs={"hive_path": "examples/sample-evidence/AmCache.hve"},
    outputs={"items": [...]},
    iteration=1,
    cpu_ms=42,
    bytes_read=1024,
    finding_ids=[],   # optional, used when this call directly produced a finding
)

Each log() call writes one JSON line to the file. The line contains:

Field Type Purpose
audit_id UUID4 Random per-entry ID. Used by findings for citation.
prev_hash hex-SHA-256 Hash of the previous entry. First entry uses 0000...0000.
entry_hash hex-SHA-256 Hash of this entry, including prev_hash.
iteration int Loop iteration counter.
tool_name str The MCP function called.
inputs dict Validated inputs as passed in.
output_digest hex-SHA-256 Hash of the output (full output not stored — too big).
finding_ids list[str] Findings this call produced (empty if none).
ts ISO-8601 Wall-clock timestamp.
cpu_ms, bytes_read int Resource accounting.

Output is referenced by digest only; the actual output JSON is in output/<run_id>/<audit_id>.json. This keeps the chain file small enough to read and verify.


Verifying the chain

python3 -m dart_audit verify audit/CASE-001.jsonl

Output (clean chain):

chain verified: 47 entries, tail=4f7a9c1b3e8d2046...8a13c5

Output (tampered):

line 12: entry_hash mismatch (audit_id=8fa06156)
ABORT — chain breaks at line 12

The verifier walks the chain forward, re-hashes each entry from raw fields, and checks entry_hash matches and prev_hash matches the previous entry_hash. Any tamper — payload change, deletion, reorder — breaks the walk.


Tracing a finding back to evidence

When the agent emits a finding like F-013, the report cites the audit_id of the supporting MCP call. To trace:

python3 -m dart_audit trace audit/CASE-001.jsonl F-013

Walks the chain, finds the entry where F-013 was produced, and prints the chain of MCP calls that led to it (input → call → output digest → previous related call → ...). Up to the original artifact read.


What chain integrity does not prove

  • That the inputs to a tool call were honest. The agent could pass any input.
  • That the outputs were not selectively emitted by a buggy or malicious tool implementation.
  • That the playbook the agent loaded was the playbook the operator thought they were running.

The audit chain is a transcript integrity tool, not a reasoning correctness tool. See Threat model for the full scope.


Files

dart_audit/src/dart_audit/
├── __init__.py        # AuditLogger, AuditEntry classes (~150 lines)
└── __main__.py        # CLI: verify, trace

See also

Agentic-DART

Concepts

The 5 packages

Reference

Running it

Case studies

Project


Project links

Clone this wiki locally