Aller au contenu
Sécurité medium

detect-secrets : empêcher les secrets d'entrer dans votre code

23 min de lecture

detect-secrets empêche les nouveaux secrets d'entrer dans votre code, sans vous obliger à nettoyer tout l'historique Git d'un coup. Grâce à un système de baseline versionnable, l'outil identifie les secrets existants et les met de côté pour se concentrer sur les ajouts. C'est l'approche pragmatique adoptée par Yelp pour sécuriser des centaines de dépôts en entreprise.

  • Comprendre la philosophie baseline de detect-secrets
  • Scanner un projet et créer une baseline
  • Auditer les résultats pour distinguer vrais et faux positifs
  • Filtrer les faux positifs avec les exclusions et l'allowlist inline
  • Intégrer detect-secrets comme pre-commit hook et en CI/CD

Le problème : des secrets qui s'accumulent dans l'historique

Section intitulée « Le problème : des secrets qui s'accumulent dans l'historique »

Imaginez un dépôt Git avec 5 ans d'historique et 10 000 commits. Quelque part dans ces commits, des clés API et des mots de passe ont été commités par erreur. Les supprimer du code actuel ne suffit pas : ils restent dans l'historique Git. Et réécrire l'historique complet est souvent irréaliste.

C'est exactement ce problème que detect-secrets résout avec une approche différente des autres scanners :

ApprocheComment ça marcheLimitation
Scanner tout l'historique (Gitleaks, TruffleHog)Parcourt chaque commitLent sur les gros dépôts, beaucoup de bruit
Baseline (detect-secrets)Photographie les secrets existants, bloque les nouveauxNe scanne pas l'historique

L'idée est simple : on accepte que des secrets existent déjà (c'est la baseline), et on empêche la situation d'empirer. Ensuite, on migre les secrets existants progressivement.

detect-secrets repose sur deux composants qui travaillent ensemble : les plugins et les filtres.

Les plugins trouvent les secrets dans le code. Il en existe trois types :

TypeFonctionnementExemple
RegexCherche des patterns structurés (clés AWS, tokens GitHub)AKIA... détecté par AWSKeyDetector
EntropieMesure le "désordre" d'une chaîne (haute entropie = probable secret)wJalrXUtnFEMI/K7MDENG détecté par Base64HighEntropyString
Mot-cléCherche des noms de variables associés aux secretspassword = "hunter2" détecté par KeywordDetector

Les filtres éliminent les faux positifs. Par défaut, detect-secrets ignore :

  • Les UUIDs (pattern reconnaissable)
  • Les chaînes séquentielles (abcdefg)
  • Les templates (${SECRET}, {{key}}, <placeholder>)
  • Les fichiers de lock
  • Les références indirectes (password = get_secret())

detect-secrets ne s'utilise pas comme un scanner unique lancé de temps en temps. L'outil expose trois commandes aux rôles bien séparés, et confondre scan et detect-secrets-hook est l'erreur d'installation la plus fréquente. scan écrit la baseline, il ne bloque rien et retourne toujours zéro. detect-secrets-hook compare les fichiers présentés à cette baseline et échoue en cas d'écart, c'est lui qui doit tourner en pre-commit et en CI. audit ne fait que qualifier des résultats déjà enregistrés.

CommandeQuand l'utiliser
detect-secrets scanCréer ou mettre à jour la baseline
detect-secrets-hookBloquer les commits contenant de nouveaux secrets
detect-secrets auditAnalyser et labelliser les résultats de la baseline

detect-secrets est un paquet Python : il s'installe donc là où vous en avez besoin, sur votre poste pour l'usage quotidien et dans l'image du runner pour la CI. Choisissez l'onglet correspondant à votre situation. Les distributions récentes (Ubuntu 24.04, Debian 12, Fedora) appliquent la PEP 668 et refusent pip install en dehors d'un environnement virtuel : l'onglet venv est alors le seul qui fonctionne sans forcer. Les deux extras word_list et gibberish sont facultatifs et n'ont d'intérêt qu'une fois les faux positifs devenus gênants.

Fenêtre de terminal
pip install detect-secrets

Pour les fonctionnalités optionnelles :

Fenêtre de terminal
# Support word-list (exclure des mots connus)
pip install detect-secrets[word_list]
# Support détecteur de gibberish (ML)
pip install detect-secrets[gibberish]

Vérification :

Fenêtre de terminal
detect-secrets --version
# 1.5.0

