Traquer les secrets oubliés avec Gitleaks
- Comprendre pourquoi un secret (mot de passe, clé d'API, jeton) committé par erreur reste un danger, même après suppression.
- Utiliser Gitleaks pour retrouver les secrets cachés dans l'historique d'un dépôt Git.
- Savoir réagir à une fuite, puis bloquer les prochaines avant même qu'elles ne soient committées.
Des robots parcourent GitHub en permanence, à la recherche de clés d'API et de mots de passe. Une clé publiée par erreur peut être repérée et exploitée en quelques minutes, parfois avec des factures de plusieurs milliers d'euros à la clé pour son propriétaire. Cette séance vous apprend à ne pas être cette personne-là.
Petite vérification
Avant de commencer, vérifiez si vous savez encore...
- dans votre dépôt
bilan-stage, vérifier avec une commande quenotes_perso.txtn'est pas suivi par Git (il ne doit pas apparaître dans la sortie) - afficher l'historique de
bilan-stageavec un commit par ligne, sous forme de graphe, pour toutes les branches - expliquer la différence entre
git rm --cached notes_perso.txtet la suppression du fichier avec l'explorateur de fichiers
📌 Réponse attendue
Notions théoriques
Qu'est-ce qu'un secret ?
Un secret est une information qui donne un accès à un service ou à des données. Celui qui la connaît peut agir à la place de son propriétaire.
| Type de secret | Exemple | Ce qu'un attaquant peut faire |
|---|---|---|
| Mot de passe de base de données | $db_password = '...' | Lire, modifier ou effacer toutes les données |
| Clé d'API cloud | AWS_SECRET_ACCESS_KEY=... | Créer des serveurs (payants !), lire des sauvegardes |
| Jeton d'accès GitHub | ghp_... | Lire ou modifier vos dépôts privés |
| Clé privée SSH | id_ed25519 | Se connecter à vos serveurs, sans mot de passe |
Pourquoi Git aggrave le problème
Git n'oublie rien : c'est sa raison d'être. Un fichier supprimé dans un commit récent reste présent dans les commits plus anciens.
commit 1 Initialise le site
commit 2 Ajoute config.php (contient le mot de passe) <- secret ici
commit 3 Retire le mot de passe de config.php <- le fichier est propre...
... mais commit 2 existe toujours
Quiconque clone le dépôt peut remonter jusqu'au commit 2 et lire le mot de passe. Et sur GitHub, les copies (clones, forks) se multiplient : impossible de « rappeler » un secret une fois publié.
Règle d'or : un secret qui a été committé doit être considéré comme compromis, même si le commit n'a jamais quitté votre ordinateur, même s'il a été « corrigé » juste après. La seule vraie solution est de le révoquer (en générer un nouveau et invalider l'ancien).
Gitleaks, le détecteur de secrets
Gitleaks est un outil libre, en ligne de commande, qui analyse un dépôt Git (et tout son historique) à la recherche de secrets. Il reconnaît :
- des formats connus (une clé AWS commence par
AKIA, un jeton GitHub parghp_, etc.) ; - des chaînes suspectes : une variable nommée
passwordouapi_keyqui contient un texte « aléatoire » (on mesure son entropie, c'est-à-dire son désordre).
Gitleaks n'est pas infaillible. Il peut signaler à tort une chaîne inoffensive (un faux positif) ou manquer un secret à la forme inhabituelle (un faux négatif). C'est un filet de sécurité, pas une garantie.
Ce cours utilise Gitleaks 8.30. Si vous lisez d'anciens tutoriels qui utilisent
gitleaks detect ou gitleaks protect, ces commandes ont été remplacées par gitleaks git et
gitleaks dir.
Installer Gitleaks
- Windows : sur la page Releases de Gitleaks,
téléchargez le fichier
gitleaks_..._windows_x64.zip, extrayezgitleaks.exedans le dossierbinde votre répertoire personnel (créez-le s'il n'existe pas :~/bindans Git Bash), puis rouvrez Git Bash. - macOS :
brew install gitleaks
- Debian / Ubuntu :
curl -sSL -o gitleaks.tar.gz https://github.com/gitleaks/gitleaks/releases/download/v8.30.1/gitleaks_8.30.1_linux_x64.tar.gz
tar xzf gitleaks.tar.gz gitleaks
sudo mv gitleaks /usr/local/bin/
Vérifiez l'installation avec :
gitleaks version
Les commandes à connaître
| Commande | Ce qu'elle analyse |
|---|---|
gitleaks git | Tout l'historique du dépôt courant, commit par commit |
gitleaks dir . | Les fichiers actuellement présents dans le dossier, sans regarder l'historique |
gitleaks git --pre-commit --staged | Seulement les modifications prêtes à être committées |
Quelques options utiles, combinables avec les commandes précédentes :
| Option | Effet |
|---|---|
-v (ou --verbose) | Affiche le détail de chaque fuite trouvée (sans elle, seul le total est affiché) |
--redact | Masque les secrets dans l'affichage et dans les rapports |
-r fichier -f json | Écrit un rapport au format JSON dans fichier |
--no-banner | Supprime le logo d'en-tête |
Gitleaks renvoie un code de sortie : 0 si rien n'a été trouvé, 1 si au moins une fuite a été
détectée. C'est ce qui lui permet de bloquer un commit ou une publication automatiquement.
Lire une fuite détectée
Avec l'option -v, chaque fuite ressemble à ceci :
Finding: api_key = "k8Jd92LsPq03MxVbN7tRw4YhZc15AeUo"
Secret: k8Jd92LsPq03MxVbN7tRw4YhZc15AeUo
RuleID: generic-api-key
Entropy: 5.000000
File: config.txt
Line: 1
Commit: 3f2a9c1e0b7d4a58c6e91d2f4b8a7c05e1d3f9a6
Author: Camille Durand
Date: 2026-10-20T07:40:31Z
Fingerprint: 3f2a9c1e0b7d4a58c6e91d2f4b8a7c05e1d3f9a6:config.txt:generic-api-key:1
| Champ | Signification |
|---|---|
RuleID | La règle qui a déclenché l'alerte (type de secret supposé) |
File et Line | Où se trouve le secret |
Commit et Author | Dans quel commit il a été introduit, et par qui |
Entropy | Le « désordre » du texte (plus c'est élevé, plus il ressemble à un vrai secret) |
Fingerprint | L'empreinte unique de cette fuite (commit:fichier:règle:ligne) |
Que faire quand un secret a fuité ?
Dans cet ordre :
- Révoquer le secret : changer le mot de passe, supprimer la clé d'API et en créer une autre. C'est l'action la plus urgente, avant toute autre.
- Retirer le secret du code : le lire depuis une variable d'environnement ou depuis un fichier
.env, lui-même exclu du suivi par.gitignore. - Nettoyer l'historique, si nécessaire : réécrire les anciens commits est une opération avancée
(outil
git filter-repo) qui dépasse ce cours, et qui ne dispense jamais de l'étape 1.
Gérer les faux positifs et les fuites déjà connues
Pour dire à Gitleaks d'ignorer une fuite, on écrit son empreinte (Fingerprint) dans un fichier
.gitleaksignore, à la racine du dépôt, à raison d'une empreinte par ligne :
3f2a9c1e0b7d4a58c6e91d2f4b8a7c05e1d3f9a6:config.txt:generic-api-key:1
Ignorer une fuite ne la supprime pas : on dit seulement à Gitleaks de ne plus s'en plaindre. Ne l'utilisez que pour un faux positif (une donnée fictive), ou pour un vrai secret déjà révoqué.
Bloquer les fuites avant qu'elles n'arrivent
Le mieux est d'empêcher le commit fautif. Git permet d'exécuter un script automatiquement avant
chaque commit : le hook pre-commit, fichier .git/hooks/pre-commit. Si ce script se termine
avec un code d'erreur, Git annule le commit.
#!/bin/sh
gitleaks git --pre-commit --staged --redact --verbose
Un hook reste local à votre ordinateur : chacun doit l'installer, et git commit --no-verify permet
de le contourner. Pour une protection solide, on ajoute aussi une vérification sur le serveur
(par exemple avec GitHub Actions et l'action gitleaks/gitleaks-action), ainsi que la
protection native de GitHub, nommée push protection, qui refuse certains secrets connus à l'envoi.
Exemple pratique
Créons un mini-dépôt, avec un secret volontairement oublié dedans (la valeur est fictive) :
mkdir demo-fuite
cd demo-fuite
git init
printf 'api_key = "k8Jd92LsPq03MxVbN7tRw4YhZc15AeUo"\n' > config.txt
git add config.txt
git commit -m "Ajoute la configuration"
Lançons l'analyse de l'historique, avec le détail :
gitleaks git -v
Gitleaks affiche la fuite (règle generic-api-key, fichier config.txt, ligne 1), puis un résumé :
INF 1 commits scanned.
WRN leaks found: 1
Vérifions maintenant le comportement face à un « nettoyage » :
printf 'api_key = "A_REMPLIR"\n' > config.txt
git commit -am "Retire la clé"
gitleaks dir .
gitleaks git
La commande gitleaks dir . répond no leaks found (le fichier actuel est propre), alors que
gitleaks git signale toujours 1 fuite, dans le premier commit.
Retenez cette différence : dir regarde l'état actuel, git regarde toute l'histoire. Pour
traquer un secret oublié, c'est presque toujours gitleaks git qu'il faut lancer.
Test de mémorisation/compréhension
TP pour réfléchir et résoudre des problèmes
Mise en situation : l'alerte aux Cyclos du Bocage
Vous effectuez un stage dans l'association Les Cyclos du Bocage (sorties vélo du dimanche). Lundi matin, la présidente vous accueille avec une mine inquiète : le fournisseur de stockage en ligne de l'association vient de lui envoyer un message d'« activité inhabituelle sur votre compte ». Le site a été développé par Camille Durand, le stagiaire de l'an dernier, et personne ne sait s'il s'est bien débarrassé de ses mots de passe.
Votre mission : mener l'enquête sur le dépôt Git du site, gérer la crise, puis blinder le dépôt pour que cela ne se reproduise jamais. Le TP se déroule en quatre phases :
| Phase | Étapes | Objectif |
|---|---|---|
| 1. Enquête | 1 à 7 | Installer l'outil, détecter les fuites, comprendre pourquoi elles sont toujours là |
| 2. Gestion de crise | 8 à 10 | Planifier la réaction, trier les vraies fuites des fausses, produire un rapport |
| 3. Blindage | 11 à 13 | Bloquer les futures fuites avec un hook, un .gitignore et une règle personnalisée |
| 4. Bilan | 14 | Vérifier que le dépôt est propre |
Le dépôt de ce TP contient de fausses clés, fabriquées pour l'occasion. Ne le poussez jamais sur GitHub : tout le TP se déroule en local.
Étape 1 — Installer Gitleaks
Suivez la section « Installer Gitleaks » de cette séance pour votre système, puis vérifiez que l'outil répond dans votre terminal (Git Bash sous Windows). Notez la version affichée.
Les options des outils évoluent (gitleaks detect est devenu gitleaks git). Quand une commande
trouvée en ligne ne marche pas, la première vérification à faire est la version installée.
Étape 2 — Récupérer le dépôt du stagiaire
Le dépôt de Camille n'est plus disponible : vous allez le reconstituer avec un script. Dans
Documents/, créez un fichier piege.sh contenant exactement ceci (copier-coller) :
#!/bin/bash
# Fabrique le dépôt piégé "cyclos-bocage" (6 commits, quelques secrets oubliés dans l'historique)
set -e
rm -rf cyclos-bocage
mkdir cyclos-bocage
cd cyclos-bocage
git init -q -b main
# Auteur fictif des commits (valable uniquement pour ce script)
export GIT_AUTHOR_NAME="Camille Durand" GIT_AUTHOR_EMAIL="camille.durand@cyclos-bocage.fr"
export GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME" GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"
# Les préfixes sont séparés du reste pour que ce script lui-même ne déclenche aucune alerte
AWS="AKIA"
GH="ghp_"
# Commit 1
cat > index.php <<'PHP'
<?php
echo "<h1>Les Cyclos du Bocage</h1>";
echo "<p>Inscriptions aux sorties du dimanche.</p>";
PHP
printf '# Les Cyclos du Bocage\n\nSite d inscription aux sorties velo.\n' > README.md
git add .
git commit -q -m "Initialise le site des Cyclos du Bocage"
# Commit 2
cat > config.php <<'PHP'
<?php
$db_host = 'localhost';
$db_name = 'cyclos';
$db_user = 'cyclos_admin';
$db_password = 'Zq7vR2mXk9TpL4wB';
PHP
git add .
git commit -q -m "Ajoute la connexion à la base de données"
# Commit 3
printf '#!/bin/sh\nexport AWS_ACCESS_KEY_ID=%sQ3EGRTYUWMNB5XKD\nexport AWS_SECRET_ACCESS_KEY=h3Fk9Qw2LmZx7VbN4tRy8UcJ6sPdA1eGoHiK5nXq\naws s3 sync ./sorties s3://cyclos-bocage-sauvegardes\n' "$AWS" > backup.sh
git add .
git commit -q -m "Ajoute la sauvegarde automatique"
# Commit 4
printf '#!/bin/sh\nTOKEN=%sRk8Wq2Zx7LmV4tNb9YcJ3sPdA6eGoHi5nXuT\ngit clone https://$TOKEN@github.com/cyclos-bocage/site.git /var/www/site\n' "$GH" > deploy.sh
git add .
git commit -q -m "Ajoute le script de déploiement"
# Commit 5 : le "nettoyage" des secrets
rm deploy.sh
cat > config.php <<'PHP'
<?php
$db_host = 'localhost';
$db_name = 'cyclos';
$db_user = 'cyclos_admin';
$db_password = getenv('DB_PASSWORD');
PHP
printf '#!/bin/sh\naws s3 sync ./sorties s3://cyclos-bocage-sauvegardes\n' > backup.sh
git add -A
git commit -q -m "Retire les mots de passe du code"
# Commit 6
mkdir tests
cat > tests/exemple_paiement.txt <<'TXT'
# Jeu d'essai fictif pour les tests automatiques
api_key = "k8Jd92LsPq03MxVbN7tRw4YhZc15AeUo"
TXT
git add .
git commit -q -m "Ajoute un jeu d'essai pour les tests"
echo "Dépôt cyclos-bocage créé."
Exécutez ce script depuis Documents/, puis placez-vous dans le dépôt qu'il vient de créer.
Un script qui fabrique un dépôt de test permet de recommencer à zéro autant de fois que
nécessaire. C'est une technique très utilisée par les développeurs pour reproduire un bug ou
préparer une démonstration. Vous pouvez relancer bash piege.sh pour tout reprendre au départ.
Étape 3 — Premier scan : combien de fuites ?
Depuis le dossier cyclos-bocage, lancez l'analyse de tout l'historique, sans option particulière.
Notez le nombre de commits analysés et le nombre de fuites annoncées.
Dans un terminal, echo $? affiche le code de sortie de la dernière commande. Après ce scan, vous
obtenez 1 : c'est ce code non nul qu'un hook ou un robot d'automatisation utilise pour bloquer une
action. Après un scan propre, vous obtiendriez 0.
Étape 4 — Voir le détail des fuites
Relancez l'analyse en demandant le détail de chaque fuite, sans afficher les secrets en clair (vous êtes peut-être en train de partager votre écran en classe).
Une capture d'écran, un fichier de logs ou un rapport envoyé par e-mail peuvent eux-mêmes devenir une
fuite. Prenez l'habitude d'utiliser --redact dès que vous n'avez pas besoin de lire la valeur du
secret.
Étape 5 — Mener l'enquête
Pour chacune des 5 fuites, relevez dans le résultat de l'étape 4 : le fichier, la règle
(RuleID) et le hash du commit. Retrouvez ensuite le message de ce commit en le cherchant dans
l'historique.
Complétez ce tableau de bord de l'enquête, puis comparez-le avec la réponse attendue :
| Fichier | Règle | Message du commit | Auteur |
|---|---|---|---|
| ... | ... | ... | ... |
📌 Réponse attendue
Retrouver l'origine d'une fuite est bien plus rapide quand les messages de commit sont clairs. L'historique est aussi un outil d'enquête : une bonne habitude de commit profite à toute l'équipe.
Étape 6 — Le piège : dir contre git
Camille a écrit dans le commit « Retire les mots de passe du code » qu'il avait tout nettoyé.
Vérifiez-le : affichez le contenu actuel de config.php et backup.sh (avec cat), puis analysez
uniquement les fichiers présents dans le dossier, sans l'historique.
gitleaks dir répond à « mon code actuel est-il propre ? » et gitleaks git répond à « a-t-on
déjà publié un secret un jour ? ». Pour un audit, la seconde question est la plus importante.
Étape 7 — Prouver que le secret est toujours là
Pour convaincre la présidente que « supprimer le fichier » ne suffit pas, retrouvez le jeton GitHub
du fichier deploy.sh supprimé. Ce fichier a été ajouté il y a 2 commits avant le dernier : Git sait
afficher le contenu d'un fichier tel qu'il était dans un commit donné, avec la notation
commit:chemin/du/fichier.
Vous venez de le démontrer vous-même : une suppression n'efface rien. C'est pour cette raison que la première réaction à une fuite est toujours de révoquer le secret, pas de nettoyer le code.
Étape 8 — Le plan de crise
La présidente vous demande un plan d'action, à lui présenter en 3 actions dans l'ordre pour les 4 vraies fuites. Vous disposez de ces ingrédients, à classer :
- A. mettre le mot de passe et les clés dans un fichier
.env, exclu de Git - B. changer le mot de passe de la base, supprimer les clés AWS, révoquer le jeton GitHub
- C. installer un contrôle automatique pour que cela n'arrive plus
📌 Réponse attendue
Réécrire l'historique prend du temps et peut échouer. Révoquer un secret prend quelques minutes et le rend inutile. Dans une vraie fuite, c'est donc toujours la première action, et elle est indépendante de tout ce qui se passe ensuite dans Git.
Étape 9 — Écarter le faux positif
Dans la suite du TP, on considère que les 4 vraies fuites ont été révoquées (étape 8, action B).
Il reste le faux positif du fichier tests/exemple_paiement.txt. Déclarez-le à Gitleaks :
- Relancez
gitleaks git -vet copiez la ligneFingerprint:de la fuite concernanttests/exemple_paiement.txt(uniquement la valeur, sans le motFingerprint:). - Écrivez cette empreinte dans le fichier
.gitleaksignore, à la racine du dépôt. - Relancez
gitleaks git: il ne doit plus annoncer que 4 fuites.
Le fichier .gitleaksignore doit être committé et relu comme du code. Chaque ligne y est une
exception à la règle : mieux vaut en avoir peu, et savoir pourquoi chacune est là.
Étape 10 — Produire un rapport
La présidente souhaite un rapport écrit à transmettre à son assureur. Générez-le au format JSON dans
le fichier rapport.json, avec les secrets masqués, puis comptez les fuites qu'il contient avec
grep -c '"RuleID"' rapport.json.
Sans --redact, rapport.json contiendrait les secrets en clair, et il aurait suffi de le committer
ou de l'envoyer par e-mail pour reproduire la fuite. Même avec --redact, ne versionnez pas ce genre
de fichier généré : nous l'exclurons de Git à l'étape 12.
Étape 11 — Installer le hook anti-fuite
Place au blindage. Créez le fichier .git/hooks/pre-commit et rendez-le exécutable, puis testez-le en
essayant de committer un fichier .env qui contient un mot de passe.
- Créez le hook :
printf '#!/bin/sh\ngitleaks git --pre-commit --staged --redact --verbose\n' > .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit
- Créez un fichier
.envavec un nouveau mot de passe de base de données (fictif) :
printf 'DB_PASSWORD=Nv4kT8pQ2sWz6HyE\n' > .env
- Tentez de le committer. Le commit doit être refusé.
Un secret bloqué avant le commit ne laisse aucune trace dans l'historique : il n'y a rien à révoquer ni à nettoyer. Plus la détection est tôt dans la chaîne, moins la fuite coûte cher.
Étape 12 — Ranger le secret au bon endroit
Le fichier .env est l'endroit normal pour un secret, à condition qu'il reste hors de Git.
- Retirez
.envde la zone de préparation (sans le supprimer du disque). - Créez un
.gitignorequi contient deux lignes :.envetrapport.json. - Committez
.gitignoreet.gitleaksignore: le hook doit laisser passer ce commit.
Le bon réflexe est d'écrire .env dans .gitignore avant de créer le fichier. On fournit aussi
souvent un fichier .env.example (sans les vraies valeurs) pour indiquer aux collègues quelles
variables définir.
Étape 13 — Une règle maison
L'association utilise un format de jeton d'adhérent à elle : CYCLOS- suivi de 12 majuscules ou
chiffres (par exemple CYCLOS-7QK29XMB41ZD). Gitleaks ne connaît pas ce format, mais on peut le lui
apprendre dans un fichier .gitleaks.toml à la racine du dépôt :
[extend]
useDefault = true
[[rules]]
id = "cyclos-adherent-token"
description = "Jeton d'adhérent des Cyclos du Bocage"
regex = '''CYCLOS-[A-Z0-9]{12}'''
keywords = ["cyclos-"]
Créez ensuite le fichier .gitleaks.toml, puis testez la nouvelle règle :
printf 'jeton = CYCLOS-7QK29XMB41ZD\n' > adherent.txt
git add .gitleaks.toml adherent.txt
git commit -m "Ajoute un jeton adhérent"
Le hook doit refuser le commit, avec la règle cyclos-adherent-token. Annulez alors l'ajout du
jeton, supprimez adherent.txt, puis committez la règle seule :
git restore --staged adherent.txt
rm adherent.txt
git commit -m "Ajoute la règle maison Gitleaks"
Un identifiant interne, un numéro de contrat, un format de jeton maison : aucun outil généraliste ne les connaît. Écrire une règle personnalisée est souvent la seule façon de protéger ce qui est spécifique à une organisation.
Étape 14 — Bilan : le dépôt est-il propre ?
Dernière vérification. Les 4 vraies fuites sont encore dans l'historique, mais leurs secrets sont
révoqués : on peut donc les déclarer, pour que Gitleaks ne signale plus que les nouvelles fuites.
Ajoutez à .gitleaksignore toutes les empreintes du rapport de l'étape 10, puis relancez l'analyse
complète :
grep -o '"Fingerprint": *"[^"]*"' rapport.json | cut -d'"' -f4 >> .gitleaksignore
Un dépôt est propre quand gitleaks git renvoie le code 0. Intégré à un hook, puis à une
vérification automatique sur le serveur, ce résultat devient une garantie permanente et non une
vérification ponctuelle.
📌 Une solution
Pour aller plus loin : le robot de GitHub
Le hook protège votre ordinateur, mais pas celui d'un collègue qui l'a oublié. Sur un dépôt GitHub,
on ajoute donc une vérification automatique à chaque envoi, avec GitHub Actions : créez le fichier
.github/workflows/gitleaks.yml dans le dépôt.
name: gitleaks
on: [push, pull_request]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
L'option fetch-depth: 0 demande à GitHub de récupérer tout l'historique, sans quoi Gitleaks ne
verrait que le dernier commit. Cette action est gratuite pour un compte personnel ; pour une
organisation, elle demande une licence (variable GITLEAKS_LICENSE).
Une fois ce fichier poussé sur GitHub, chaque git push et chaque Pull Request déclenchent
l'analyse. Une fuite détectée fait échouer la vérification, ce qui signale immédiatement le problème
aux relecteurs de la Pull Request.