🛡️ Sécurité

Secrets et .env : ne jamais les commiter (et purger l'historique si c'est déjà fait)

Clés API, mots de passe BDD, tokens Stripe, credentials SMTP : un seul commit maladroit suffit à tout faire fuiter. Comment protéger ses secrets en amont, scanner avant de pousser, et purger l'historique git quand le mal est déjà fait.

intermédiaire ⏱ 15 min Mise à jour : 2026-04-24

Un mot de passe de base de données dans wp-config.php, une clé Stripe live dans un .env, un token d'API mail dans un fichier de config : le jour où ça part dans un commit, c'est fuité pour de bon. Git garde tout, les scrappers indexent les dépôts publics en quelques minutes, et même un dépôt privé ne vous protège pas d'un contributeur qui clone puis part. Cette doc couvre la prévention (ce qu'on ne commit jamais, comment le verrouiller), la détection (scanner avant push), et la procédure de purge quand la bêtise est déjà faite.

Le problème

Ce qu'on retrouve régulièrement dans des dépôts d'agences pendant les audits :

  • Un wp-config.php avec les identifiants MySQL de production.
  • Un .env complet avec clés Stripe sk_live_, Mailjet, SendGrid, AWS.
  • Des config/parameters.yml Symfony avec la clé secrète d'appli.
  • Des clés SSH privées (id_rsa) oubliées dans un dossier deploy/.
  • Des .htpasswd avec le hash bcrypt du mot de passe admin.

Une fois poussé sur GitHub ou GitLab public, considérez que c'est dans la nature. Les bots parcourent les commits en continu, GitHub Search indexe le contenu des fichiers, et des services entiers (TruffleHog, Gitleaks cloud) vivent de ça. Même sur un dépôt privé, un secret dans l'historique reste récupérable par tout collaborateur passé ou présent.

🔥
Un secret commité est un secret fuité
Peu importe que vous supprimiez le fichier dans le commit suivant, que vous forciez un git rm, que le dépôt soit privé : le secret est dans l'historique, indexable, clonable. La seule réaction correcte est de le révoquer et le rotate immédiatement. La purge de l'historique vient après, pour limiter les dégâts futurs, pas pour « annuler » la fuite.

1. Le .gitignore de base

Premier rempart, le plus bête et le plus efficace. Posez ça à la racine de n'importe quel projet, avant le premier commit :

# Secrets et variables d'environnement
.env
.env.local
.env.*.local
.env.production
.env.staging

# Configs applicatives locales
wp-config.php
config/local.php
config/parameters.yml
config/database.yml
app/etc/env.php

# Clés et certificats
*.pem
*.key
*.p12
*.pfx
id_rsa
id_rsa.pub
id_ed25519
*.crt

# Auth HTTP
.htpasswd

# Dossier secrets divers
secrets/
private/
credentials/

# Dumps BDD et backups
*.sql
*.sql.gz
*.dump
backup/
backups/

# Artefacts de build
node_modules/
vendor/
.cache/

Le pattern à adopter : un .env.example committé, avec toutes les clés mais vides, qui sert de documentation. Chaque environnement (local, staging, prod) a son propre .env non versionné.

# .env.example (committé)
DB_HOST=
DB_NAME=
DB_USER=
DB_PASSWORD=

STRIPE_PUBLIC_KEY=
STRIPE_SECRET_KEY=

SMTP_HOST=
SMTP_USER=
SMTP_PASSWORD=
Si le fichier est déjà tracké, .gitignore ne suffit pas
.gitignore empêche Git de suivre les nouveaux fichiers, mais il n'a aucun effet sur ce qui est déjà dans l'index. Si wp-config.php est déjà versionné, il faut git rm --cached wp-config.php puis commit, et le fichier cesse d'être suivi sans être supprimé du disque. Mais attention : l'historique garde tout. Voir la section purge plus bas.

2. Scanner avant de pousser

Le .gitignore protège des oublis les plus grossiers. Mais il ne vous sauvera pas si un dev colle une clé API en dur dans du PHP. D'où les scanners automatisés, déclenchés avant chaque commit.

Gitleaks en hook pre-commit

Gitleaks est un binaire Go, rapide, avec des regex pour plus de 150 types de secrets connus. Installation puis intégration via le framework pre-commit :

# Installation du framework (une fois, par machine de dev)
pip install pre-commit
# ou
brew install pre-commit

Fichier .pre-commit-config.yaml à la racine du projet :

