Tu démarres une session Claude Code dans ton repo. Sans rien faire, Claude peut lire ton .env au moment où il indexe le projet. Tes clés Anthropic, Stripe, OpenAI, ta DATABASE_URL deviennent du contexte — donc potentiellement des logs côté provider.
Ajouter "ne lis pas mes .env" dans ton CLAUDE.md ne suffit pas. C'est une consigne, pas une contrainte. Sous pression (tâche complexe, contexte long, instruction ambiguë) le modèle peut l'ignorer. La seule protection fiable c'est une deny rule dans settings.json — appliquée par la CLI elle-même, avant que Claude ne voie le fichier.
Les 3 chemins de leak à connaître
- Lecture directe. Claude scanne ton repo, ouvre
.env, le contenu rentre dans la conversation. Le cas évident. - Sortie de commande. Claude lance
npm testoubun run dev. Une requête HTTP plante en logguantAuthorization: Bearer sk-.... Une connexion Postgres timeout en dumpant la connection string. Claude capture toute la sortie. Tes secrets sont dans la conversation, sans qu'il n'ait jamais ouvert un.env. - Grep / search. Claude grep une fonction et tombe sur un fichier de config qui contient des credentials. La sortie du grep inclut la ligne entière.
La majorité des protections en circulation ne couvrent que le chemin 1.
La fix qui marche : deny rules globales
Dans ~/.claude/settings.json (config globale, s'applique à tous tes projets) :
{
"permissions": {
"deny": [
"Read(**/.env*)",
"Read(**/*.pem)",
"Read(**/*.key)",
"Read(**/secrets/**)",
"Read(**/credentials/**)",
"Read(**/.aws/**)",
"Read(**/.ssh/**)",
"Read(**/.npmrc)",
"Read(**/.pypirc)",
"Write(**/.env*)",
"Write(**/.ssh/**)"
]
}
}
Ces règles bloquent l'accès en lecture avant que Claude ne voie le fichier. Le double-asterisk ** couvre tous les sous-dossiers, donc même si tu as un .env au fond de apps/web/, c'est protégé.
Tu peux ajouter une règle équivalente dans le settings.json du repo pour chaque projet client (utile si t'as des conventions spécifiques, comme un dossier vault/ qui contient des secrets).
Bloquer le leak via sortie de commande
Les deny rules ne couvrent pas la capture de sortie. Pour ça, utilise un .env.test avec des valeurs fictives :
# .env.test — safe à lire, safe à leak
STRIPE_SECRET_KEY=sk_test_dummy_key
DATABASE_URL=postgres://test:test@localhost:5432/testdb
OPENAI_API_KEY=sk-test-not-real
Pointe ton framework de test sur .env.test (Vitest, Jest, Playwright le font tous via une option). Quand Claude lance les tests, il voit des dummies, jamais les vrais secrets.
Filet de sécurité : pre-commit hook
Même avec les deny rules, un copier-coller malheureux peut envoyer un secret dans un commit. Dans .git/hooks/pre-commit :
#!/bin/bash
PATTERNS=('sk-ant-' 'sk-live-' 'sk_live_' 'ghp_' 'gho_' 'AKIA' 'xox[bpors]-' 'SG\.' 'BEGIN.*PRIVATE KEY')
for pattern in "${PATTERNS[@]}"; do
if git diff --cached | grep -qE "$pattern"; then
echo "BLOCKED: secret matching '$pattern' detected"
exit 1
fi
done
if git diff --cached --name-only | grep -qE '\.env$|id_rsa|\.pem$|\.key$'; then
echo "BLOCKED: sensitive file in staging"
exit 1
fi
Rend-le exécutable :
chmod +x .git/hooks/pre-commit
Tu peux aussi utiliser gitleaks en pre-commit — plus de patterns, plus à jour.
Checklist avant ta prochaine session
- Deny rules
.env*dans~/.claude/settings.json .env.testavec valeurs fictives pour les tests- Pre-commit hook qui scanne les patterns de secrets
.envdans.gitignore(évident, mais on vérifie)- Credentials de prod dans un vault, pas en plaintext
Si t'as les 5, t'es propre. Si tu démarres à zéro, copie-colle juste le bloc deny ci-dessus dans ton ~/.claude/settings.json — c'est la protection qui couvre 80% du risque en 10 lignes.