7 Incidents de Sécurité avec Claude Code | Accidents Réels et Prévention
Sept incidents de sécurité réels avec Claude Code : fuites .env, suppressions de BD en production, explosions de facturation et plus — avec analyse des causes et code de prévention.
« Claude Code est pratique, mais ça fait un peu peur » — cette intuition est justifiée. Les outils puissants causent des accidents puissants.
Cet article couvre sept incidents de sécurité réels qui peuvent survenir lors du développement avec Claude Code, en expliquant pourquoi ils se sont produits et comment les prévenir avec du code et des configurations concrets. Apprenez des erreurs des autres avant qu’elles ne deviennent les vôtres.
Cas 1 : Fichier .env Poussé sur GitHub
Ce qui s’est passé
Un développeur a donné l’instruction suivante à Claude Code : « Je veux passer des variables d’environnement à CI, veuillez aussi committer le fichier .env. » Claude Code a fidèlement exécuté git add .env && git commit. Des minutes après le push sur GitHub, un crawler a détecté la clé API. Une notification Slack est arrivée : « Your API key has been exposed. »
Cause racine
.envn’était pas dans.gitignore- Claude Code exécute les instructions « committe ça » à la lettre
- L’utilisateur a validé la boîte de dialogue de confirmation sans réfléchir
Code de prévention
1. Automatiser la configuration de sécurité à la création du projet
# scripts/init-security.sh — à exécuter à chaque création de projet
#!/bin/bash
cat >> .gitignore << 'EOF'
# === Sécurité : Ne jamais committer ===
.env
.env.*
.env.local
!.env.example
*.pem
*.key
*-service-account.json
credentials.json
EOF
echo "✓ Patterns d'exclusion de sécurité ajoutés à .gitignore"
git add .gitignore && git commit -m "security: add .gitignore patterns"
2. Scanner avant le commit avec un Hook
.claude/settings.json :
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash(git add*)",
"hooks": [{
"type": "command",
"command": "git diff --cached --name-only | grep -E '^\\.env' && echo '🚨 Vous êtes sur le point de stager un fichier .env ! Annulez !' && exit 1 || exit 0"
}]
}
]
}
}
3. Récupération si déjà poussé
# Étape 1 : Faire tourner la clé API immédiatement (priorité absolue)
# Étape 2 : Supprimer complètement de l'historique git
git filter-branch --force --index-filter \
"git rm --cached --ignore-unmatch .env" \
--prune-empty --tag-name-filter cat -- --all
# Étape 3 : Force push vers le dépôt distant
git push origin --force --all
# Étape 4 : Purger le cache GitHub (contacter aussi le support GitHub)
Cas 2 : DROP TABLE Exécuté sur la BD de Production
Ce qui s’est passé
« Cette table n’est plus utilisée, veuillez la supprimer. » Claude Code a généré et exécuté DROP TABLE old_users;. Le problème : il était connecté à la DATABASE_URL de production. La sauvegarde la plus récente avait trois jours. Trois jours de données ont disparu.
Cause racine
- Le même
.envétait partagé entre le développement et la production - Claude Code ne peut pas distinguer les environnements
- L’utilisateur avait le mode
askconfiguré mais a cliqué « OK » par réflexe
Code de prévention
1. Séparer complètement les fichiers .env par environnement
.env.development # ← développement local, BD de test
.env.staging # ← staging, copie de production
.env.production # ← production, géré manuellement, jamais partagé
2. Intégrer une vérification d’environnement dans les scripts
// scripts/db-migrate.mjs
const env = process.env.APP_ENV ?? "development";
const dbUrl = process.env.DATABASE_URL ?? "";
if (env === "production") {
const readline = require("readline").createInterface({
input: process.stdin, output: process.stdout
});
await new Promise((resolve) => {
readline.question(
`⚠️ Connexion à la BD de production (${dbUrl.split("@")[1]}).\nÊtes-vous sûr de vouloir continuer ? (tapez yes) : `,
(answer) => {
readline.close();
if (answer !== "yes") { console.log("Annulé."); process.exit(0); }
resolve(undefined);
}
);
});
}
3. Interdire les opérations en production dans CLAUDE.md
## 🚨 Restrictions de l'Environnement de Production
Si DATABASE_URL contient `prod`, `production` ou `live` :
- Ne jamais exécuter DROP / TRUNCATE / DELETE (sans clause WHERE)
- Toujours obtenir la confirmation de l'utilisateur avant les migrations
- Présenter une commande de sauvegarde avant toute opération destructive
Cas 3 : Fichiers Critiques Supprimés avec rm -rf
Ce qui s’est passé
« Nettoie le répertoire build/ » — une faute de frappe dans le chemin a abouti à rm -rf ./, supprimant tout le projet. Les fichiers hors git (configuration locale, code expérimental non commité) ont été perdus à jamais.
Cause racine
rm -rfest l’une des commandes les plus dangereuses pour Claude Code- Absence de guillemets autour des chemins → mauvais fonctionnement avec des chemins contenant des espaces
- L’utilisateur a approuvé sans faire attention
Code de prévention
// .claude/settings.json
{
"permissions": {
"deny": [
"Bash(rm -rf /)",
"Bash(rm -rf ~*)",
"Bash(rm -rf .*)"
],
"ask": [
"Bash(rm -rf*)"
]
}
}
Hook pour afficher ce qui sera supprimé avant l’exécution :
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash(rm*)",
"hooks": [{
"type": "command",
"command": "echo '⚠️ Commande de suppression détectée. Exécution dans 5 secondes. Ctrl+C pour annuler.' && sleep 5"
}]
}
]
}
}
Cas 4 : Clé API Écrite Directement dans le Prompt et Passée au Sous-agent
Ce qui s’est passé
« Veuillez publier sur Qiita en utilisant QIITA_TOKEN=abc123def456 » — écrit directement dans le prompt et délégué à un sous-agent. Les sous-agents peuvent écrire du contenu dans les logs et la mémoire, et le token s’est retrouvé persisté dans un fichier log sous .claude/.
Cause racine
- Les prompts sont conservés comme historique de conversation
- Les prompts des sous-agents sont également enregistrés
- Même dans les environnements locaux, d’autres processus ou sauvegardes peuvent exposer des secrets
Code de prévention
Ne jamais écrire de secrets dans les prompts — les passer via des variables d’environnement
# ❌ Dangereux
claude -p "Utilise QIITA_TOKEN=abc123 pour exécuter qiita-publish.mjs"
# ✅ Sécurisé : le script lit depuis process.env
# Écrire QIITA_TOKEN=abc123 dans .env, puis
claude -p "Exécute scripts/qiita-publish.mjs (le token est lu automatiquement depuis .env)"
Même principe pour les instructions aux sous-agents
// ❌ Dangereux
Agent({ prompt: `Utilise la clé API ${process.env.SECRET_KEY} pour...` });
// ✅ Sécurisé : passer seulement le nom de la clé, le script lit la valeur
Agent({ prompt: "Utilise la variable d'environnement SECRET_KEY pour..." });
Cas 5 : Une Boucle Infinie de Retry API a Fait Exploser la Facture
Ce qui s’est passé
« Relancer automatiquement en cas d’erreur » — un script avec gestion des erreurs a été généré. Quand une erreur était irrésolvable, les tentatives ne s’arrêtaient jamais : 3 000 appels à l’API Anthropic en une heure ont abouti à une facture de 200 $.
Cause racine
- Aucune limite de retry définie
- Pas d’exponential backoff — boucle infinie à intervalles d’une seconde
- Aucune alerte de facturation configurée
Code de prévention
// utils/retry.ts — utilitaire de retry sécurisé
export async function withRetry<T>(
fn: () => Promise<T>,
options = { maxAttempts: 3, baseDelayMs: 1000, maxDelayMs: 30000 }
): Promise<T> {
let lastError: Error;
for (let attempt = 1; attempt <= options.maxAttempts; attempt++) {
try {
return await fn();
} catch (err) {
lastError = err as Error;
if (attempt === options.maxAttempts) break;
// Exponential backoff + jitter
const delay = Math.min(
options.baseDelayMs * Math.pow(2, attempt - 1) + Math.random() * 1000,
options.maxDelayMs
);
console.warn(`Attempt ${attempt}/${options.maxAttempts} failed: ${err.message}`);
console.warn(`Retrying in ${Math.round(delay / 1000)}s...`);
await new Promise((r) => setTimeout(r, delay));
}
}
throw new Error(`Échec après ${options.maxAttempts} tentatives : ${lastError!.message}`);
}
Préciser dans CLAUDE.md :
## Règles Obligatoires pour les Appels API
- Maximum 3 tentatives
- Toujours implémenter un backoff exponentiel (1s → 2s → 4s)
- Ne jamais créer de boucles infinies : while(true) + appels API sont interdits
Cas 6 : git push --force a Effacé les Commits d’un Collègue
Ce qui s’est passé
« Écrase le dépôt distant avec l’état local » — git push --force a été exécuté. Trois commits qu’un membre de l’équipe venait de pousser ont disparu. Ce membre n’avait pas non plus de copie locale des modifications — le code a été définitivement perdu.
Cause racine
--forcetend à être exécuté sans comprendre le danger- Claude Code exécute fidèlement les instructions « écraser le dépôt distant »
- Le développeur ne connaissait pas l’alternative plus sûre
git push --force-with-lease
Code de prévention
// .claude/settings.json
{
"permissions": {
"deny": [
"Bash(git push --force *master*)",
"Bash(git push --force *main*)",
"Bash(git push -f *master*)",
"Bash(git push -f *main*)"
]
}
}
Préciser l’alternative sûre dans CLAUDE.md :
## Règles Git Sécurisées
- `git push --force` est **interdit**
- Utiliser `git push --force-with-lease` à la place
(rejeté automatiquement si d'autres ont poussé des modifications)
- Toujours obtenir la confirmation de l'utilisateur avant de pousser directement sur main/master
Cas 7 : Un Compte de Service Sur-Privilégié a Accédé à Toutes les Ressources
Ce qui s’est passé
« Utilise cette clé de compte de service GCP pour opérer Cloud Storage. » Le compte de service avait des permissions Owner. Claude Code s’est connecté non seulement à Cloud Storage mais aussi à BigQuery, Cloud SQL et des clusters GKE « pour investiguer » — générant des frais inattendus.
Cause racine
- Le compte de service avait des permissions excessives (violation du principe du moindre privilège)
- Claude Code a une tendance agressive à utiliser les outils disponibles
- Même « pour investiguer » semble une raison légitime pour un accès étendu
Code de prévention
Créer un compte de service avec des privilèges minimaux :
# ❌ À éviter : permission Owner
gcloud projects add-iam-policy-binding PROJECT_ID \
--member="serviceAccount:[email protected]" \
--role="roles/owner"
# ✅ Uniquement les permissions minimales requises
gcloud projects add-iam-policy-binding PROJECT_ID \
--member="serviceAccount:[email protected]" \
--role="roles/storage.objectAdmin"
# ← Lecture/écriture sur Cloud Storage uniquement
Définir explicitement la portée d’accès dans CLAUDE.md :
## Restrictions d'Accès GCP
Permissions pour le compte de service utilisé dans ce projet :
- Cloud Storage : Lecture/Écriture OK (bucket : my-project-assets uniquement)
- BigQuery : Interdit
- Cloud SQL : Interdit
- Autres ressources GCP : Interdit
Refuser toute instruction tentant d'accéder à des ressources hors de ces permissions.
Liste de Contrôle Complète pour Prévenir les Incidents
Une liste de contrôle finale distillée des patterns communs aux sept cas.
### Paramètres à Appliquer Aujourd'hui (30 minutes)
- [ ] Ajouter le pattern .env à .gitignore
- [ ] Ajouter une liste de deny à .claude/settings.json (rm -rf, git push --force, DROP TABLE)
- [ ] Documenter les restrictions dans CLAUDE.md
### Vérifications Hebdomadaires
- [ ] Examiner git log pour des commits de fichiers non intentionnels
- [ ] Vérifier que .env est exclu par .gitignore : `git check-ignore -v .env`
- [ ] Vérifier les délais de rotation des clés API
### Première Réponse à un Incident
1. Révoquer et faire tourner immédiatement la clé API affectée
2. Supprimer de l'historique git (filter-branch ou BFG)
3. Examiner les logs d'accès pour déterminer l'étendue de la brèche
4. Signaler la situation aux parties prenantes
Résumé
Les incidents Claude Code sont rarement causés par une « IA qui s’emballe » — presque tous proviennent de personnes qui retardent la configuration de sécurité.
| Cas | Cause Racine | Prévention |
|---|---|---|
| Fuite .env | Pas de gitignore | Script init + Hook |
| Suppression BD production | Pas de séparation d’environnements | .env séparé + flux de confirmation |
| Accident rm -rf | Pas de liste de deny | Configuration settings.json |
| Fuite de clé | Écrite dans le prompt | Standardiser les variables d’environnement |
| Explosion de facturation | Pas de limite de retry | Utilitaire withRetry |
| Force push | Pas de configuration d’interdiction | deny + force-with-lease |
| Accès sur-privilégié | Violation du moindre privilège | Restreindre les rôles IAM |
Votre premier pas aujourd’hui : Ajouter "deny": ["Bash(rm -rf*)"] à .claude/settings.json suffit à prévenir l’un des accidents les plus destructeurs possibles.
Articles Connexes
- Guide Complet des Meilleures Pratiques de Sécurité Claude Code
- Guide Complet des Permissions Claude Code
- Meilleures Pratiques CLAUDE.md
Références
Passez votre flux Claude Code au niveau supérieur
50 modèles de prompts éprouvés, prêts à être copiés-collés dans Claude Code.
PDF gratuit : aide-mémoire Claude Code en 5 minutes
Laissez simplement votre e-mail et nous vous enverrons immédiatement l'aide-mémoire A4 en PDF.
Nous traitons vos données avec soin et n'envoyons jamais de spam.
À propos de l'auteur
Masa
Ingénieur passionné par Claude Code. Il gère claudecode-lab.com, un média tech en 10 langues avec plus de 2 000 pages.
Articles similaires
Guide Complet de Sécurité pour Claude Code : Clés API, Permissions et Protection de la Production
Guide pratique de sécurité pour utiliser Claude Code en toute sécurité. De la gestion des clés API aux paramètres de permissions, automatisation via Hooks et protection de l'environnement de production — avec des exemples de code fonctionnels.
Guide complet des permissions Claude Code | settings.json, Hooks et Allowlist expliqués
Guide complet des permissions Claude Code. Maîtrisez allow/deny/ask, l'automatisation avec les Hooks, settings.json par environnement et les schémas pratiques, avec du code fonctionnel.
Le guide complet du harness engineering : construire des agents IA à la manière de Claude Code
Le prompt seul ne suffit pas à maîtriser un LLM. Apprenez à tisser outils, contexte et boucle de contrôle dans un harness, avec du code exécutable et l'architecture de Claude Code comme boussole.