Aller au contenu
CI/CD & Automatisation medium

Secrets Scanning : détecter les secrets exposés dans le code

18 min de lecture

Un couple d'AK/SK committé par erreur. Une clé API dans un fichier .env.example. Un token GitLab dans les logs de CI. Ces scénarios ne sont pas hypothétiques : GitGuardian a recensé 28,65 millions de nouveaux secrets en dur ajoutés sur GitHub public en 2025, soit 34 % de plus qu'en 2024.

Le secrets scanning n'est pas une option. C'est une ligne de défense obligatoire dans toute usine logicielle.

  • Distinguer un vrai secret du bruit (hashes, UUID, fixtures de test)
  • Placer les contrôles aux trois niveaux : pre-commit, merge request, branche
  • Choisir entre GitLab Secret Detection, Gitleaks et TruffleHog
  • Configurer une allowlist Gitleaks valide sans neutraliser le scanner
  • Trier et remédier une alerte : qualification, rotation, traçabilité

Le raisonnement tient en deux temps. D'abord, un secret ne fuit pas par accident mais par des chemins identifiables, donc instrumentables. Ensuite, l'objectif réaliste n'est pas d'empêcher toute fuite, mais de raccourcir le temps d'exposition entre le moment où le secret part et le moment où il est révoqué. Le même rapport GitGuardian mesure l'ampleur du problème inverse : en janvier 2026, 64 % des secrets valides détectés en 2022 étaient toujours actifs. Détecter ne sert à rien sans procédure de rotation.

Les secrets fuient par des chemins prévisibles :

VecteurExempleDétection possible
Commit directpassword = "admin123" dans le code✅ Scanning repo
Fichier exemple.env.example avec vraies valeurs✅ Scanning repo
Historique GitSecret supprimé mais présent dans l'historique✅ Scan historique
Logs CI/CDToken affiché dans les logs de build⚠️ Masquage variables
ArtefactsCredentials dans un binaire ou config packagée⚠️ Scan artefacts
MR/PRSecret dans une diff avant merge✅ Scan MR

L'enjeu n'est pas d'atteindre le "zéro secret exposé" (utopique), mais de :

  1. Détecter au plus tôt, Avant le merge, idéalement avant le push
  2. Standardiser la réponse, Procédure claire de rotation/révocation
  3. Tracer les incidents, Pour améliorer la prévention

Ce qu'on appelle "secret" (et ce qu'on n'appelle pas secret)

Section intitulée « Ce qu'on appelle "secret" (et ce qu'on n'appelle pas secret) »

Définir le périmètre avant d'installer un scanner évite le scénario classique où l'équipe désactive l'outil au bout de deux semaines. Un scanner détecte des motifs, pas des secrets : c'est vous qui décidez ce qui compte comme credential exploitable et ce qui relève du bruit acceptable.

Ces quatre familles se distinguent par leur procédure de rotation, pas par leur format. Un token API se révoque en une requête, une clé privée TLS impose de réémettre un certificat et de le redéployer, un mot de passe de base de données oblige à coordonner l'application et le service. Classer un secret dans la bonne famille dès la détection détermine donc le délai de remédiation à annoncer.

Tokens API

GitHub PAT, GitLab tokens, tokens cloud (AWS, GCP, Azure)

Credentials

Mots de passe, strings de connexion DB, credentials SMTP

Clés cryptographiques

Clés privées SSH/TLS, clés de chiffrement, JWT secrets

Webhooks & URLs

URLs avec tokens intégrés, webhooks Slack/Discord

Tous les patterns détectés ne sont pas des secrets :

  • UUIDs, Identifiants, pas des secrets
  • Hashes, SHA256 d'un fichier, pas un mot de passe
  • Fixtures de test, Fausses clés pour les tests unitaires
  • Documentation, Exemples avec xxx ou REPLACE_ME

Une stratégie efficace gère ce bruit au lieu de le subir.

Où placer les contrôles (défense en profondeur)

Section intitulée « Où placer les contrôles (défense en profondeur) »

La détection des secrets doit intervenir à plusieurs niveaux :

  1. Avant le push (client-side)

    Pre-commit hooks avec Gitleaks. Bloque le commit avant qu'il n'atteigne le serveur.

    ✅ Feedback immédiat pour le développeur ⚠️ Contournable (hooks désactivables)

  2. Dans la Merge Request

    Scan automatique sur chaque MR. Feedback visible dans la diff.

    ✅ Point de contrôle centralisé ✅ Impossible à contourner ⚠️ Détection "tardive" (après commit)

  3. Sur la branche par défaut

    Scan post-merge comme filet de sécurité.

    ✅ Couverture complète ⚠️ Réaction après le fait

