Aller au contenu
English
Compte : non connecté, synchronisation possible Mon apprentissage
Selectionner le thème
English
Read in English

Secrets dans le code et l'historique Git

Par mis à jour le

Améliorer cette page

Un développeur commit "temporairement" un mot de passe pour tester. Il le retire dans le commit suivant. Problème : l'historique Git conserve tout. Trois ans plus tard, un attaquant scanne les dépôts publics et trouve ce secret toujours valide.

Mais Git n'est que le point de départ. Une fois poussé, le secret se réplique dans les clones, les forks, les runners CI, les artefacts, les sauvegardes et les systèmes d'analyse. Ce guide couvre les trois couches nécessaires : prévenir, détecter et réagir.

  • Pourquoi supprimer un commit ne suffit pas, et ce qui est vraiment irrémédiable
  • Comment l'écosystème autour de Git multiplie la surface de fuite
  • Les outils de détection (Gitleaks, TruffleHog) et la push protection
  • Les mesures de prévention (hooks, CI, forge)
  • Comment réagir et nettoyer l'historique sans se raconter d'histoires

Le hub de gestion des secrets situe ce guide parmi les autres : secrets statiques, cycle de vie, identité machine et coffres-forts.

Quand vous commitez un secret puis le supprimez :

Fenêtre de terminal
# Commit 1 : ajout du secret
git add .env
git commit -m "Add config"
# Commit 2 : suppression du secret
git rm .env
git commit -m "Remove config"

Le fichier n'apparaît plus dans HEAD, mais il existe toujours dans l'historique :

Fenêtre de terminal
# Le secret est toujours accessible
git show HEAD~1:.env
# DB_PASSWORD=SuperSecret123
Fenêtre de terminal
# Tentative de réécriture d'historique
git rebase -i HEAD~2
# Suppression du commit contenant le secret
git push --force

Un force push réécrit l'historique du dépôt distant, mais il n'efface pas le secret partout :

  • Les clones existants conservent l'ancien historique
  • Les forks conservent l'ancien historique
  • Les systèmes de backup conservent l'ancien historique
  • Les caches CI conservent potentiellement l'historique

Même après un rebase, le reflog local garde une trace :

Fenêtre de terminal
git reflog
# abc1234 HEAD@{0}: rebase: ...
# def5678 HEAD@{1}: commit: Add config # ← Le secret est encore là

Par défaut, Git conserve les entrées du reflog 90 jours, et 30 jours pour celles qui pointent vers des commits devenus inaccessibles, comme le commit retiré par un rebase (gc.reflogExpire et gc.reflogExpireUnreachable). Sur les machines des autres développeurs, l'ancien historique reste intact.

Un secret committé ne disparaît pas avec un simple git rm. L'historique Git est distribué : chaque clone, chaque fork conserve une copie complète. Mais le problème va bien au-delà du dépôt lui-même.

