Tips & Tricks

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

  • .env n’é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 ask configuré 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 -rf est 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

  • --force tend à ê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é.

CasCause RacinePrévention
Fuite .envPas de gitignoreScript init + Hook
Suppression BD productionPas de séparation d’environnements.env séparé + flux de confirmation
Accident rm -rfPas de liste de denyConfiguration settings.json
Fuite de cléÉcrite dans le promptStandardiser les variables d’environnement
Explosion de facturationPas de limite de retryUtilitaire withRetry
Force pushPas de configuration d’interdictiondeny + force-with-lease
Accès sur-privilégiéViolation du moindre privilègeRestreindre 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

Références

#claude-code #security #incident #best-practices #devops

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.

Gratuit

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.

Masa

À 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.