repos:
  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.21.2
    hooks:
      - id: gitleaks

  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v5.0.0
    hooks:
      - id: check-added-large-files
        args: ['--maxkb=1000']
      - id: detect-private-key
      - id: detect-aws-credentials
        args: ['--allow-missing-credentials']

Puis on installe le hook dans le dépôt local :

cd mon-projet/
pre-commit install

À partir de ce moment, tout git commit passe par le scanner. Si Gitleaks repère une clé, le commit est bloqué avec un rapport clair.

Option sans framework : git-secrets d'AWS

Plus léger, pas de dépendance Python, parfait pour une machine de dev minimaliste :

brew install git-secrets        # macOS
# ou: apt install git-secrets   # Debian/Ubuntu

cd mon-projet/
git secrets --install
git secrets --register-aws
git secrets --add 'sk_live_[0-9a-zA-Z]{24,}'     # Stripe
git secrets --add 'xox[baprs]-[0-9a-zA-Z]{10,}'  # Slack tokens

Chez Datacampus : détection côté GitLab

Notre instance gitlab.datacampus.fr embarque la Secret Detection native GitLab. Activée par défaut pour les projets des agences hébergées chez nous. Concrètement :

  • À chaque push, un job de détection tourne dans le pipeline CI.
  • Les secrets repérés remontent dans l'onglet Security & Compliance du projet.
  • Des push rules peuvent être configurées pour bloquer purement et simplement un push qui contient un pattern sensible (disponible sur demande côté Datacampus, ouvrez un ticket sur le ServiceDesk).

C'est une deuxième ligne de défense utile, mais ça ne dispense pas du hook local : une fois le secret arrivé sur GitLab, même bloqué par la push rule, il a transité par le réseau et se retrouve parfois dans les logs serveur. L'idéal reste d'intercepter avant le push.

3. Procédure si un secret a déjà été commité

Scénario classique : un dev s'aperçoit qu'il a poussé un .env sur la branche main la veille. Voici l'ordre d'action, strictement.

Étape 1 — Révoquer, immédiatement

Avant même de toucher à l'historique git. Le secret est considéré comme fuité.

  • Mot de passe BDD : changer via Plesk ou en SQL (ALTER USER ... IDENTIFIED BY ...), puis mettre à jour le .env de prod et staging.
  • Clé Stripe : dashboard Stripe > Développeurs > Clés API > Roll. La nouvelle clé est disponible immédiatement, l'ancienne reste active 12h par défaut (à désactiver tout de suite si vous êtes sûr d'avoir tout migré).
  • Token SMTP / Mailjet / SendGrid : interface du fournisseur, supprimer l'ancienne clé, en créer une nouvelle.
  • Clé SSH privée : supprimer la clé publique correspondante de tous les ~/.ssh/authorized_keys des serveurs concernés, regénérer une nouvelle paire.
  • AWS access key : IAM > Utilisateur > Credentials > Make inactive, puis Delete.
  • Password admin WordPress / Plesk : changer, vérifier les sessions actives, forcer la déconnexion globale.

Étape 2 — Auditer les accès récents

Sur les 24 à 48h qui suivent le commit, il faut vérifier si la clé a été utilisée. Les endroits à regarder :

  • Logs d'accès BDD : connexions depuis des IP inhabituelles, requêtes massives de SELECT sur des tables sensibles.
  • Dashboard du fournisseur d'API (Stripe, Mailjet…) : onglet activité, volume d'appels, erreurs d'authentification, appels depuis des IP non familières.
  • Logs SSH : lastlog, /var/log/auth.log, tentatives avec la clé compromise.
  • Logs applicatifs : connexions admin, créations de comptes, modifications suspectes.

Si vous repérez une utilisation anormale, passez en mode incident : voir Site piraté : que faire.

Étape 3 — Purger l'historique git avec BFG

Une fois le secret révoqué, on nettoie le dépôt pour éviter qu'un clone futur ne récupère la clé (même morte, elle donne des indices sur votre stack). BFG Repo-Cleaner est largement plus simple que git filter-branch.

# Cloner le dépôt en mirror (toutes les branches, tous les refs)
git clone --mirror git@gitlab.datacampus.fr:agence/projet.git
cd projet.git

# Créer le fichier des secrets à remplacer (un par ligne)
cat > ../passwords.txt <<'EOF'
monSuperSecretDB123
sk_live_51H7xK2abcdef1234567890
xoxb-slack-token-leaked
EOF

# Lancer BFG
bfg --replace-text ../passwords.txt

# Nettoyer le reflog et garbage collect agressif
git reflog expire --expire=now --all
git gc --prune=now --aggressive

# Force-push vers le remote
git push --force