Recommandation : combiner les trois niveaux. Le pre-commit accélère le feedback, la MR garantit le contrôle, le scan de branche assure la couverture.

Trois outils dominent le terrain, et ils ne résolvent pas le même problème. La lecture utile du comparatif ci-dessous porte sur deux lignes seulement : Historique, qui décide si vous verrez les secrets déjà supprimés du code actuel, et Validation, qui décide du volume de faux positifs que votre équipe devra trier chaque semaine.

Les trois colonnes ne s'excluent pas : beaucoup d'équipes font tourner Gitleaks en local et TruffleHog en CI. Retenez surtout la ligne Méthode, qui explique tout le reste. Une détection par expression régulière reconnaît les formats connus, rate un token maison et ne sait pas si le credential est encore actif. Une détection par entropie repère les chaînes aléatoires quel que soit leur format, au prix de faux positifs que la validation vient ensuite filtrer.

CritèreGitLab Secret DetectionGitleaksTruffleHog
IntégrationNative GitLabCI génériqueCI générique
MéthodeRègles regexRègles regexEntropie + regex + validation
Historique⚠️ Limité✅ Complet✅ Complet
Validation✅ (certains providers)
CoûtScanner inclus dans tous les tiers ; rapports MR et gestion des vulnérabilités en UltimateGratuitGratuit / Enterprise
BruitMoyenConfigurableFaible (validation)

Le scanner intégré à GitLab s'active par une seule ligne d'include, sans job à écrire. Il tourne sur tous les tiers, y compris Free ; en Free et Premium vous récupérez le rapport JSON en artefact, alors qu'Ultimate l'intègre au widget de la merge request, au tableau de bord sécurité et aux politiques de sécurité. Son angle mort est l'historique : le scan porte sur les commits de la branche courante, pas sur l'intégralité du dépôt.