La baseline est le fichier qui donne son sens à l'outil : elle enregistre l'état des secrets au moment où vous commencez, et tout ce qui apparaît ensuite est traité comme une régression. Deux points de vigilance dès cette première manipulation. La baseline se commite dans le dépôt, sans quoi le hook n'a aucune référence à comparer. Et elle ne contient jamais la valeur des secrets, uniquement une empreinte, le nom du fichier et le numéro de ligne : la publier ne divulgue rien, mais elle révèle où chercher, ce qui suffit à justifier de garder le dépôt privé.

  1. Créer la baseline initiale : detect-secrets scanne tous les fichiers trackés par Git et génère un fichier JSON contenant les secrets détectés.

    Fenêtre de terminal
    detect-secrets scan > .secrets.baseline

    Le fichier .secrets.baseline est un JSON de cinq clés (version, plugins_used, filters_used, results, generated_at). Trois d'entre elles se relisent en revue :

    • plugins_used : la liste des 27 plugins actifs
    • filters_used : les 11 filtres appliqués
    • results : les secrets détectés, regroupés par fichier
  2. Vérifier ce qui a été détecté : utilisez la commande audit --report pour afficher un résumé lisible.

    Fenêtre de terminal
    detect-secrets audit .secrets.baseline --report

    Chaque secret est affiché avec son fichier, sa ligne, son type et sa catégorie (UNVERIFIED, VERIFIED_TRUE ou VERIFIED_FALSE).

  3. Versionner la baseline : ajoutez le fichier au dépôt Git. Il servira de référence pour bloquer les futurs secrets.

    Fenêtre de terminal
    git add .secrets.baseline
    git commit -m "chore: add detect-secrets baseline"

Au fil du temps, votre code évolue. Des secrets sont supprimés, d'autres ajoutés. Pour synchroniser la baseline :

Fenêtre de terminal
detect-secrets scan --baseline .secrets.baseline

Cette commande :

  • Met à jour la baseline vers le format de la dernière version
  • Ajoute les nouveaux secrets détectés
  • Supprime les secrets qui ne sont plus dans le code
  • Préserve les labels déjà attribués lors de l'audit

Pour tester rapidement si une valeur est détectée comme secret :

Fenêtre de terminal
detect-secrets scan --string "AKIAIOSFODNN7EXAMPLE"
AWSKeyDetector : True (unverified)
ArtifactoryDetector : False
AzureStorageKeyDetector : False
Base64HighEntropyString : False (3.684)
BasicAuthDetector : False
...

Chaque plugin indique True ou False. Pour les détecteurs d'entropie, le score est affiché entre parenthèses (seuil par défaut : 4.5 pour base64, 3.0 pour hex).

detect-secrets v1.5.0 embarque 27 plugins activés par défaut :

PluginCe qu'il détecte
AWSKeyDetectorClés d'accès AWS (AKIA...)
AzureStorageKeyDetectorClés de stockage Azure
BasicAuthDetectorIdentifiants en URLs (user:pass@host)
GitHubTokenDetectorTokens GitHub (ghp_, gho_, ghs_)
GitLabTokenDetectorTokens GitLab (glpat-)
JwtTokenDetectorJSON Web Tokens (eyJ...)
KeywordDetectorVariables nommées password, secret, token, etc.
OpenAIDetectorClés API OpenAI (sk-)
PrivateKeyDetectorClés privées RSA, DSA, ECDSA, OpenSSH
SlackDetectorTokens et webhooks Slack
StripeDetectorClés API Stripe (sk_live_, rk_live_)
Base64HighEntropyStringChaînes base64 à haute entropie (seuil : 4.5)
HexHighEntropyStringChaînes hexadécimales à haute entropie (seuil : 3.0)

Et aussi : ArtifactoryDetector, CloudantDetector, DiscordBotTokenDetector, IbmCloudIamDetector, IbmCosHmacDetector, IPPublicDetector, MailchimpDetector, NpmDetector, PypiTokenDetector, SendGridDetector, SoftlayerDetector, SquareOAuthDetector, TelegramBotTokenDetector, TwilioKeyDetector.

Tous les plugins ne sont pas pertinents partout. KeywordDetector déclenche sur toute variable nommée password ou token, y compris dans les jeux de tests et les fichiers d'exemple ; IPPublicDetector remonte les adresses IP publiques, dont celles, parfaitement légitimes, d'une documentation réseau. Désactiver un plugin change la composition de la baseline : régénérez-la dans la foulée, sinon le hook comparera des résultats produits par deux configurations différentes.

Fenêtre de terminal
# Désactiver le détecteur de mots-clés et les IP publiques
detect-secrets scan \
--disable-plugin KeywordDetector \
--disable-plugin IPPublicDetector

Pour n'utiliser qu'un seul plugin, désactivez tous les autres :

