Shell snapshots base64-encode function bodies, making obfuscated content indistinguishable from a malicious payload
Summary
Claude Code's shell-snapshot files (at ~/.claude/shell-snapshots/snapshot-bash-<id>-<token>.sh) serialize captured shell functions by base64-encoding the function body and wrapping it in an eval "$(echo '<base64>' | base64 -d)" invocation.
This happens even for entirely benign functions captured from the user's interactive shell environment. Example — a literal line from one of my snapshot files:
eval "$(echo 'Z2F3a2xpYnBhdGhfYXBwZW5kICgpIAp7IAogICAgWyAteiAiJEFXS0xJQlBBVEgiIF0gJiYgQVdL
TElCUEFUSD1gZ2F3ayAnQkVHSU4ge3ByaW50IEVOVklST05bIkFXS0xJQlBBVEgiXX0nYAogIGV4cG9y
dCBBV0tMSUJQQVRIPSIkQVdLTElCUEFUSDokKiIKfQo=' | base64 -d)" > /dev/null 2>&1
That decodes to a perfectly ordinary function from the gawk Debian package's /etc/profile.d/gawk.sh:
gawklibpath_append ()
{
[ -z "$AWKLIBPATH" ] && AWKLIBPATH=`gawk 'BEGIN {print ENVIRON["AWKLIBPATH"]}'`
export AWKLIBPATH="$AWKLIBPATH:$*"
}
A single snapshot file on my system had 6 such blobs; I have ~10 snapshot files sitting in ~/.claude/shell-snapshots/.
Why this is a security concern
The obfuscation looks suspicious even when it isn't. A reviewer glancing at the snapshot cannot distinguish between:
- A legitimate shell function captured by Claude Code's snapshotter and re-serialized through base64, and
- A malicious payload injected by something Claude Code has sourced or read from the user's environment.
This matters because:
- A compromised entry in a directory the user considers "trusted" — an attacker-supplied
.bashrcfragment, a dotfile from a third-party setup script, adirenv-style file sourced at shell startup, an entry in/etc/profile.d/from a compromised package — could smuggle arbitrary code into the snapshot, and it would blend in perfectly with the existing base64 blobs. - A user who inspects the snapshot file (as users should be encouraged to do when evaluating the trust surface of any tool that loads their shell state) has no easy way to audit it. They have to base64-decode every blob individually to see what it actually executes on every subsequent Bash tool call.
- The format effectively trains the user to accept that Claude Code's internal files contain opaque obfuscated code, undermining one of the natural defenses against a supply-chain or environment-level attack. "This is normal for Claude Code" becomes a cover story for an attacker.
This is defense-in-depth: even if the snapshot content itself is never malicious today, the presence of opaque obfuscated code in a tool's own state directory degrades the user's ability to notice when something else has been compromised.
Suggested remediation
Preferred: store captured function bodies as plain shell text, matching what the user would see from declare -f directly, so a reviewer can read them in place. If certain edge cases (embedded single quotes, trailing newlines, unusual escapes) require careful quoting, prefer heredocs or explicit escaping rather than opaque base64 wrapping.
If base64 wrapping is necessary, please at minimum:
- Put a clear banner at the top of the snapshot file explaining that base64 is used for serialization of specific constructs, pointing the user at how to audit it, and listing the function names included so a user can spot-check by name without having to decode each blob.
- Produce a deterministic, diffable format — the current format is hard to diff across sessions, which also hurts audit.
Environment
- Claude Code running Opus 4.7 (1M context) on a Linux (Debian-family) host
- bash shell,
declare -fis the capture mechanism based on the surrounding snapshot file structure - Snapshot format observed:
# Snapshot fileheader,unalias -a, then manyeval "$(echo '<base64>' | base64 -d)" > /dev/null 2>&1lines
Attribution
This report was drafted by Claude (Opus 4.7) at the user's request during a Claude Code session, after the user flagged the obfuscated-looking content in ~/.claude/shell-snapshots/.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