
I recently had a concerning moment while using Claude Code. I typed /init to initialize the tool in my fresh project, and during development something unexpected happened - Claude Code attempted to read my .env file. My heart skipped a beat.
# What I saw
Claude Code is reviewing your .env file...
Why was this alarming?
Environment variables often contain:
- Database credentials
- API keys for third-party services
- Cloud provider secrets (AWS, GCP, Azure)
- Authentication tokens
Even if these are "just" dev or UAT environment secrets, exposure is still a serious security concern.
The Vulnerability History
My concern wasn't paranoid. Researching further, I discovered that Claude Code has had several security vulnerabilities:
- CVE-2026-25724: A symbolic link bypass that allowed reading restricted files
- Issue: Indirect Bash commands could still access files even with deny rules
-
Broken .claudeignore: The
.claudeignorefile, which was supposed to block file access like.gitignore, simply didn't work - Command injection vulnerabilities: Multiple ways to bypass write protection
The Solution: Defense in Depth
After reading through security advisories and community discussions, I implemented a multi-layered approach:
Layer 1: Claude Code's Built-in Permission System
The .claudeignore file was broken, but the newer settings.json permission system actually works:
// ~/.claude/settings.json
{
"permissions": {
"deny": [
"Read(**/.env*)",
"Read(**/env.php*)",
"Read(**/*.pem)",
"Read(**/*.key)",
"Read(**/secrets/**)",
"Read(**/credentials/**)",
"Read(**/.aws/**)",
"Read(**/.ssh/**)",
"Read(**/docker-compose*.yml)",
"Read(**/config/database.yml)"
]
}
}
This tells Claude Code: "Under no circumstances should you read these patterns."
But there's a catch: These rules are enforced by Claude Code itself. We're trusting the tool to honor our deny rules - the same tool that had bypass vulnerabilities. This is why we need more.
Layer 2: SOPS (Secrets OPerationS) - Encryption at Rest
SOPS (Secrets OPerationS) is Mozilla's tool for encrypting files while keeping them in version control. It's my second line of defense.
The beauty of SOPS: Even if Claude Code ignores deny rules, it only sees encrypted file.
Installation Guide
macOS:
brew install sops
Linux:
# Download latest release from https://github.com/getsops/sops/releases
wget https://github.com/getsops/sops/releases/download/v3.12.1/sops-v3.12.1.linux.amd64
sudo mv sops-v3.12.1.linux.amd64 /usr/local/bin/sops
sudo chmod +x /usr/local/bin/sops
Windows:
# Using Chocolatey
choco install sops
Setting Up Age Encryption (Simpler than GPG)
I use age for encryption because it's simpler than GPG:
### Install age
brew install age # macOS
# or
apt-get install age # Debian/Ubuntu
# Generate a key
mkdir -p ~/.config/sops/age
age-keygen -o ~/.config/sops/age/keys.txt
# Your public key (share this for encryption)
cat ~/.config/sops/age/keys.txt | grep "public key"
My ZSH Helper Functions (For Fast Commands)
Based on my workflow, here are the functions I added to my .zshrc or .bashrc:
nano ~/.zshrc or nano ~/.bashrc
# Encrypt a file (default: .env)
enc() {
local file="${1:-.env}"
if [ ! -f "$file" ]; then
echo "❌ File not found: $file"
return 1
fi
# Get public key from keys.txt
local pubkey=$(cat ~/.config/sops/age/keys.txt | grep "public key" | cut -d: -f2 | xargs)
sops encrypt --age "$pubkey" "$file" > "$file.encrypted" && mv "$file.encrypted" "$file"
echo "✅ Encrypted: $file"
}
# Edit an encrypted file (default: .env)
edit() {
local file="${1:-.env}"
if [ ! -f "$file" ]; then
echo "❌ File not found: $file"
return 1
fi
# SOPS automatically decrypts to temp, opens editor, and re-encrypts
sops edit "$file"
}
## View decrypted contents (default: .env)
view() {
local file="${1:-.env}"
if [ ! -f "$file" ]; then
echo "❌ File not found: $file"
return 1
fi
sops decrypt "$file"
}
## Check if file is encrypted
isenc() {
local file="${1:-.env}"
if [ ! -f "$file" ]; then
echo "❌ File not found: $file"
return 1
fi
if grep -q "ENC\[AES256" "$file"; then
echo "✅ Encrypted: $file"
else
echo "⚠️ Plain text: $file"
fi
}
Daily Development
# first encrypt your file first, only one time
enc .env
# Start your day - edit secrets
edit .env
# Check if a file is encrypted
isenc .env
# Output: ✅ Encrypted: .env
# Run your dev server with decrypted secrets
sops exec-env .env 'npm run dev'
# View current secrets (quick check)
view .env
Why This Multi-Layer Approach Works
- Layer 1 (Claude's deny rules): Stops accidental reads by a properly behaving Claude
- Layer 2 (SOPS encryption): Makes the data useless even if bypassed
- Layer 3 (Age encryption): Ensures only key holders can decrypt
- Layer 4 (Git): Encrypted files can be safely committed or ignored from git.
What This Protects Against
- ✅ Claude Code ignoring deny rules
- ✅ Accidental exposure in screenshots or copy-paste
- ✅ Repository leaks (encrypted files are useless without keys)
- ✅ Malicious actors with read access to your filesystem
Top comments (0)