Prevention fails: making secrets actually disappear from session databases

Let me be honest about AI agent security: the prevention layers are necessary, and they are not sufficient.

You can inject secrets into subprocesses without the agent seeing them. You can DLP-scan outbound LLM traffic and mask keys in flight. And still, the secret gets somewhere it shouldn’t — a debug log line, a pasted config in a prompt, a session database row written by a tool call that dumped an environment variable. Sessions stores are append-only by design; the past is already written.

So the last layer of a credential strategy isn’t prevention. It’s erasure: going back and making the bytes actually gone.

Deleting a row is not erasing data

The trap with SQLite-backed session stores is that “delete” is a lie. When you delete a row:

  • FTS virtual tables keep their own copies of indexed text. Delete the row, the FTS index still contains the tokens.
  • Free pages retain the old bytes until overwritten. The data is physically present in the file, recoverable with any forensics tool.

A redaction that replaces the value in the row but leaves the FTS index and page remnants is theater — the secret is still sitting in the file, just harder to see.

The scrub pipeline

The retroactive scrubber works on two targets:

trustless dlp scrub-db <db-path> — for SQLite session databases. It runs both redaction layers over every table: Layer 1 replaces known secret values (zero false positives), Layer 2 masks pattern matches (gitleaks-derived rules, the same ones the outbound DLP uses). Then it rebuilds the FTS virtual tables and runs VACUUM, so no physical remnants survive in the file. The verification is in the test suite: after scrubbing, the secret value is absent from the raw file bytes, not just from the query results.

trustless dlp scrub-text <path> — for plaintext files and directories, same two layers, same pattern set.

The defaults are designed for safety-first operation:

trustless dlp scrub-db  ~/.local/state/hermes/sessions.db            # dry-run: scan only
trustless dlp scrub-db  ~/.local/state/hermes/sessions.db --apply    # write changes
trustless dlp scrub-db  ~/.local/state/hermes/sessions.db --apply --backup  # keep a .bak first

The default is dry-run — it scans and prints per-table/per-file hit counts without writing a byte. --apply actually scrubs, --backup keeps a .bak copy first, and --min-len (default 8) prevents the pattern layer from shredding short, low-value strings.

Why this is the last layer, not the first

Erasure is a cleanup tool, and it should be treated as one: run it when a key is exposed, before shipping a laptop, or on a schedule as belt-and- braces. It’s the layer that answers the question “what if a secret already got into a session store yesterday?” with something better than “hope it doesn’t get read.”

Prevention makes the future safer. Erasure makes the past safe. You need both — and the second one is the one most setups don’t have.

This is the fourth layer of trustless, a zero-dependency credential broker CLI for AI agents (Go, single binary, 321 tests with -race, cosign-signed releases with SBOM). The scrubber shares its pattern rules with the outbound DLP, so what you mask in flight is what you erase retroactively.

Have you ever recovered a “deleted” secret from a database or log file — and what did you do about it?