The agent reads your .env. So does the prompt cache.

May 20, 2026 · 3 min read

Claude Code asks to read .env so it can wire up your local dev server. You hit accept. The file goes into the conversation. The conversation goes into the prompt cache. The cache lives on someone else's hardware for some retention window you don't fully control.

Now multiply that by every agent you've pointed at a repo with credentials checked out next to the code.

I built drape because the obvious fixes don't help when the agent is the thing doing the reading.

What it does

$ cat .env
DATABASE_URL=postgres://user:hunter2@localhost/db
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
GITHUB_TOKEN=ghp_1234567890abcdef1234567890abcdef1234
APP_PASSWORD=correct-horse-battery-staple
RANDOM_HEX=a8f3c9e7b2d1f4a6c8e0b9d7f2a1c4e6

$ drape .env
DATABASE_URL=<basic-auth>
AWS_ACCESS_KEY_ID=<aws-access-key>
GITHUB_TOKEN=<github-token>
APP_PASSWORD=cor...
RANDOM_HEX=a8f...

The agent still gets a useful file. It sees the shape of the config, the variable names, and a hint at what kind of credential each holds. The actual bytes never enter the model context.

Three strategies, most-protective wins

drape applies three masking strategies in order. Whichever hides most, wins.

Pattern recognition. Known credential shapes are replaced with a type label. AWS keys, GitHub tokens, Slack tokens, JWTs, Stripe keys, private keys, basic-auth URIs, and about fifteen others. Powered by Yelp's detect-secrets. Zero characters leak; the agent only learns the type.

Entropy gate. Values whose entropy falls below 3.0 bits per character render as . This catches short, low-variety values like hunter2 or password. Note that passphrases like correct-horse-battery-staple have enough character diversity to clear the threshold -- they fall through to prefix reveal instead. The defaults are conservative.

Length-bounded prefix. For high-entropy values that don't match a pattern, drape reveals the first three characters, capped at 25% of the value's length. A 22-char hex blob shows three chars (16^19 brute force space remaining). A four-char value shows one char.

Order matters. A short AWS key would otherwise leak a meaningful prefix; the pattern detector catches it first and replaces it with a label. The 25% cap prevents the prefix strategy from doing the wrong thing on tiny values. The defaults are conservative because the cost of getting them wrong is significant.

Read isn't the only leak path

The first version of drape only intercepted Read calls. That's not enough. An agent with Bash access can cat .env directly. Or grep PASSWORD .env. Or pipe through awk -F= to skip the masking layer entirely. The hook now covers three Claude Code tools:

  • Read on a .env-shaped path returns the masked rendering.
  • Grep on a .env-shaped path re-runs the search against the masked rendering, so matches on key names still surface but values stay hidden.
  • Bash commands that try to read a .env-shaped path with cat, head, tail, less, grep, rg, ag, sed, awk, or cut are denied with a redirect telling the agent to use drape instead.

The agent doesn't lose the ability to look at config. It loses the ability to look at config the unsafe way.

SOPS without round-tripping plaintext

For encrypted .env.sops files, drape detects the format, shells out to sops -d, masks the in-memory plaintext, and prints. The decrypted file is never written to disk and never returned to the caller. Same logic whether you invoke drape from the shell or the hook fires it.

Audit without disclosure

Set DRAPE_AUDIT_LOG=~/.drape-audit.jsonl and every masking operation appends a line:

{"ts":"2026-05-06T19:30:00+00:00","event":"cli_mask","file":".env",
 "format":"env","key_count":12,"prefix_chars":3}

Filename, format, key count. No values, ever. You get a forensic trail of when an agent looked at credentials without writing the credentials anywhere new. The audit writer is the only path in drape that swallows exceptions: if the log destination is unwritable, masking still succeeds. Failing closed on the audit log would be worse than failing open.

Install

uv tool install drape                 # core: .env, .env.sops
uv tool install 'drape[all]'          # + YAML, TOML, and structured format support
bash scripts/install-claude-hook.sh --project-dir .

Or run without installing: uvx drape .env

64 tests, 88% line coverage, MIT license. Source at https://github.com/pike00/drape.