example-petstore.com

Domaine d’exemple · Pas un service réel · Les visites depuis un navigateur affichent cette page · Les requêtes API reçoivent 410 Gone

Guide · Sécurité

Supprimer un secret de l’historique Git

Une clé, un jeton ou un mot de passe s’est retrouvé dans un commit. Supprimer le fichier dans un nouveau commit ne sert à rien : l’ancien commit le contient toujours, dans chaque clone et chaque fork. Ce guide présente le bon ordre : d’abord révoquer, puis réécrire l’historique, nettoyer la plateforme d’hébergement et faire en sorte que cela ne se reproduise pas.

D’abord : révoquer le secret

Considérez un secret commité comme exposé, même dans un dépôt privé : les clones, les forks, les journaux de CI et les sauvegardes peuvent déjà en contenir une copie, et des scanners automatisés repèrent les nouveaux commits des dépôts publics en quelques minutes.

  1. Révoquez ou renouvelez la clé, le jeton ou le mot de passe auprès du service qui l’a émis.
  2. Placez le nouveau dans la configuration ou dans un gestionnaire de secrets, pas dans le code.
  3. Vérifiez dans les journaux du service qu’aucune utilisation inconnue n’apparaît.

Où révoquer les clés chez les principaux fournisseurs : Identifiants divulgués : que faire maintenant. La réécriture de l’historique vient ensuite, jamais à la place.

Réécrire l’historique ou non

Une fois révoqué, un secret ne donne plus accès à rien, et GitHub indique que cela peut suffire. Une réécriture reste utile lorsque :

  • le secret ne peut pas être révoqué, ou le fichier contient d’autres données sensibles, comme des données personnelles ou des dossiers clients ;
  • le dépôt est public ou le deviendra ;
  • vous voulez que les scanners cessent de signaler l’ancien secret.

Une réécriture modifie l’ID de chaque commit ultérieur. Les autres contributeurs doivent rebaser leur travail, les pull requests ouvertes peuvent perdre leurs commentaires de revue et les signatures de commit sont supprimées. Convenez d’un moment avec toutes les personnes concernées, et fusionnez ou fermez d’abord les pull requests ouvertes.

Trouver chaque occurrence

# Quels commits ont ajouté ou supprimé la chaîne ?
git log --all --oneline -S 'the-secret-value'

# Dans quels fichiers apparaissait-elle ?
git grep 'the-secret-value' $(git rev-list --all)

# Analyser tout l’historique à la recherche de formats de secrets connus
gitleaks git -v

Notez chaque chemin de fichier sous lequel le secret est apparu, y compris les anciens noms si le fichier a été déplacé ou renommé.

Réécrire avec git-filter-repo

git-filter-repo est l’outil recommandé par GitHub. Utilisez la version 2.47 ou ultérieure, qui propose l’option --sensitive-data-removal, et travaillez dans un clone neuf.

# Installation (ou passez par votre gestionnaire de paquets)
brew install git-filter-repo        # macOS
pip install git-filter-repo         # partout où Python est disponible

git clone https://github.com/YOUR-ORG/YOUR-REPO
cd YOUR-REPO

# Option 1 : supprimer un fichier entier de tout l’historique
git-filter-repo --sensitive-data-removal --invert-paths --path config/secrets.yml

# Option 2 : remplacer le secret partout où il apparaît
git-filter-repo --sensitive-data-removal --replace-text ../replacements.txt

Le fichier de remplacements contient une valeur par ligne. Par défaut, chaque correspondance devient ***REMOVED*** ; avec ==>, vous choisissez le remplacement, et regex: recherche un motif :

# ../replacements.txt (en dehors du dépôt)
sk_live_51Hx0000000000000000000000
AKIA0000000000000000==>AWS_ACCESS_KEY_ID_REMOVED
regex:password\s*=\s*"[^"]+"==>password = "REMOVED"

Vérifiez le résultat avec git log --all -S 'the-secret-value' : la commande ne doit rien afficher. Écrasez ensuite le dépôt distant :

git push --force --mirror origin

Une protection de branche qui bloque les force push doit être désactivée le temps de cette opération. Après le push, la réécriture ne peut plus être annulée.

Nettoyer GitHub

Après le force push, les anciens commits restent accessibles via les pull requests, les vues en cache et les forks.

  • Pull requests et vues en cache : contactez le support GitHub en indiquant le nombre de pull requests concernées (grep -c '^refs/pull/.*/head$' .git/filter-repo/changed-refs) et les « First Changed Commit(s) » affichés par git-filter-repo. GitHub n’intervient que lorsque le renouvellement de l’identifiant ne peut pas écarter le risque.
  • Forks : les commits des forks y restent. Demandez à leurs propriétaires de supprimer ou de nettoyer le fork ; GitHub ne communique pas leurs coordonnées.
  • Clones des collègues : chacun doit rebaser ses branches sur le nouvel historique, et non les fusionner. Une seule fusion suffit à faire revenir les anciens commits.

Sur GitLab, Bitbucket ou un serveur auto-hébergé, les étapes sont similaires ; consultez la documentation de la plateforme pour savoir comment elle supprime les anciens objets et les vues en cache.

Alternative : BFG

BFG Repo-Cleaner est un outil plus ancien, écrit en Java, encore largement utilisé. Il travaille sur un clone miroir et, par défaut, ne touche pas au dernier commit : retirez donc d’abord le secret de la version actuelle dans un commit normal.

git clone --mirror https://github.com/YOUR-ORG/YOUR-REPO.git
java -jar bfg.jar --replace-text replacements.txt YOUR-REPO.git
cd YOUR-REPO.git
git reflog expire --expire=now --all && git gc --prune=now --aggressive
git push

Éviter la prochaine fuite

  • Protection des push : sur GitHub, l’analyse des secrets avec protection des push bloque un push qui contient un format de secret connu avant qu’il n’atteigne le dépôt.
  • Une vérification avant chaque commit : exécutez gitleaks ou git-secrets comme hook pre-commit, et dans la CI pour ceux qui le contournent.
  • De la configuration, pas du code : lisez les secrets depuis des variables d’environnement ou un gestionnaire de secrets, et ajoutez .env et les fichiers similaires à .gitignore avant le premier commit.
  • Regarder avant de commiter : indexez les fichiers un par un et vérifiez git diff --cached, au lieu d’utiliser git add . ou git commit -a.

Cette clé a aussi été envoyée à une adresse fictive comme api.example-petstore.com ? Elle a alors également atteint un serveur que vous ne contrôlez pas : Configurer les clients API et les SDK.

Sources