Une fois le secret poussé, il peut être répliqué dans :

  • les clones de développeurs (chacun a une copie complète)
  • les forks (y compris ceux d'ex-collaborateurs)
  • les runners CI/CD (qui clonent le dépôt à chaque pipeline)
  • les caches de build (Docker layers, cache npm, etc.)
  • les artefacts de pipeline (logs, rapports, archives)
  • les sauvegardes de la forge (GitHub, GitLab, Gitea)
  • les mirrors et répliques automatiques
  • les systèmes d'analyse branchés au dépôt (SonarQube, Snyk, etc.)
  • l'indexation temporaire par la forge (recherche GitHub, API events)
SurfacePersistanceNettoyable ?
Historique Git (dépôt d'origine)PermanenteOui, avec git-filter-repo
Clones développeursJusqu'au recloneNécessite coordination
ForksPermanenteNon, hors de votre contrôle
Runners CI/CDDurée du job (ou cache)Purge manuelle
Artefacts de pipelineSelon la rétentionSuppression manuelle
Mirrors / backups forgeVariableDépend de la plateforme
Indexation temporaireQuelques heuresAutomatique
Systèmes d'analyse tiersVariableDépend du service

La colonne Nettoyable ? distingue ce qui est irrémédiable (forks, clones externes) de ce que vous pouvez encore maîtriser.

Il faut distinguer deux types d'acteurs :

Détection défensive (pour vous protéger) :

  • GitHub Secret Scanning : détecte les patterns connus après le commit et alerte le mainteneur
  • GitHub Push Protection : bloque le push avant qu'il n'entre dans le dépôt (voir section dédiée)
  • Chercheurs en sécurité : scans avec TruffleHog ou Gitleaks

Exploitation opportuniste (pour vous attaquer) :

  • Bots malveillants : scannent les événements publics GitHub en temps réel et exploitent les clés trouvées automatiquement

Sur un dépôt public, certaines clés peuvent être détectées et exploitées très rapidement, parfois en quelques minutes.

Certains patterns de secrets sont détectés automatiquement par les scanners. Les bots malveillants ciblent en priorité les clés cloud car elles donnent un accès immédiat à des ressources payantes.

TypePatternRisque
AWS Access KeyAKIA... (20 caractères)Accès cloud complet
GitHub PATghp_...Accès aux dépôts
Stripe API Keysk_live_...Transactions financières
JWT SecretVariableForge de tokens
Private Key-----BEGIN RSA PRIVATE KEY-----Usurpation d'identité
Database URLpostgres://user:pass@hostAccès base de données
.npmrc / .pypirc_authToken=...Publication de paquets
docker/config.json"auth": "base64..."Push vers un registry
kubeconfigtoken: ou certificat clientAccès cluster Kubernetes
Terraform Cloud token*.atlasv1.*Gestion d'infrastructure
Certificat client-----BEGIN CERTIFICATE-----Authentification mTLS

Scénario 1 : la clé AWS exploitée en quelques minutes

Section intitulée « Scénario 1 : la clé AWS exploitée en quelques minutes »
T+0min : Développeur push un commit avec clé AWS
T+2min : Bot scanne le dépôt public via l'API GitHub
T+3min : Bot extrait la clé et lance des instances EC2 pour miner
T+5min : Facture AWS qui explose
T+1h : Développeur remarque le problème

Coût réel : des milliers d'euros de facturation cloud.

T-3ans : Secret committé pour un test
T-3ans : Secret supprimé dans le commit suivant
T : Attaquant scanne l'historique
T+1h : Secret toujours valide, accès prod compromis

Personne n'avait révoqué le secret : il avait seulement disparu du code courant. Un scan des fichiers présents, gitleaks dir, ne voit plus rien ; un scan de l'historique, gitleaks git, le retrouve dans le commit d'origine. C'est exactement ce que fait l'attaquant.

T : Dépôt privé avec secret dans l'historique
T : Employé quitte l'entreprise, garde son fork
T+6m : L'ex-employé a toujours accès au secret

Même après nettoyage de l'historique sur le dépôt d'origine, le fork conserve sa propre copie complète. C'est pourquoi la révocation du secret est la seule action fiable.

La prévention repose sur plusieurs couches complémentaires. Aucune n'est suffisante à elle seule.

Le schéma suit le trajet d'un commit, du poste à la branche principale : chaque barrière rattrape ce que la précédente a laissé passer, et la seule que l'on ne contourne pas depuis le poste est le scan CI bloquant.

Trajet d'un commit vers la branche principale : hook pre-commit Gitleaks sur le poste, push protection de la forge, puis scan CI bloquant sur la pull request. Un --no-verify, un merge ou une automatisation évitent le hook et arrivent directement à la push protection.évite le hookCommit surle posteHook pre-commit(Gitleaks)Push protectionde la forgeScan CI bloquantsur la PRBrancheprincipale--no-verify, merge,automatisation
Source · SVG
Échap pour fermer, + et − pour zoomer, défilement pour se déplacer. 100 %

Les hooks locaux bloquent le commit si un secret est détecté :

.pre-commit-config.yaml
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.30.1
hooks:
- id: gitleaks
Fenêtre de terminal
# Installation
pip install pre-commit
pre-commit install
# Test
git commit -m "Test"
# Gitleaks scanne automatiquement les fichiers modifiés

Deux pièges attendent la personne qui écrit son premier .gitleaks.toml, et tous deux ont été mesurés sur Gitleaks 8.22.1 et 8.30.1. Les chemins de l'allowlist sont des expressions régulières, pas des motifs de fichiers : un motif comme **/*_test.go fait planter Gitleaks au chargement (panic: regexp: Compile), et avec lui le hook et la CI. Surtout, un fichier qui déclare ses propres [[rules]] remplace les règles par défaut, sauf s'il porte [extend] useDefault = true : sans cette ligne, le jeton GitHub du dépôt de test n'était plus détecté, seule la règle maison restait active.

.gitleaks.toml
[extend]
useDefault = true # garder les règles par défaut en plus des vôtres
[allowlist]
paths = [
'''test/fixtures/''', # expressions régulières, pas des globs
'''_test\.go$''',
]
[[rules]]
id = "custom-api-key"
description = "Custom API Key"
regex = '''custom_api_key\s*=\s*['"]([^'"]+)['"]'''

GitHub propose une push protection qui scanne le contenu au moment du push et bloque l'envoi si un secret connu est détecté. C'est une brique de prévention très forte, différente du secret scanning a posteriori :

MécanismeQuandAction
Push protectionAu moment du pushBloque l'envoi
Secret scanningAprès le commitAlerte le mainteneur

Avantages de la push protection :

  • Agit avant que le secret n'entre dans le dépôt
  • Couvre les patterns connus (clés cloud, tokens de forges, etc.)
  • Complémentaire des hooks locaux (protège même sans pre-commit)

Limites :

  • Ne détecte que les patterns connus de la forge
  • Un développeur peut forcer le passage (avec justification)
  • Ne remplace pas les scans CI sur l'historique complet

Pour les organisations, GitHub permet aussi de définir des custom patterns pour le secret scanning et la push protection, adaptés aux formats de secrets internes.

# Secrets et configurations locales
.env
.env.*
*.key
*.pem
*.p12
*.jks
secrets/
credentials/
# Registres de paquets
.npmrc
.pypirc
# Docker
docker/config.json
# Kubernetes
kubeconfig
*.kubeconfig
# IDE
.idea/
.vscode/settings.json
# Logs qui peuvent contenir des secrets
*.log
logs/

Gitleaks est un scanner rapide basé sur des règles (regex). Il est particulièrement adapté aux hooks pre-commit et à l'intégration CI.

Fenêtre de terminal
# Scanner l'historique complet (tous les commits)
gitleaks git --verbose
# Scanner seulement les fichiers présents sur le disque
gitleaks dir --verbose

La différence entre les deux commandes est au cœur de ce guide. Sur un dépôt dont le secret a été retiré du code mais reste dans un commit antérieur, gitleaks dir ne trouve rien et gitleaks git le trouve : le secret « corrigé » n'a jamais quitté l'historique. Depuis la version 8.19, ces deux sous-commandes remplacent gitleaks detect, qui fonctionne encore mais n'est plus listée dans l'aide.

Points forts :

  • Rapide, scanne des dépôts volumineux en secondes
  • Patterns préconfigurés, couvre les formats les plus courants
  • Facile à intégrer, pre-commit, GitHub Actions, GitLab CI

TruffleHog va plus loin avec la vérification active : il teste si le secret trouvé est encore valide en tentant de l'utiliser. Cela permet de prioriser les vrais secrets actifs et d'écarter les faux positifs ou les secrets déjà révoqués.

Fenêtre de terminal
# Scanner avec vérification active
trufflehog git file://. --only-verified
# Sortie
Found verified result
Detector Type: AWS
Raw result: AKIAIOSFODNN7EXAMPLE
Commit: abc1234
Email: dev@company.com

Points forts :

  • Vérification active, distingue un secret exploitable d'un faux positif ou d'un secret déjà révoqué
  • Détection par entropie, repère les secrets qui ne suivent pas un pattern connu
  • Classification, catégorise les résultats par type et par risque

Le scan en CI est la dernière barrière avant que le code n'atteigne la branche principale. Il doit bloquer la PR si un secret est détecté.

name: Secret Scan
on: [push, pull_request]
permissions: {}
jobs:
gitleaks:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false
- uses: gitleaks/gitleaks-action@e0c47f4f8be36e29cdc102c57e68cb5cbf0e8d1e # v3.0.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Bonnes pratiques CI :

  • Pinner les actions par SHA, éviter les tags mutables
  • Scanner le diff sur les PR, rapide, cible les changements
  • Scanner l'historique complet périodiquement, attrape les secrets anciens
  • Bloquer la PR en cas de détection, pas de allow_failure: true

Trois outils tiennent ce rôle : Gitleaks, l'implémentation de référence de la formation, TruffleHog, qui vérifie si un secret trouvé est encore actif, et detect-secrets, qui travaille avec une baseline des faux positifs acceptés.

  1. Révoquer immédiatement

    Changez le secret avant de nettoyer l'historique. Un attaquant peut avoir déjà copié le secret.

  2. Évaluer l'impact

    Depuis quand le secret était-il exposé ? Quels accès donne-t-il ? Le dépôt est-il public ou privé ?

  3. Rechercher une utilisation malveillante

    Vérifiez les logs d'audit du service concerné (CloudTrail pour AWS, audit logs pour GitHub, etc.).

  4. Nettoyer l'historique

    Voir la section suivante sur git-filter-repo.

  5. Purger les artefacts

    • Supprimer les artefacts CI/CD contenant le secret
    • Vider les caches de build (Docker, npm, etc.)
    • Demander aux collaborateurs de recloner le dépôt
  6. Prévenir la récidive

    Installer les pre-commit hooks, les scans CI, et activer la push protection.

Nettoyer l'historique : ce que cela fait, et ce que cela ne fait pas

Section intitulée « Nettoyer l'historique : ce que cela fait, et ce que cela ne fait pas »

git-filter-repo est l'outil recommandé pour réécrire l'historique (il remplace l'obsolète git filter-branch).

Fenêtre de terminal
# Supprimer un fichier de tout l'historique
git filter-repo --invert-paths --path config/secrets.yml
# Remplacer un pattern dans tout l'historique
git filter-repo --replace-text expressions.txt

git-filter-repo retire le remote origin après la réécriture, pour empêcher un push accidentel : mesuré, git remote est vide après l'opération. Il faut donc le rajouter avant de pousser l'historique réécrit.

Fenêtre de terminal
# Rajouter le remote retiré par git-filter-repo
git remote add origin <url-du-depot>
# Force push sur toutes les branches
git push --force --all
git push --force --tags

Réécrire l'historique du dépôt d'origine est nécessaire, mais ne suffit pas :

SurfaceNettoyée automatiquement ?
Dépôt d'origineOui (après force push)
Clones des développeursNon, chacun doit recloner
ForksNon, hors de votre contrôle
Artefacts CI/CDNon, suppression manuelle
Caches de buildNon, purge manuelle
Backups de la forgeNon, dépend de la plateforme
Systèmes d'analyse tiersNon, dépend du service

Conséquence : le nettoyage d'historique est utile pour l'hygiène du dépôt, mais la révocation du secret reste la seule action vraiment fiable. Si un fork ou un clone externe a capturé le secret, aucune réécriture de votre côté ne l'effacera.

Les problèmes ci-dessous reviennent au moment de mettre en place les contrôles ou de nettoyer un dépôt. Trois d'entre eux ont été reproduits en conteneur : la configuration Gitleaks qui plante, celle qui éteint les règles par défaut, et le refus de git-filter-repo.

SymptômeCause probableSolution
Gitleaks détecte des faux positifsPattern trop large ou fichier de testAjouter le chemin, en expression régulière, dans [allowlist] du .gitleaks.toml
git-filter-repo refuse : « does not look like a fresh clone »Le dépôt n'est pas un clone frais : historique dans le reflog, ou fichiers modifiésTravailler sur un clone frais et propre (git clone --no-local), ou forcer avec --force en connaissance de cause
Gitleaks plante au démarrage : panic: regexp: CompileUn chemin de l'allowlist écrit comme un glob (**/*_test.go)Écrire les chemins en expressions régulières (_test\.go$)
Plus aucune alerte après l'ajout d'une règle maisonDes [[rules]] sans [extend] remplacent les règles par défautAjouter [extend] useDefault = true au .gitleaks.toml
Secret détecté mais déjà révoquéPas de filtrage par validitéUtiliser trufflehog --only-verified pour prioriser
Hook pre-commit contournéDéveloppeur a utilisé --no-verifyAjouter un scan CI bloquant comme filet de sécurité
Push protection contournéeDéveloppeur a forcé le passageVérifier les justifications dans les logs de la forge
Collaborateurs en erreur après force pushSHA des commits réécritsDemander à l'équipe de recloner le dépôt
  1. Dès qu'un secret atteint Git, il est compromis, la révocation est la seule action fiable
  2. Git n'est que le point de départ, le secret se propage dans les clones, forks, runners CI, artefacts et sauvegardes
  3. Révoquer avant de nettoyer, le nettoyage d'historique n'efface pas les copies externes
  4. Trois couches de prévention, pre-commit hooks + push protection + scan CI bloquant
  5. Aucune couche ne suffit seule, les hooks sont contournables, la push protection ne couvre que les patterns connus, la CI ne protège qu'après le push
  6. Le nettoyage d'historique a ses limites, git-filter-repo réécrit le dépôt, mais pas les forks ni les caches externes

Vérifiez que l'essentiel de ce guide est acquis. Les questions portent uniquement sur ce qui vient d'être expliqué ici.

Contrôle de connaissances

Validez vos connaissances avec ce quiz interactif

6 questions
6 min.
70% requis

Informations

  • Le chronomètre démarre au clic sur Démarrer
  • Questions à choix multiples, vrai/faux et réponses courtes
  • Vous pouvez naviguer entre les questions
  • Les résultats détaillés sont affichés à la fin

Lance le quiz et démarre le chronomètre

Ce projet vous est utile ?

Les guides, formations, quiz et labs restent gratuits. Si vous les utilisez régulièrement, vous pouvez contribuer à leur maintenance et à leur développement.

Devenir membre de soutien (nouvel onglet) Soutenir une fois (nouvel onglet) En savoir plus

Suivez aussi le projet sur LinkedIn