Gitleaks est un scanner open source qui cherche des secrets (clés d’API, jetons, clés privées, mots de passe) dans un dépôt git, commit par commit. Lancé en intégration continue sur tout l’historique, il fait échouer le build dès qu’un secret apparaît dans n’importe quel commit, y compris un commit ancien dont le fichier a disparu depuis. Il ne remplace pas la rotation : un secret détecté après avoir été poussé doit être considéré comme compromis.
Que détecte Gitleaks ?
Gitleaks détecte des chaînes qui ressemblent à des secrets, à partir d’un jeu de règles fondées sur des expressions régulières, des mots-clés et un seuil d’entropie. Ses règles par défaut couvrent les formats reconnaissables des grands fournisseurs (jetons GitHub, clés de services cloud, clés privées PEM, webhooks de messageries) et une règle générique, generic-api-key, qui repère une valeur à forte entropie placée à côté d’un mot comme key, secret ou token.
Deux conséquences pratiques en découlent :
- un secret au format propriétaire inconnu des règles passe inaperçu, sauf s’il déclenche la règle générique ;
- la règle générique produit des faux positifs, typiquement sur des identifiants qui contiennent le mot « token » sans en être un.
Gitleaks ne vérifie pas qu’un secret est valide auprès de son émetteur. Il signale une forme, pas un risque confirmé : la qualification reste humaine.
Pourquoi scanner tout l’historique plutôt que le diff ?
Parce qu’un secret supprimé dans le dernier commit reste lisible dans les commits précédents, et que quiconque clone le dépôt récupère l’historique entier. Scanner uniquement le diff d’une pull request attrape les nouveaux secrets, mais ne dit rien de ceux qui sont déjà dans le dépôt.
Le piège le plus courant n’est pas Gitleaks lui-même, c’est le checkout. Par défaut, actions/checkout ne récupère que le dernier commit. Gitleaks lit l’historique via git log : sur un clone superficiel, il ne voit qu’un seul commit, et le contrôle paraît vert sans avoir rien examiné. L’option fetch-depth: 0 récupère l’historique complet.
| Mode | Ce qui est lu | Ce qu’il attrape | Ce qu’il manque |
|---|---|---|---|
| Diff de la PR | commits de la branche | nouveau secret introduit | secrets déjà présents |
| Historique complet | tous les commits accessibles | tout secret jamais commité | secrets jamais commités |
| Répertoire sans git | fichiers de l’arbre courant | secret dans un fichier présent | tout ce qui n’est plus dans l’arbre |
Pour limiter un scan à une plage de commits, Gitleaks accepte des options transmises à git log via --log-opts. C’est utile sur un très gros dépôt, mais un scan complet périodique reste nécessaire pour couvrir l’existant.
Pre-commit ou CI : où lancer Gitleaks ?
Les deux ont un rôle différent, et seule la CI fait foi.
Un hook pre-commit intercepte le secret avant qu’il n’entre dans l’historique, ce qui évite toute rotation. Le projet fournit un hook pour le framework pre-commit :
# .pre-commit-config.yaml
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.30.1
hooks:
- id: gitleaks
Mais un hook local se contourne (git commit --no-verify), dépend de l’installation de chaque poste et ne s’applique pas aux commits arrivés par une autre voie. La CI, elle, s’exécute sur chaque push et chaque pull request, quel que soit le poste d’origine. Le hook réduit le nombre d’incidents ; la CI garantit qu’aucun n’est ignoré.
Comment configurer Gitleaks et ses exceptions ?
La configuration vit dans un fichier .gitleaks.toml versionné à la racine du dépôt. La pratique la plus sûre consiste à étendre les règles officielles plutôt qu’à les réécrire, puis à ajouter des exceptions ciblées, chacune justifiée par un commentaire :
[extend]
useDefault = true
[allowlist]
description = "Faux positifs connus, un par motif"
paths = [
# Sommaires de navigation : des identifiants de pages, pas des secrets.
'''apps/[a-z-]+/sidebars\.js''',
]
Gitleaks propose plusieurs niveaux d’exception, du plus large au plus étroit :
pathsdans l’allowlist : ignore des fichiers entiers, à réserver à des fichiers sans aucune donnée sensible possible ;regexesoustopwords: ignore des valeurs précises ;- un fichier
.gitleaksignorecontenant les empreintes de résultats individuels ; - un commentaire
gitleaks:allowsur la ligne concernée.
Plus l’exception est étroite, moins elle risque de masquer un vrai secret ajouté plus tard au même endroit. Une exception de chemin peut aussi survivre au fichier qu’elle vise : si le scan couvre l’historique, le fichier supprimé y figure toujours et l’exception reste nécessaire.
Que faire quand un secret a fuité ?
Révoquer le secret chez son émetteur, d’abord. Réécrire l’historique ne suffit pas : une fois poussé, le secret a pu être copié par un clone, un fork, un cache de CI, un journal de build ou un outil d’indexation, et aucune réécriture n’atteint ces copies.
L’ordre des gestes :
- Révoquer et renouveler la clé, le jeton ou le mot de passe auprès du service qui l’a émis.
- Vérifier l’usage du secret compromis dans les journaux de ce service, sur la période d’exposition.
- Sortir le secret du code : le remplacer par une variable d’environnement ou un fichier de secrets hors dépôt.
- Réécrire l’historique si nécessaire (avec
git filter-repopar exemple), en sachant que cette étape réduit l’exposition future sans annuler l’exposition passée.
L’option --redact de Gitleaks évite d’aggraver la fuite : elle masque la valeur trouvée dans la sortie, ce qui compte quand les journaux de CI sont lisibles par d’autres personnes que l’auteur du commit.
Comment saas-core l’intègre
saas-core lance Gitleaks dans le job ci de .github/workflows/ci.yml, à chaque push sur main, à chaque pull request et sur déclenchement manuel. L’étape gitleaks secret scan exécute :
./gitleaks detect --source . --config .gitleaks.toml --redact -v
Les choix visibles dans le dépôt :
- Historique complet : le checkout du job
ciutilisefetch-depth: 0. Le commentaire du workflow indique qu’avant ce réglage, Gitleaks ne scannait qu’un commit sur plus de 200. - Binaire officiel, version figée :
GITLEAKS_VERSION=8.30.1, téléchargé depuis les releases GitHub du projet plutôt que viagitleaks-action, qui exige une licence pour une organisation. L’archive est choisie selon l’architecture du runner (x64 ou arm64) et vérifiée avant extraction contre une somme SHA-256 écrite dans le workflow, et non lue à côté de l’archive. Le testscripts/ci-supply-chain.test.mjscontrôle ce point pour les téléchargements des workflows. - Règles officielles conservées :
.gitleaks.tomldéclareuseDefault = trueet une seule exception de chemin, commentée, pour d’anciens sommaires de navigation qui n’existent plus dans l’arbre mais restent dans l’historique. - Valeurs masquées :
--redactdans la commande.
Ce qui est bloquant : un résultat Gitleaks met le job ci en échec. Le job livrer de .github/workflows/release-images.yml lit la conclusion de ci (et de e2e) sur le commit étiqueté et refuse la livraison si elle est en échec.
Ce qui n’est pas en place : le hook .githooks/pre-commit du dépôt ne lance que typecheck et check:css, pas Gitleaks. La fiche de contrôle C45 de la documentation de sécurité précise aussi que le scan ne couvre que le dépôt de code, et qu’un secret déjà présent dans l’historique reste exposé tant qu’il n’est pas renouvelé chez son émetteur.
Pour aller plus loin
Gitleaks ne lit que des secrets ; il ne cherche pas les défauts de sécurité dans le code lui-même. Ce rôle revient à une analyse statique, décrite dans Semgrep en CI : analyse statique du code TypeScript. La place de ces contrôles dans l’ensemble de la chaîne est détaillée dans le pipeline CI/CD GitHub Actions d’un SaaS.
Le détail de ce que le kit saas-core fournit, workflows de sécurité compris, figure sur la page tarifs.