Guide de Purge et de Nettoyage des Secrets dans l'Historique Git¶
Ce document décrit la procédure complète et éprouvée pour identifier, assainir et purger définitivement des secrets ou données sensibles (mots de passe, clés d'API, adresses e-mail) accidentellement committés dans l'historique d'un dépôt Git.
🎯 Contexte & Objectif¶
Lors du développement d'infrastructures IaC ou de stacks Docker, des clés secrètes ou informations personnelles peuvent s'infiltrer dans des révisions historiques. Supprimer simplement le fichier ou le texte dans un nouveau commit ne suffit pas : Git conserve l'intégralité des versions antérieures dans sa base d'objets.
L'objectif est d'effectuer une réécriture propre et exhaustive de l'historique Git tout en préservant l'intégrité de la structure des commits.
🔍 Étape 1 : Diagnostic et Localisation des Fuites¶
1. Lister les fichiers modifiés dans l'historique sous un dossier sensible¶
2. Rechercher des motifs de secrets spécifiques (Regex / String matching)¶
# Rechercher les commits contenant une chaîne spécifique (ex: email ou mot de passe)
git log -S "$(cat /tmp/motif-recherche)" --oneline
# Ne jamais écrire le secret recherché directement dans la ligne de commande :
# il serait enregistré dans l'historique du shell (~/.zsh_history, ~/.bash_history).
🛡️ Étape 2 : Sauvegarde de Sécurité (Obligatoire)¶
Avant toute opération de réécriture d'historique, créez toujours une branche ou une copie locale de secours :
# Créer une branche de sauvegarde locale
git branch backup-main-before-purge
# Optionnel : Faire un clone miroir complet du dépôt
git clone --mirror file://. ../docker-examples-backup.git
🧹 Étape 3 : Assainissement avec Script tree-filter¶
Nous utilisons git filter-branch avec un script Python (/tmp/sanitize_tree.py) exécuté à chaque commit réécrit.
1. Construction de la table de remplacement¶
git filter-repo attend un fichier de motifs, à raison d'une règle par ligne :
[!CAUTION] Ce fichier est un inventaire complet de vos secrets en clair. C'est le piège principal de toute la procédure, et il se referme sans bruit : on rédige la table, on purge, puis on documente la méthode « pour la prochaine fois » en y recopiant la table — et le commit de documentation réintroduit dans l'historique exactement ce que la purge venait d'en retirer.
Règles non négociables : - Le fichier vit dans
/tmp, jamais dans l'arborescence du dépôt. - Il est détruit dès la purge terminée (shred -uplutôt querm). - La documentation décrit la méthode, jamais les valeurs. - Si le dépôt publie un site (MkDocs, GitHub Pages), vérifier que la page générée ne contient pas non plus ces valeurs : le HTML publié est un canal d'exposition distinct de l'historique Git, et indexable par les moteurs de recherche.
Génération de la table à partir des fichiers de secrets non versionnés, sans jamais recopier une valeur à la main :
# Les valeurs proviennent de .secrets/, ignoré par git
for f in .secrets/*-password .secrets/*-token; do
printf 'literal:%s==>[REDACTED]\n' "$(cat "$f")"
done > /tmp/purge-rules.txt
chmod 600 /tmp/purge-rules.txt
2. Exécution du filtrage sur l'ensemble des révisions (HEAD)¶
git filter-branch est officiellement déconseillé par Git (lent, et sujet à des
corruptions silencieuses). L'outil recommandé est git-filter-repo :
# Sur TOUTES les références, pas seulement HEAD :
# une branche oubliée (gh-pages, une branche de feature) conserverait le secret.
git filter-repo --replace-text /tmp/purge-rules.txt --force
git filter-reporetire le remoteoriginaprès réécriture, volontairement, pour éviter unpushréflexe. Il faut le rétablir explicitement à l'étape 5.
🗑️ Étape 4 : Purge Complète des Objets Obsolètes & Reflog¶
git filter-branch conserve des copies de sauvegarde dans refs/original/. Tant que ces références existent, les anciens secrets restent présents dans l'annuaire d'objets Git local.
1. Supprimer les références de sauvegarde¶
# Supprimer les références originelles créées par filter-branch
git update-ref -d refs/original/refs/heads/main
rm -rf .git/refs/original/
2. Expirer le reflog et purger la mémoire d'objets orphelins (Garbage Collection)¶
# Expirer immédiatement le reflog
git reflog expire --expire=now --all
# Forcer la collecte des déchets et la compression agressive
git gc --prune=now --aggressive
🚀 Étape 5 : Force-Push vers le Dépôt Distant (GitHub)¶
Une fois l'historique local assaini et vérifié avec git log -S, mettez à jour le dépôt distant :
[!WARNING] Le
--force-pushréécrit l'historique sur le serveur distants. Tous les autres collaborateurs du dépôt devront ré-effectuer un clone propre ou effectuergit reset --hard origin/main.
Nettoyage post-vérification¶
# Supprimer la branche de backup une fois le succès confirmé
git branch -D backup-main-before-purge
# Détruire la table de motifs — elle contient les secrets en clair
shred -u /tmp/purge-rules.txt
# Le clone miroir de sauvegarde contient ENCORE les secrets : le détruire aussi
rm -rf ../docker-examples-backup.git
🛡️ Bonnes Pratiques Préventives¶
- Règles
.gitignorestrictes : - Scans automatisés avec TruffleHog / Gitleaks / Snyk :
- Fichiers de passpass séparés : Toujours stocker les valeurs sensibles brutes dans des fichiers non versionnés dans
.secrets/<service>-passwordet utiliser la fonction Ansiblelookup('file', ...)ou des variables d'environnement Docker.env.