Fenêtre de terminal
detect-secrets scan --list-all-plugins | \
grep -v 'AWSKeyDetector' | \
sed "s#^#--disable-plugin #g" | \
xargs detect-secrets scan

Les détecteurs d'entropie acceptent un seuil entre 0.0 et 8.0. Un seuil plus bas détecte plus de secrets (mais plus de faux positifs) :

Fenêtre de terminal
# Seuil base64 abaissé de 4.5 à 3.5
detect-secrets scan --base64-limit 3.5
# Seuil hex relevé de 3.0 à 4.0
detect-secrets scan --hex-limit 4.0

Les faux positifs sont inévitables. detect-secrets propose plusieurs mécanismes pour les gérer.

Ajoutez un commentaire pragma: allowlist secret sur la ligne à exclure :

# Ce n'est pas un vrai secret, c'est un exemple
EXAMPLE_KEY = "AKIAIOSFODNN7EXAMPLE" # pragma: allowlist secret

Pour exclure la ligne suivante :

// pragma: allowlist nextline secret
const EXAMPLE_KEY = "AKIAIOSFODNN7EXAMPLE";

L'exclusion de fichiers travaille sur le chemin, pas sur le contenu. C'est le mécanisme à privilégier pour les répertoires de fixtures et les fichiers signés, dont on sait par construction qu'ils contiennent des chaînes à haute entropie. Le motif est une expression régulière Python appliquée au chemin relatif, \.signature$ ne correspond donc qu'aux fichiers se terminant par cette extension.

Fenêtre de terminal
detect-secrets scan --exclude-files '\.signature$' --exclude-files 'test_data/.*'

L'exclusion par ligne est plus fine que l'exclusion par fichier : elle laisse le fichier sous surveillance et ne neutralise que les lignes reconnaissables. Réservez-la à des motifs vraiment discriminants, comme un préfixe de variable de démonstration. Un motif trop large, KEY par exemple, éteindrait silencieusement la détection sur la moitié du dépôt.

Fenêtre de terminal
detect-secrets scan --exclude-lines 'EXAMPLE' --exclude-lines 'fake_secret'

Ce troisième niveau porte sur la valeur détectée elle-même, quel que soit le fichier ou la ligne où elle apparaît. C'est le bon outil pour les valeurs factices répétées dans tout le dépôt et pour les gabarits de variables non substituées. L'expression est une regex, d'où les échappements dans \$\{.*\} qui vise les placeholders de la forme ${VARIABLE}.

Fenêtre de terminal
detect-secrets scan --exclude-secrets '(fakesecret|\$\{.*\})'

Pour exclure un grand nombre de faux positifs connus, utilisez un fichier de mots :

Fenêtre de terminal
pip install detect-secrets[word_list]
wordlist.txt
example-password
test-api-key
SuperSecret123
Fenêtre de terminal
detect-secrets scan --word-list wordlist.txt

L'audit permet de labelliser chaque secret comme vrai positif ou faux positif. C'est utile pour :

  • Établir la liste des secrets à migrer
  • Mesurer la précision des plugins
  • Améliorer la configuration

L'audit ouvre une session en ligne de commande qui vous présente les résultats un par un, avec cinq lignes de contexte autour de chaque détection. Attention au sens de la question posée : detect-secrets demande « cette chaîne peut-elle être commitée ? », pas « est-ce un secret ? ». Répondre y marque donc un faux positif. Les réponses sont écrites dans la baseline sous forme de champ is_secret, et un q sauvegarde le travail déjà fait, ce qui permet de traiter un gros dépôt en plusieurs fois.

Fenêtre de terminal
detect-secrets audit .secrets.baseline

Pour chaque secret, detect-secrets affiche le contexte (5 lignes avant/après) et demande :

Secret: 1 of 12
Filename: config.env
Secret Type: Secret Keyword
----------
1: DATABASE_HOST=localhost
2: DATABASE_PORT=5432
3: DATABASE_USER=admin
4: DATABASE_PASSWORD=SuperSecret123!
5: AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
----------
Should this string be committed to the repository? (y)es, (n)o, (s)kip, (q)uit:
  • y (yes) : c'est un faux positif, on peut le garder
  • n (no) : c'est un vrai secret, à migrer
  • s (skip) : on ne sait pas, on passe
  • q (quit) : arrêter et sauvegarder

Le mode --report produit une sortie lisible sans rien demander : c'est la forme à utiliser en revue d'équipe ou dans un job de CI qui publie un artefact. Les deux filtres --only-real et --only-false sont exclusifs et répondent à deux besoins différents. --only-real donne la liste de travail des secrets à faire tourner et à sortir du code ; --only-false sert à relire vos propres décisions, l'endroit où se cachent les erreurs d'audit qui rouvriront une brèche.

