DEV Community

Cover image for Claude Code Has Been Reading Your Database Password This Whole Time
Sophea
Sophea

Posted on

Claude Code Has Been Reading Your Database Password This Whole Time


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...
Enter fullscreen mode Exit fullscreen mode

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 .claudeignore file, 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)"
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Windows:

# Using Chocolatey
choco install sops
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Why This Multi-Layer Approach Works

  1. Layer 1 (Claude's deny rules): Stops accidental reads by a properly behaving Claude
  2. Layer 2 (SOPS encryption): Makes the data useless even if bypassed
  3. Layer 3 (Age encryption): Ensures only key holders can decrypt
  4. 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)