[FEATURE] Redact credential-shaped values when writing session transcripts

Status Open
Maintainer reply None cached
Activity 2 comments · opened Aug 14, 2026

Preflight Checklist

  • [x] I have searched existing requests and this feature hasn't been requested yet
  • [x] This is a single feature request (not multiple features)

Problem Statement

Session transcripts record tool output verbatim, so users end up with credentials on
disk that they never typed. Shell history records what you typed; a transcript records what
came back. One cat .env, one failed deploy printing a connection string, one CLI command
echoing a key — and the secret is in ~/.claude/projects/**/*.jsonl in cleartext.

On one developer machine (Windows 11, ~15 projects, 31 days of daily use, 47 sessions,
~114 MB of transcripts) an audit recovered these live credentials from the transcripts:

  • Azure Storage account keys for two storage accounts
  • An Azure SQL admin password guarding three databases, used by three applications
  • A Twilio auth token
  • An OAuth token for a cloud-storage uploader, still valid after ~5 years

All have since been rotated or revoked. None of them were ever typed by the user — every one
arrived as tool output.

Two properties make this worse than ordinary local-secret hygiene:

  1. Aggregation. One directory concentrates the secrets of every project the user has

touched, where previously they were scattered.

  1. Agent reachability. MCP servers, subagents and hooks run as the user with filesystem

access. Combined with prompt injection from untrusted content, there is a plausible
read-and-exfiltrate path that needs no malware.

The part I think is not widely appreciated: an agent's intent is not a retention control

This is reproducible in about five minutes, and the result is binary.

Reproduce:

  1. In a session, have Claude generate 2048 random bytes, write them to a file, and record the

SHA-256. Tell it the value must not be remembered.

  1. End the session. Start a fresh one. Ask it to recall the contents. It cannot — as expected.
  2. Now grep the previous session's .jsonl in ~/.claude/projects/.

Result: the full content is recoverable bit-exact from the transcript (verify by
SHA-256 against the original), because it was logged as an ordinary Read tool result.

In our run the assistant had, unprompted and before any test was announced, explicitly
reasoned about not persisting the value:

"I haven't written the contents, or that hash, into memory… quietly filing the answer where a successor would find it would be me rigging it."

It persisted anyway, through a channel it was not guarding. A model deciding not to record
something does not prevent it being recorded.
Only not-displaying the value does. "The
assistant won't remember it" is a natural assumption for users to make, and it is wrong.

Proposed Solution

Redact credential-shaped values as the transcript is written. Write
[REDACTED:aws_key] in place of the value. A pattern scan over a transcript line costs well
under a millisecond, and --resume does not need the secret to reconstruct the conversation.

If redaction-by-default is considered too aggressive, the weaker version still helps a great
deal: warn — surface a one-line notice that a credential-shaped value was written to the
transcript. Users cannot act on an exposure they do not know exists. Today there is no signal
at all.

Either behaviour would ideally be configurable (transcriptRedaction: off | warn | redact).

Alternatives Considered

Shortening cleanupPeriodDays limits the exposure window but does not prevent it, and it
trades directly against wanting a usable history — see #59248 and #62476, which cover the
silent-deletion side and which I am deliberately not duplicating here. Redaction is
attractive precisely because it dissolves that trade rather than picking a side: a redacted
transcript can be kept indefinitely.

Additional Context

I built a redacting archiver over my own transcripts to check the idea is cheap. It is — one
afternoon, 113.8 MB → 48.1 MB gzipped. But the first version failed its own verification,
and that failure is the useful part for anyone implementing this:

  • Patterns only catch key=value shape. The SQL password was still present in 18 of 47

archived files, because it also appeared in prose — "the plaintext password <value> for
SQL login" — which matches nothing.

  • Patterns are keyword-blind. Twilio__AuthToken=… contains none of password, secret

or api_key, so nothing fired. It was found by reading a file by eye.

  • Over-matching is just as damaging. An early sk-[A-Za-z0-9_-]{20,} rule reported four

non-existent OpenAI keys — it had matched dust-ma|sk-filters and
ri|sk-and-environment.pdf. A scanner that cries wolf gets ignored.

So: a shipped implementation should be explicit that redaction is best-effort, and should not
imply completeness. Pairing patterns with hash-matching of known-revoked values closed most of
the gap for me (the plaintext is never stored — only SHA-256 of dead values).

Scope

To be clear about what this is not: transcript storage is documented, intended behaviour and
the mechanism behind --resume. Nothing here bypasses a control, and no trust boundary is
crossed — the files are readable only by the user who could already read their own .env
files. This is a defaults-and-defence-in-depth request, not a vulnerability report, which is
why it is filed here rather than through the disclosure programme.

Measured on one machine, one OS, one version; I have not tested whether the transcript format
or retention default varies by platform or release.

---

*Investigated and drafted with Claude Code. Findings measured on my own machine; every
credential referenced was rotated or revoked before filing.*

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