Fenêtre de terminal
# Rapport complet
detect-secrets audit --report .secrets.baseline
# Seulement les vrais secrets
detect-secrets audit --report --only-real .secrets.baseline
# Seulement les faux positifs
detect-secrets audit --report --only-false .secrets.baseline

Après un audit interactif, mesurez la précision de vos plugins :

Fenêtre de terminal
detect-secrets audit --stats .secrets.baseline
Base64HighEntropyString:
- Precision: 75% (3 / 4 labelled secrets)
- Recall: 60% (3 / 5 known true secrets)

La comparaison de deux baselines répond à une question précise : combien de détections gagne ou perd un changement de seuil ? Plutôt que de régler --base64-limit à l'intuition, produisez deux fichiers avec des seuils différents et laissez audit --diff afficher l'écart. Un seuil abaissé qui n'ajoute que du bruit se voit immédiatement, et vous conservez la trace de la décision.

Fenêtre de terminal
detect-secrets scan --base64-limit 4 > limit4.json
detect-secrets scan --base64-limit 5 > limit5.json
detect-secrets audit --diff limit4.json limit5.json

La méthode recommandée par la documentation officielle est d'utiliser le framework pre-commit :

  1. Créer la baseline :

    Fenêtre de terminal
    detect-secrets scan > .secrets.baseline
  2. Configurer le hook dans .pre-commit-config.yaml :

    .pre-commit-config.yaml
    repos:
    - repo: https://github.com/Yelp/detect-secrets
    rev: v1.5.0
    hooks:
    - id: detect-secrets
    args: ['--baseline', '.secrets.baseline']
    exclude: package.lock.json
  3. Installer les hooks :

    Fenêtre de terminal
    pre-commit install
  4. Tester : essayez de commiter un fichier contenant un secret non listé dans la baseline. Le commit sera bloqué.

Le job reproduit ce que fait le hook local, mais sur la totalité des fichiers suivis : detect-secrets scan --baseline réaligne la baseline sur le code, puis detect-secrets-hook échoue si une détection ne s'y trouve pas. Trois précautions de supply chain rendent ce workflow utilisable en l'état. permissions: {} au niveau du workflow retire tous les droits par défaut, le job ne réclamant ensuite que contents: read. L'action checkout est épinglée par SHA de commit (un tag comme @v4 peut être redéplacé) et reçoit persist-credentials: false pour ne pas laisser le jeton GitHub dans le runner. Enfin, la version de detect-secrets est figée : sans cela, une future version aux plugins différents ferait échouer le job sans qu'aucun code n'ait changé.

.github/workflows/detect-secrets.yml
name: Detect Secrets
on: [push, pull_request]
permissions: {}
jobs:
detect-secrets:
runs-on: ubuntu-24.04
permissions:
contents: read
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- name: Install detect-secrets
run: pip install detect-secrets==1.5.0
- name: Scan for secrets
run: |
detect-secrets scan --baseline .secrets.baseline
git ls-files -z | xargs -0 detect-secrets-hook --baseline .secrets.baseline

Le pipeline GitLab suit la même logique, avec deux différences propres à la plateforme. L'image est épinglée par digest et non par tag : python:3.12-slim est réécrit à chaque publication d'un correctif Python, et un scanner de secrets qui change de base sans prévenir n'est plus reproductible. Récupérez le digest courant avec docker buildx imagetools inspect python:3.12-slim, puis mettez-le à jour sciemment. Le second point concerne git ls-files : il faut que le dépôt soit cloné avec son index Git, ce qui exclut les stratégies GIT_STRATEGY: none parfois utilisées pour accélérer les jobs.

.gitlab-ci.yml
detect-secrets:
image: python:3.12-slim@sha256:57cd7c3a7a273101a6485ba99423ee568157882804b1124b4dd04266317710de
stage: test
before_script:
- pip install detect-secrets==1.5.0
script:
- detect-secrets scan --baseline .secrets.baseline
- git ls-files -z | xargs -0 detect-secrets-hook --baseline .secrets.baseline

detect-secrets peut aussi être utilisé directement dans vos scripts Python :

from detect_secrets import SecretsCollection
from detect_secrets.settings import default_settings
secrets = SecretsCollection()
with default_settings():
secrets.scan_file('config.env')
import json
print(json.dumps(secrets.json(), indent=2))

Pour une configuration personnalisée :