include:
- template: Jobs/Secret-Detection.gitlab-ci.yml
  • Génère un rapport de sécurité consultable dans la MR (tier Ultimate)
  • Règles maintenues par GitLab
  • Limité aux commits de la branche courante (pas l'historique complet)

Documentation GitLab Secret Detection

Gitleaks est un binaire Go unique, sans dépendance, qui s'insère dans n'importe quelle CI. Depuis la version 8, la commande detect a disparu au profit de deux sous-commandes explicites : gitleaks git parcourt l'historique des commits, gitleaks dir inspecte l'arborescence de fichiers telle qu'elle est sur le disque. L'image est épinglée par digest ci-dessous pour que la pipeline exécute exactement le binaire audité, et non ce que le registre publiera demain sous le même tag.

secrets-scan:
image: ghcr.io/gitleaks/gitleaks:v8.30.1@sha256:c00b6bd0aeb3071cbcb79009cb16a60dd9e0a7c60e2be9ab65d25e6bc8abbb7f
script:
- gitleaks git --redact --verbose .
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"

Forces :

  • Configuration fine via .gitleaks.toml
  • Scan de l'historique complet (gitleaks git --log-opts="--all" .)
  • Code de sortie 1 dès qu'une fuite est trouvée, exploitable pour le gating

Guide Gitleaks

TruffleHog se distingue par la vérification active : pour les fournisseurs qu'il connaît, il appelle l'API concernée pour savoir si le credential trouvé fonctionne encore. Un secret déjà révoqué sort du rapport, ce qui fait chuter le volume de tri. Le filtre --results=verified remplace l'ancien --only-verified, toujours accepté mais retiré de l'aide. Attention au détail qui coûte des heures de débogage en CI : avec --fail, TruffleHog sort avec le code 183, pas 1, ce qui déroute les scripts qui testent -eq 1.

secrets-scan:
image: trufflesecurity/trufflehog:3.95.9@sha256:59b244249d1a1aef4baa24fe73d3c931616264482580d806d77f6c74d26b3e42
script:
- trufflehog git file://. --results=verified --fail

Forces :

  • Vérifie si le secret est encore valide (certains providers)
  • Réduit drastiquement les faux positifs
  • Scan multi-sources (Git, S3, Slack, etc.)

TruffleHog GitHub

Ce tableau se lit par la colonne de gauche : identifiez la ligne qui décrit votre situation actuelle, pas celle que vous visez dans six mois. La dernière ligne est celle que je recommande dès que l'équipe dépasse une dizaine de développeurs, parce qu'elle place un contrôle rapide et local avant le push et un contrôle exhaustif et non contournable dans la CI.

SituationRecommandation
GitLab Ultimate, équipe petiteGitLab Secret Detection
CI générique, besoin de contrôleGitleaks
Volume important, faux positifs problématiquesTruffleHog
Défense en profondeurGitleaks (pre-commit) + TruffleHog (CI)

Avant d'implémenter, l'équipe doit trancher :

Ces trois questions déterminent si votre scanner sera respecté ou contourné. Bloquer une pipeline coûte du temps à toute l'équipe : la décision doit être prise avant le déploiement, pas au premier incident. La troisième ligne est la plus souvent négligée alors qu'elle conditionne les deux autres, car une règle de blocage sans mécanisme d'exception documenté finit toujours en --no-verify généralisé.

QuestionOptions
Quand bloquer la pipeline ?Toujours / Seulement sur branches protégées / Jamais (warning)
Quels types de secrets bloquent ?Tous / Seulement haute sévérité / Liste explicite
Quid des faux positifs ?Baseline + allowlist / Review manuelle

Une alerte sans propriétaire nommé reste ouverte. Répondez aux trois questions ci-dessous par écrit, dans le dépôt lui-même, avant d'activer le scanner : c'est ce document que la personne d'astreinte ouvrira à 2 h du matin. Le choix le plus efficace en pratique consiste à confier le traitement à l'auteur du commit, qui seul sait à quoi sert le credential, avec l'équipe sécurité en escalade sur les secrets de production.

  • Qui traite une alerte ?, L'auteur du commit ? L'équipe sécurité ?
  • Quel délai de traitement ?, Immédiat pour les vrais secrets ?
  • Quelle escalade ?, Notification Slack ? Incident automatique ?

Les exceptions sont inévitables. Les documenter :

.gitleaks.toml
[extend]
useDefault = true
[[allowlists]]
description = "Fausses cles utilisees par les tests unitaires"
regexes = ['''FAKE_.*_KEY''']
paths = ['''tests/''', '''fixtures/''']

Le bloc [extend] avec useDefault = true est obligatoire : sans lui, votre fichier remplace le jeu de règles par défaut de Gitleaks au lieu de le compléter, et le scanner ne détecte plus rien. Les exclusions globales s'écrivent avec [[allowlists]] au niveau racine ; la forme [[rules.allowlist]] que l'on croise encore dans de vieux exemples fait échouer le chargement de la configuration avec le message 'Rules[0].AllowList' expected a map, got 'slice'. Vérifiez toujours qu'une modification du fichier laisse le scanner opérationnel, en réintroduisant volontairement un faux secret dans une branche de test.

Règles :

  • Toute exception a une date d'expiration
  • Toute exception est justifiée (commentaire)
  • Revue trimestrielle des exceptions

Un scanner qui remonte des alertes que personne ne traite dégrade la sécurité, parce qu'il installe l'habitude d'ignorer les avertissements. Le processus ci-dessous se déroule dans un ordre imposé : on qualifie avant d'agir, puis on applique l'action minimale correspondant à la situation, puis on trace. La question qui gouverne tout le reste vient en deuxième position, l'exposition publique, car c'est elle qui transforme un incident interne en course contre les robots de scraping.

  1. Vrai secret ?

    Faux positif (hash, fixture, exemple) → Ajouter à l'allowlist

  2. Exposé publiquement ?

    Repo public, MR publique, historique accessible → Rotation urgente

  3. Quel scope ?

    Accès limité (dev) vs accès large (prod, admin)

  4. Rotation possible ?

    Token révocable → Révoquer immédiatement Credential hardcodé → Rotation + audit d'utilisation

Le mot « minimales » est à prendre au pied de la lettre : ce sont les actions en dessous desquelles l'incident n'est pas clos. Notez que la rotation est présente dans les trois lignes, y compris quand le secret a été retiré du code. La raison tient au fonctionnement de Git : un commit supprimé de la branche reste accessible dans les objets du dépôt, dans les forks et dans les clones déjà réalisés. Seule la révocation du credential ferme réellement l'accès.

SituationAction
Secret valide détecté1. Révoquer/rotater 2. Vérifier les logs d'accès 3. Supprimer du code
Secret dans l'historiqueRotation obligatoire (même si supprimé du code actuel)
Secret exposé publiquementRotation + audit + évaluer la purge d'historique

Chaque incident de secret exposé devrait générer :

  • Une issue de suivi (rotation effectuée ?)
  • Un post-mortem léger (comment c'est arrivé ?)
  • Une amélioration (règle ajoutée, process modifié)

Ces six croyances reviennent dans presque tous les audits de pipeline. Les trois premières portent sur une compréhension incomplète du fonctionnement de Git et de la CI, les trois dernières sur des choix d'organisation qui font échouer un dispositif pourtant correctement outillé. La dernière est la plus structurante : confondre détection et gestion des secrets conduit à empiler des scanners sans jamais régler la cause.

❌ On a masqué la variable, donc c'est bon

Masquer une variable CI empêche son affichage dans les logs. Ça n'empêche pas de la committer dans le code.

❌ On a supprimé le fichier du repo

L'historique Git conserve tout. Un git log -p ou un clone suffisent à retrouver le secret. Seule la rotation protège.

❌ On scanne la branche default, ça suffit

Le secret est déjà exposé quand il atteint main. Scanner les MR permet de bloquer avant le merge.

❌ Trop de faux positifs → on désactive

La bonne réponse : configurer les règles, maintenir une allowlist, assigner un owner. Pas désactiver.

❌ On bloque tout dès J1

Sans process de gestion des exceptions et de rotation, vous créez du contournement. Commencer en mode warning, puis durcir.

❌ Confondre scanning et management

Le secrets scanning détecte les fuites. La gestion des secrets (Vault, KMS) stocke et distribue les secrets proprement. Ce sont deux sujets complémentaires.

La checklist suit les trois temps d'un déploiement : ce qu'on décide avant d'écrire la moindre ligne de CI, ce qu'on installe, puis ce qu'on doit avoir prêt le jour où une alerte tombe. Traitez-les dans cet ordre, un dispositif technique posé sans décision de gouvernance produit surtout du contournement.

Ces cinq points se tranchent en réunion, pas dans un fichier YAML. Tant qu'ils ne sont pas écrits quelque part dans le dépôt, chaque alerte relancera la même discussion. Le point le plus discuté est le troisième, les règles de blocage : commencez en mode avertissement, mesurez le volume réel de détections sur un mois, puis durcissez.

  • Niveaux de scan définis (pre-commit / MR / branche)
  • Outil(s) choisi(s) avec justification
  • Règles de blocage documentées
  • Ownership des alertes assigné
  • Process d'exception défini

Le seul point réellement obligatoire de cette liste est le job CI sur les merge requests : c'est le seul contrôle qu'un développeur ne peut pas désactiver depuis son poste. Le hook pre-commit améliore le confort et raccourcit la boucle de retour, mais il se contourne avec git commit --no-verify et ne doit jamais être compté comme une barrière. L'allowlist initiale se remplit en faisant tourner le scanner une première fois sur l'historique, avant d'activer le blocage.

  • Pre-commit hooks configurés (optionnel mais recommandé)
  • Job CI de scan sur les MR
  • Scan de la branche default comme filet
  • Allowlist initiale pour les faux positifs connus
  • Notifications configurées (Slack, email)

C'est la partie que les équipes préparent en dernier et dont elles ont besoin en premier. Une procédure de rotation par type de secret est indispensable parce que révoquer un token GitHub et faire tourner un mot de passe de base de données n'engagent ni les mêmes commandes, ni les mêmes personnes, ni le même risque d'interruption de service. Rédigez-les à froid, avec la commande exacte et le nom du service concerné.

  • Procédure de rotation documentée par type de secret
  • Contacts d'escalade identifiés
  • Template d'issue pour les incidents secrets
  • Revue trimestrielle des exceptions planifiée
  • Un secret committé est compromis, même supprimé du code : l'historique Git, les forks et les clones le conservent. Seule la rotation ferme l'accès.
  • Placez le contrôle à trois niveaux : pre-commit pour le confort, merge request pour la barrière non contournable, branche par défaut comme filet.
  • Gitleaks 8 n'a plus de commande detect : utilisez gitleaks git pour l'historique, gitleaks dir pour l'arborescence.
  • TruffleHog valide les secrets auprès des fournisseurs et sort avec le code 183 quand --fail est actif, pas avec 1.
  • Épinglez les images de scanner par digest : un scanner qui change sous vos pieds fausse la comparaison entre deux exécutions.
  • Décidez de la gouvernance (blocage, ownership, exceptions) avant de déployer l'outil, sinon vous obtiendrez du contournement plutôt que des corrections.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn