Skip to content

Mask secrets before they reach the store - #53

Merged
gengwg merged 1 commit into
mainfrom
feat/redact-secrets
Sep 7, 2026
Merged

gengwg merged 1 commit into
mainfrom
feat/redact-secrets

Conversation

@gengwg

@gengwg gengwg commented Sep 7, 2026

Copy link
Copy Markdown
Owner

A session records whatever crossed it — a key pasted into a prompt, a printenv, a .env read back by a tool — and all of it landed in agentlens.db in clear text, a file with no authentication in front of it. A token in a transcript is a token at rest.

The pattern table is the tier-1 set from Grafana's agento11y (Apache-2.0, patterns hand-curated from Gitleaks; attribution in NOTICE). Twenty-two high-confidence formats: cloud and provider API keys, GitHub and Slack tokens, private-key blocks, connection strings carrying credentials, bearer tokens.

Their mask format is kept verbatim so their own 28 light-mode fixtures are the tests here — the only real proof that a transcribed regex is faithful. All pass unchanged.

Their tier 2 is deliberately left out. It guesses at key=value shapes, which is right for a product that must not leak and wrong here: DB_PASSWORD=hunter2 in a transcript is often the thing you opened the transcript to find.

Where it hooks in: the two prepared statements every adapter writes through, not per field and not per adapter, so prompts, model output, tool arguments and tool results are covered in one pass. The event is already serialized JSON there and every mask is plain ASCII, so masking the serialized form cannot break it. Cost measured at 2 µs per event with no secret, 4 µs with one.

Scale of the exposure: of 44,934 existing events, 12 across 8 sessions contain something this would now remove. Some of those are certainly the example tokens from this feature's own development, and I did not look to find out which. Old rows are not rewritten, and AGENTLENS_REDACT=0 turns masking off.

One footnote: the first push was rejected by GitHub's secret scanning, because the borrowed fixtures carry realistic example keys. That is the correct behaviour from it, so the corpus is stored base64 and decoded by the test rather than allowlisted. 82 tests pass.

https://claude.ai/code/session_01NZDHk4yVYP4jMZkTUMQ1Lw

A session records whatever crossed it: a key pasted into a prompt, a
printenv, a .env read back by a tool. All of it landed in agentlens.db in
clear text, a file with no authentication in front of it.

The pattern table is agento11y's tier-1 set (Apache-2.0, patterns from
Gitleaks; see NOTICE) - 22 high-confidence formats. Their mask format is
kept verbatim so their own 28 light-mode fixtures serve as the tests, which
is the only real proof a transcribed regex is faithful. Their tier 2 is
left out on purpose: it guesses at key=value shapes, and DB_PASSWORD=x in a
transcript is often the thing you opened the transcript to find.

Masking sits in the two prepared statements every adapter writes through,
so prompts, model output, tool arguments and tool results are covered in
one pass. Cost is 2 microseconds per event with no secret, 4 with one.

The fixtures are stored base64: GitHub's secret scanning blocked the first
push because they carry realistic example keys, which is fair, so the test
decodes them rather than the repo allowlisting a secret.

Of 44,934 existing events, 12 across 8 sessions contain something this
would now remove. Old rows are not rewritten; AGENTLENS_REDACT=0 disables.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZDHk4yVYP4jMZkTUMQ1Lw
@gengwg
gengwg merged commit 6a260d1 into main Sep 7, 2026
1 check passed
@gengwg
gengwg deleted the feat/redact-secrets branch September 7, 2026 18:09
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