from detect_secrets import SecretsCollection
from detect_secrets.settings import transient_settings
secrets = SecretsCollection()
with transient_settings({
'plugins_used': [
{'name': 'AWSKeyDetector'},
{'name': 'Base64HighEntropyString', 'limit': 5.0},
],
}) as settings:
secrets.scan_file('config.env')

Les trois outils sont souvent présentés comme concurrents alors qu'ils ne répondent pas à la même question. Deux lignes du tableau suffisent à trancher. La ligne Historique Git dit si l'outil sait remonter dans les commits passés : c'est le seul critère qui compte pour un audit d'existant, et detect-secrets y répond non. La ligne Vérification des secrets dit si l'outil interroge le fournisseur pour savoir si la clé trouvée est encore active : c'est ce qui sépare une alerte à traiter aujourd'hui d'un token révoqué il y a deux ans, et c'est la force de TruffleHog. Le reste (langage, performance) pèse peu à l'échelle d'un dépôt d'équipe.

Critèredetect-secretsGitleaksTruffleHog
LangagePythonGoGo
ApprocheBaseline + diffScan completScan complet
Historique GitNon (seulement les fichiers actuels)OuiOui
Vérification des secretsOui (certains plugins)NonOui (800+ détecteurs)
Plugins customOui (fichiers Python)Oui (règles TOML)Non
Filtres customOui (fichiers Python)Oui (allowlists TOML)Non
Audit interactifOuiNonNon
Pre-commitOui (natif)OuiOui
PerformanceRapide (pas d'historique)Très rapide (Go)Rapide (Go)
Cas d'usage idéalPrévention progressive en entrepriseScan complet d'historiqueScan multi-sources (Git, Docker, S3)

Ce tableau existe pour éviter le pire scénario : croire un dépôt propre parce que le pipeline est vert. La première ligne est la plus importante et découle directement du choix de conception de l'outil, detect-secrets ne regarde pas l'historique et ne détectera donc jamais une clé commitée l'an dernier puis retirée du code. Les deux lignes suivantes décrivent des angles morts de détection : un secret réparti sur plusieurs lignes, ou un mot de passe dont le nom de variable n'évoque rien pour le détecteur de mots-clés. Traitez la colonne Contournement comme une liste de tâches, pas comme une note de bas de page.

LimitationConséquenceContournement
Pas de scan d'historiqueLes secrets déjà commités ne sont pas détectésUtiliser Gitleaks ou TruffleHog en complément
Secrets multi-lignesNon détectés par défautÉcrire un plugin custom
Mots de passe faibleslogin = "hunter2" passe si le KeywordDetector ne match pas le nom de variableCombiner avec un mot-clé personnalisé
Maintenance de la baselineDoit être mise à jour régulièrementAutomatiser avec detect-secrets scan --baseline
Pas de scan Docker/S3Limité aux fichiers locaux et GitUtiliser TruffleHog pour ces cibles

Les cinq symptômes ci-dessous couvrent la mise en place initiale. Trois d'entre eux ont la même racine et méritent d'être reconnus d'un coup d'oeil : detect-secrets s'appuie sur Git pour savoir quels fichiers scanner. Une baseline vide, un dépôt non détecté ou un hook qui refuse de partir signalent presque toujours un fichier non ajouté à l'index plutôt qu'un défaut de l'outil. Commencez donc par git status avant de toucher aux seuils ou aux plugins.

SymptômeCauseSolution
Did not detect git repositoryVersion de Git < 1.8.5Mettre à jour Git
Not a valid baseline file! (Windows)Encodage du fichier incorrectS'assurer que .secrets.baseline est en UTF-8
Baseline vide ("results": {})Les fichiers ne sont pas trackés par GitAjouter les fichiers avec git add ou utiliser --all-files
Your baseline file is unstaged.secrets.baseline pas dans la zone de stagingExécuter git add .secrets.baseline
Trop de faux positifsSeuils d'entropie trop bas ou plugins trop largesAjuster --base64-limit, --exclude-lines, ou --disable-plugin
  • detect-secrets adopte une approche baseline : il accepte les secrets existants et empêche les nouveaux d'entrer.
  • Le scan par défaut ne couvre que les fichiers trackés par Git (pas l'historique des commits).
  • 27 plugins couvrent les principaux fournisseurs cloud, tokens, clés privées et mots de passe.
  • Les filtres (exclusions, allowlist inline, word-list) permettent de gérer les faux positifs.
  • detect-secrets-hook est le gardien au quotidien : il bloque les commits contenant de nouveaux secrets.
  • L'audit interactif permet de labelliser les résultats et de mesurer la précision des plugins.
  • Pour un scan d'historique complet, combinez avec Gitleaks ou TruffleHog.

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