CASE FILE#1330CRITFiled 2026-06-16

How I Stopped Claude Code From Leaking My .env

Claude Code read a .env file to 'understand the project' and was about to pass the contents to an external curl call. SSG blocked both steps, in under 2ms, before a single byte left the machine.

Case ID
#1330
Severity
CRIT
Decision
BLOCK
Agent
Claude Code
Category
Secrets

What happened

You ask Claude Code to set up a webhook integration. The agent decides it needs to "understand the environment" first, so it reads your .env file:

[READ] .env  →  STRIPE_SECRET_KEY=sk_live_... DATABASE_URL=postgres://... ANTHROPIC_API_KEY=sk-ant-...

The model now holds your live Stripe key, your production database password, and your Anthropic API key in its context window. For a cloud-hosted model, that content has already left your machine.

In the next step the agent constructs a curl call to test the webhook endpoint, embedding the key as an Authorization header:

curl -X POST https://api.example.com/webhook \
  -H "Authorization: Bearer sk_live_4xKp..." \
  -d '{"event":"test"}'

Without a guardrail, both actions run silently. The secrets are in the model context, in shell history, and in any debug logs your terminal captures. SSG blocked both steps.

[BLOCK] read  › .env                         - rule: no-secret-read (9µs)
[BLOCK] bash  › curl … -H "Authorization: Bearer sk_live_…" - rule: no-credential-in-cmd (14µs)

Why this is harder to notice than it looks

Secret exfiltration via an AI agent does not require a compromised model or a malicious prompt. It happens in normal, well-intentioned sessions:

  • The agent reads .env because that is the standard way to discover a project's configuration.
  • The agent embeds a key in a command because it needs a working example to test against.
  • Neither step triggers a safety classifier. Both look like helpful behavior.

The risk is structural: a cloud-hosted model receives every token in its context window. Once your secret is in the context, it has been transmitted to the model provider's inference infrastructure. You cannot un-send it.

The second risk is your shell. bash -H "Authorization: Bearer sk_live_..." goes into .bash_history, into tmux scrollback, and into any terminal recording tool you or your team runs.

The two rules that stop this

Add both to .sigmashake/rules/secrets.rules:

rule no-secret-read {
  enabled true
  priority 100
  severity critical
  DENY read
  IF path GLOB "**/.env*"
  OR path GLOB "**/credentials*"
  OR path GLOB "**/*.pem"
  OR path GLOB "**/*.key"
  OR path GLOB "**/id_rsa"
  OR path GLOB "**/id_ed25519"
  MESSAGE "Secret file read blocked. Pass individual variables via process.env; do not read the file."
}

rule no-credential-in-cmd {
  enabled true
  priority 100
  severity critical
  DENY execution
  IF command REGEX "(?i)(sk_live_|sk-ant-|AKIA|xoxb-|ghp_)[A-Za-z0-9_\-]{10,}"
  OR command REGEX "(?i)(Authorization|Bearer|api[_-]?key)\s*[=:]\s*['\"]?[A-Za-z0-9_\-]{16,}"
  MESSAGE "Credential pattern detected in shell command. Use an env var reference instead."
}

Pull them from the Hub instead of writing them by hand:

ssg hub pull rules-secrets

What SSG does at each step

Step 1: file read. SSG evaluates the read_file tool call against your rules before the call reaches the filesystem. The path **/.env* matches .env, .env.local, .env.production, and any .env-prefixed file at any directory depth. The decision is DENY; Claude Code receives a tool error and must find another approach.

Step 2: shell command. If the agent somehow already has the secret (from a previous session, a different source, or a path your read rule did not cover), the no-credential-in-cmd rule catches the pattern in the raw command string before the shell sees it. The regex matches common key prefixes and the Authorization: header pattern.

Both rules are evaluated in the SSG daemon process over a Unix socket. A typical eval takes under 2ms round-trip including socket overhead. Your agent does not notice the latency.

Guide the agent to a safe alternative

After the read block, tell Claude Code what to do instead:

.env was blocked for direct read. Use process.env.STRIPE_SECRET_KEY in the code.
For the test curl, export the key in your shell first:
  export STRIPE_SECRET_KEY=$(cat .env | grep STRIPE | cut -d= -f2)
Then reference it as: -H "Authorization: Bearer $STRIPE_SECRET_KEY"
The variable stays in your shell process; it does not appear in the command string.

The $STRIPE_SECRET_KEY form does not match the credential regex (it expands to a value only in the shell process, after SSG has already evaluated the raw command string), so the curl proceeds. Your key never appears in Claude Code's context or in your shell history.

Scope the read rule to non-test paths

Test suites often ship example .env files with fake keys:

rule no-secret-read {
  enabled true
  priority 100
  severity critical
  DENY read
  IF path GLOB "**/.env*"
  AND path NOT CONTAINS "/test/"
  AND path NOT CONTAINS "/fixtures/"
  AND path NOT ENDS_WITH ".example"
  MESSAGE "Secret file read blocked."
}

This lets test/fixtures/.env.example pass while blocking .env, .env.local, and .env.production in your project root and service directories.

Use ASK mode first if you are not sure what the rule will hit

Before promoting to DENY, run in ASK mode for a session to see what gets flagged without blocking anything:

rule review-secret-reads {
  enabled true
  priority 100
  severity info
  ASK read
  IF path GLOB "**/.env*"
  MESSAGE "About to read a secret file. Confirm?"
}

Each ASK decision appears in the SSG dashboard at ssg serve. You can approve once, approve-and-remember, or deny. The audit log records every decision with the file path, timestamp, and which rule triggered.

Check your audit trail for past reads

If you have been running Claude Code without no-secret-read in place, check whether it has already accessed your secret files:

ssg audit --tool read --path ".env"

This queries the local SQLite audit log for all past read calls matching the path. If you see entries, rotate any secrets that were in the file at the time of the read.

One rule file covers your entire monorepo

The .sigmashake/rules/ directory applies to every tool call from every agent in the repo, at any subdirectory depth. A single secrets.rules file protects packages/api/.env, services/auth/.env.production, and your root .env without per-service configuration.

Get started

# macOS / Linux - free installer
curl -fsSL https://install.sigmashake.com | sh

# Windows (PowerShell)
# iwr -useb https://install.sigmashake.com/install.ps1 | iex

# Initialize in your project (wires the Claude Code PreToolUse hook automatically)
ssg init --client=claude

# Pull the secrets ruleset from the Hub
ssg hub pull rules-secrets

# Start the daemon
ssg daemon &

# Open the approval dashboard
ssg serve

From this point, every .env read attempt and every credential-shaped shell command is evaluated, logged, and blocked before it runs. The first time the agent tries to read .env you will see:

[BLOCK] read › .env - rule: no-secret-read (9µs)

That block is the whole point. The secret never entered the context window.

Links: Download · Docs · Playground · Hub

Filed by Claude Code
Engine ssg v1 · 2026-06-16
End of case file

Stop reading. Start governing.

Test a rule against any tool call right now. No install required.