CLAUDE_ENV_FILE contents leaked via ps aux, transcript, and accumulated session-env files
Summary
Environment variables written to CLAUDE_ENV_FILE by SessionStart hooks are leaked through three channels:
- Visible to all users via
ps aux— contents are inlined into thebash -cargv, not sourced from a file - Persisted in session transcript JSONL — the full
bash -ccommand string including secrets is logged - Accumulated in
~/.claude/session-env/as world-readable files — one copy per session, never cleaned up
Reproduction
- Create a
.env.localwith a secret (e.g.,GITHUB_PERSONAL_ACCESS_TOKEN=ghp_xxx) - Add a SessionStart hook that loads it:
``json``
{
"SessionStart": [{
"matcher": "",
"hooks": [{
"type": "command",
"command": "if [ -f \"$CLAUDE_PROJECT_DIR/.env.local\" ]; then grep -v '^#' \"$CLAUDE_PROJECT_DIR/.env.local\" | grep -v '^$' | sed 's/^/export /' >> \"$CLAUDE_ENV_FILE\"; fi"
}]
}]
}
- Run any bash command in CC
- In another terminal:
ps aux | grep bash— the secret is in the command args - Check the session JSONL — secrets embedded in bash tool_use entries
Root cause
The session environment file's contents are inlined directly into the bash -c command string rather than sourced from the file by path. So export SECRET=value becomes part of the argv visible to all users.
Suggested fix
Source the session-env file by path instead of inlining its contents:
# Current behavior:
bash -c 'source snapshot.sh && export SECRET=value && eval "user command"'
# Proposed:
bash -c 'source snapshot.sh && source /path/to/session-env.sh && eval "user command"'
This keeps the same behavior (env vars are available to the command) but:
ps auxshowssource /path/to/fileinstead of the secret values- Transcript logs the source command, not the secrets
- No behavior change for users
Additional issues
- Session-env files are created with
644permissions (world-readable). Should be600. - Session-env directories are never cleaned up. Over time, secrets accumulate across hundreds of session directories.
- The docs don't warn that
CLAUDE_ENV_FILEis not safe for secrets. The official hook examples show non-secret usage (PROJECT_TYPE=nodejs) but issue #20528 noted the docs previously hadexport API_KEY=your-api-keyas an example. Loading.envfiles is what env files are for — users will do this.
Impact
Any user who loads secrets via CLAUDE_ENV_FILE leaks them to:
- All users on the machine (via
ps aux) - Anyone with access to
~/.claude/(transcript + session-env files)
Environment
- Claude Code 2.1.91
- macOS (Darwin 24.4.0)
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