DEV Community

Cover image for Cryptographic Forensics for AI Coding Agent Sessions

Cryptographic Forensics for AI Coding Agent Sessions

A Claude Code or Codex CLI session writes a JSONL file to disk. If the agent runs rm -rf on a training-data directory or terraform destroy -auto-approve on production, that file is where an incident review starts.

A JSONL file is not evidence. Anyone with shell access can rewrite it. To a third party who doesn't trust the machine it came from, it proves nothing.

That gap matters once agents have credentials to real infrastructure. Most agent observability tooling is built for debugging and quality, not for the moment after damage is done. This post is about the three cryptographic properties that turn a transcript into something an auditor or regulator can verify, and how the DEPOSE project wires them together.

Three properties

Assume the machine that produced the bundle can't be trusted. Three things need to hold at once:

  1. Tamper-evident. Any byte change has to be detectable. Hash chain over events: change a byte, replay fails.
  2. Authenticated. The record has to be bound to a key the producer controls and publishes a fingerprint for. Ed25519 signatures over a manifest.
  3. Anti-backdated. A party other than the producer has to anchor the record in time. RFC 3161 tokens from a public TSA.

The primitives are old and well understood. The hard part is wiring them through a normalized event schema and shipping a verifier that doesn't depend on the producer's runtime.

No LLM in the signed path

Every event is captured at execution time or normalized from the session JSONL, then committed to the hash chain. The human-readable narrative is generated separately, from deterministic Handlebars templates over the signed events. It's excluded from the root hash.

If generated prose became part of the signed record, verification would depend on model behavior staying stable and reproducible. DEPOSE avoids that dependency. The signed record is event data and hashes. The prose is templated commentary with [#evt-<ulid>] citations back to the signed events. You can rewrite the narrative without affecting verification. Change an event and verification fails.

What's in a bundle

A DEPOSE bundle is a directory, not an opaque archive:

incident-01JABC.../
├── manifest.json            bundleId, rootHash, eventsJsonlSha256, sigs, timestamps
├── events.jsonl             every event in canonical JSON, byte-pinned by manifest
├── rules/destructive.yaml   ruleset used at reconstruction time
├── narrative.md / .html     templated prose with per-event citations
├── verify.txt               human-readable verification summary
├── artifacts/               captured file diffs, payloads
├── attestations/            Ed25519 signatures, RFC 3161 timestamp tokens
└── raw/                     source JSONL, shell history, capture records
Enter fullscreen mode Exit fullscreen mode

Change a byte of events.jsonl, manifest.json, or rules/destructive.yaml and verification fails. Canonical JSON follows RFC 8785 (JCS), which is what lets a Go verifier check a TypeScript-produced bundle without either side trusting the other's serializer.

Two binaries

The producer is TypeScript. The verifier is a separately-built static Go binary, depose-verify. The separation is deliberate: you hand the binary to whoever needs to check the bundle (auditor, opposing counsel, regulator, a customer's security team) and they run it on their own machine. No producer stack required.

A passing run prints:

parse        OK
signature    OK
chain-replay OK
artifacts    OK
timestamp    OK
PASS  bundleId=...  rootHash=...
Enter fullscreen mode Exit fullscreen mode

The cryptography here is mostly off-the-shelf. The actual engineering work is in normalization: getting Go and Node to serialize identically, getting timing and ordering right across capture sources, deciding what counts as one event versus two. Canonical JSON is the unsexy part. Float formatting, key ordering, unicode escapes: Go and Node have to agree byte-for-byte or the verifier rejects a bundle the producer thinks is fine. That's what the cross-language conformance vectors in tests/conformance/ are for.

Verifiers can pin a producer's expected key fingerprint and consult a revocation list, both at the command line. The RFC 3161 timestamp does double duty here: a bundle stamped before a key is revoked stays time-anchored, so "when was this signed" remains answerable even if the key is later compromised.

Capture modes

Two modes, different coverage.

Reconstruction reads the Claude Code session JSONL after the fact, compares it against shell history (bash, zsh, fish) and git reflog where available, and builds a bundle. Lower-bound mode. It can verify integrity after packaging. It can't prove the original session file was complete before capture.

Active capture installs a Claude Code PreToolUse hook and POSIX shell shims for the binaries that tend to do destructive things: terraform, aws, gh, kubectl, psql, gcloud, railway, rm. Records land under ~/.depose/captures/ at execution time. A later depose package merges them with the session JSONL so every covered event has a verified pre-execution intent on record.

DEPOSE can prove integrity of captured events. It can't prove an uninstrumented system captured everything. An agent that shells out to a binary not in the shim list, or hits an API directly, still shows up in the JSONL but won't have an active-capture record. The coverage matrix is in the repo.

macOS and Linux only. Windows isn't supported (POSIX 0600 on the key store, POSIX shell scripts for the shims). WSL2 works.

Release pipeline

Releases ship with SBOMs, provenance attestations, and signed checksums. The specifics: CycloneDX for both halves, SLSA L3 provenance, and SHA256SUMS signed via cosign keyless. CI rebuilds the two checked-in example bundles (an rm -rf on training data, a terraform destroy on infrastructure) on every push and runs three semantic tamper rejections to confirm the verifier fails closed.


Right now most coding-agent session logs are treated like disposable debug output. That assumption gets weaker the moment an agent can modify infrastructure.

https://github.com/Aftermath-Technologies-Ltd/depose

Top comments (0)