BFG remplace chaque occurrence par ***REMOVED*** dans tous les commits. Variante pour supprimer un fichier entier de l'historique (par exemple .env qui n'aurait jamais dû exister) :

bfg --delete-files .env
git reflog expire --expire=now --all
git gc --prune=now --aggressive
git push --force
🔥
Force-push = tous les contributeurs doivent re-cloner
Après la purge, l'historique du remote n'est plus compatible avec les clones locaux des autres devs. Prévenez toute l'équipe avant le force-push, demandez-leur de stash leurs modifs en cours, et après le push ils doivent supprimer leur dossier local et re-cloner. Un simple git pull ne suffit pas et peut réintroduire le secret via un merge foireux.

Étape 4 — Nettoyer les miroirs et forks

  • Si le dépôt a des forks sur GitLab, ils conservent l'ancien historique. Il faut les purger un par un ou les supprimer.
  • Les caches GitHub/GitLab peuvent garder des blobs accessibles par SHA pendant quelques jours. Un ticket auprès du support peut forcer le nettoyage immédiat.
  • Vérifiez les artefacts CI, les releases, les pages de build : ils peuvent contenir des copies du secret hors de l'historique git principal.

4. Rotation régulière, sans attendre l'incident

Même sans fuite avérée, on ne garde pas un secret en vie éternellement :

  • Mots de passe BDD : rotation annuelle minimum, à inscrire dans le calendrier maintenance de l'agence.
  • Clés API : à chaque départ de collaborateur, toutes les clés auxquelles il avait accès doivent être rotées. Sans exception.
  • Tokens SSH : une paire par poste de dev, jamais de clé partagée. Retrait immédiat de la clé publique des serveurs quand un poste est remplacé.
  • Tokens CI : rotation tous les 6 à 12 mois, avec révocation de l'ancien 24h après déploiement du nouveau.

5. Où stocker les secrets pour l'équipe

Les secrets ne vivent pas dans un Slack, pas dans un mail, pas dans un Google Doc. Options, du plus simple au plus robuste :

  • 1Password (shared vault) : le plus simple pour une agence. Un vault par client, partage granulaire, intégration CLI (op read) pour injecter en CI.
  • Bitwarden Organizations : self-hostable (Vaultwarden) si vous préférez que les secrets restent sur votre infra. Datacampus héberge des instances Vaultwarden pour plusieurs agences.
  • HashiCorp Vault : pour les équipes qui ont besoin de secrets dynamiques (credentials BDD générés à la volée, rotation automatique). Plus lourd à opérer, réservé aux projets qui en ont vraiment le besoin.

6. Cas particulier Plesk chez Datacampus

Sur l'infra Datacampus, vos sites tournent sous Plesk + Apache event + PHP-FPM. Quelques règles spécifiques :

  • wp-config.php : il vit à la racine de httpdocs/, hors du dépôt git. Le déploiement par git n'écrase pas le wp-config.php du serveur (grâce à un .gitignore ou à un déploiement sélectif). Chaque environnement a le sien, local à la machine.
  • Variables d'env Plesk : interface Plesk > Paramètres PHP > Variables d'environnement. C'est lisible par PHP via getenv() ou $_ENV, et ça ne transite jamais par git. Bon compromis pour les clés qui changent rarement.
  • Fichier .env hors racine web : si vous tenez à un .env, posez-le dans le home de l'abonnement Plesk, pas dans httpdocs/. Exemple : /var/www/vhosts/exemple.fr/private/.env, lu par PHP avec un chemin absolu. Inaccessible depuis le web, hors git.
  • Clés SSH de déploiement : une clé par agence, par projet. Jamais la clé perso d'un dev dans le authorized_keys du compte Plesk. Voir Déployer via git.

Récapitulatif

  1. .gitignore costaud dès le premier commit : .env, *.key, wp-config.php, secrets/.
  2. Pattern .env.example versionné, .env jamais.
  3. Hook pre-commit avec Gitleaks ou git-secrets sur chaque poste de dev.
  4. Secret Detection activée côté gitlab.datacampus.fr, en filet de sécurité.
  5. Si fuite : révoquer d'abord, auditer les accès, purger avec BFG ensuite.
  6. Force-push = prévenir l'équipe, tout le monde re-clone.
  7. Rotation annuelle des passwords, à chaque départ pour les clés API.
  8. Stockage partagé dans 1Password, Bitwarden ou Vault, jamais dans un chan Slack.

Pour aller plus loin

Besoin d'aide ?

Cette documentation ne couvre pas votre cas ? Notre support humain est là.