Aller au contenu principal

Analyse des secrets

fremforge recherche les identifiants committés avec gitleaks à trois endroits :

OùCe qu’elle faitPeut-on la désactiver ?
Protection des pushUn hook pre-receive analyse chaque push et le rejette purement et simplementNon
Vérification de pull requestfremforge/secret-scan fait échouer une pull request qui ajoute un secret, y compris dans les archives et les fichiers Git LFSOui, par organisation
Analyse de l’historiqueUne fois par semaine, gitleaks git lit tout l’historique de chaque dépôt et liste ce qui est déjà committéNon

Les résultats des trois sont listés sous Sécurité › Résultats › Analyse des secrets (/<org>/_admin/security/findings/secrets), et via GET /_app/api/v1/orgs/{org}/code-security/secrets/findings.

Un secret déjà présent sur la branche dans laquelle la pull request fusionne ne fait pas échouer la vérification ; voir Vérifications de pull request : seuls les nouveaux résultats bloquent. Il reste listé, et l’analyse hebdomadaire et la protection des push ne changent pas.

Fournisseurs cloud européens

Les règles propres à gitleaks couvrent plusieurs centaines de formats d’identifiants, mais aucun des fournisseurs cloud européens. fremforge ajoute des règles pour eux, et elles s’exécutent aux trois endroits ci-dessus :

FournisseurIdentifiant de règleCe qu’elle trouve
Hetzner Cloudhetzner-cloud-api-tokenUn jeton d’API sous un nom HCLOUD_ ou HETZNER_, y compris TF_VAR_hcloud_token
Scalewayscaleway-secret-keyUne clé secrète sous un nom SCW_ ou SCALEWAY_
Scalewayscaleway-key-pairUne clé d’accès suivie de sa secret_key, comme dans ~/.config/scw/config.yaml
OVHcloudovh-application-secretOVH_APPLICATION_SECRET, OVH_CONSUMER_KEY ou OVH_CLIENT_SECRET
OVHcloudovh-conf-application-secretapplication_secret, consumer_key ou client_secret dans un fichier ovh.conf
IONOS Cloudionos-cloud-tokenUn jeton sous un nom IONOS_
T Cloud / Open Telekom Cloudotc-secret-access-keyUne clé d’accès secrète sous un nom de clé secrète OTC_, OS_, HW_ ou OBS_, ou un nom *_SK contenant l’un d’eux
T Cloud / Open Telekom Cloudotc-terraform-provider-secret-keysecret_key écrit en clair dans un bloc de provider opentelekomcloud
T Cloud / Open Telekom Cloudotc-credentials-csvLe credentials.csv que la console télécharge lorsque vous créez une clé d’accès

Ces jetons n’ont pas de préfixe fixe : une règle ne correspond donc que là où le nom du fournisseur ou le format de fichier indique ce qu’est la valeur. Une simple chaîne de 64 caractères ou un UUID ailleurs ne sont pas signalés. Les valeurs fictives (xxxx…, l’UUID nul) et les références de variables (${HCLOUD_TOKEN}) sont ignorées. Pour un identifiant que ces règles ne couvrent pas, ajoutez un motif personnalisé.

Autres détecteurs

fremforge ajoute aussi des règles pour ces identifiants, en plus de celles de gitleaks :

FournisseurIdentifiant de règleCe qu’elle trouveAu push
Supabasesupabase-access-tokenUn jeton d’accès personnel, classique ou à portée limitée, ou un jeton d’accès OAuth : sbp_, sbp_oauth_ ou sbp_v0_ suivi de 40 caractères hexadécimauxBloque
Supabasesupabase-secret-keyUne clé secrète de projet (sb_secret_…), qui contourne la Row Level Security. Les clés publiables (sb_publishable_…) sont destinées aux navigateurs et ne sont pas signalées, pas plus que la clé locale par défaut publiée par la CLI SupabaseBloque
Lovablelovable-api-keyUne clé d’API d’espace de travail (lov_…)Bloque
Pydanticpydantic-logfire-tokenUn jeton d’écriture ou une clé d’API Logfire, ou une clé Pydantic AI Gateway (pylf_v…)Bloque
Cloudsmithcloudsmith-api-keyUne clé d’API : 40 caractères hexadécimaux sous un nom Cloudsmith tel que CLOUDSMITH_API_KEYSignalée uniquement

Chaque règle correspond au préfixe fixe ou au format propre du jeton, avec des bornes de longueur et d’entropie : elle bloque donc au push comme toute autre règle, et les parcours de dérogation et de contournement s’y appliquent. Une clé Cloudsmith n’a pas de préfixe : sa règle est donc signalée uniquement. Une clé Cloudsmith affectée à un nom tel que CLOUDSMITH_API_KEY est de toute façon généralement bloquée, par la règle générique de clé d’API de gitleaks.

Ce sont uniquement des règles de détection. fremforge ne propose pas de vérification de validité pour elles.

Mots de passe dans les fichiers de configuration

fremforge-password-config trouve un mot de passe écrit directement dans un fichier de configuration ou un script, par exemple DB_PASSWORD=… dans un fichier .env, password: … en YAML, spring.datasource.password=… ou ENV REDIS_PASS=… dans un Dockerfile. Elle lit les fichiers .env, YAML, .properties, .ini, .conf, .cfg, .toml, les scripts shell, PowerShell et batch, les Dockerfiles et les fichiers compose. Elle recherche une clé dont le nom évoque un mot de passe (password, passwd, passphrase, pwd, pass) avec une valeur brute qui termine la ligne. Elle écarte :

  • les références à une autre valeur (${DB_PASSWORD}, %(password)s, {{ … }}) ;
  • les valeurs fictives (changeme, example, xxx) ;
  • les catalogues de traduction ;
  • les paramètres qui désignent un hachage ou un algorithme (root_password_sha2, password.encoding.alg).

C’est une heuristique, pas un format de jeton. Nous l’avons mesurée sur CredData, un jeu public d’identifiants étiquetés provenant de dépôts open source, face à la règle générique de clé d’API de gitleaks sur les mêmes fichiers. Elle s’est montrée plus précise et a trouvé davantage de vrais mots de passe, sur des données exclues de son réglage (précision de 0,97 contre 0,95, rappel de 8 % contre 5 % de l’ensemble des mots de passe étiquetés). Elle est signalée uniquement. Elle ne lit pas le code source, et une valeur entre guillemets en YAML (password: "…") n’est pas couverte : ces formes ont aussi été mesurées et ne faisaient pas mieux que la règle générique.

Mots de passe trouvés par un classifieur (organisations propres à fremforge)

Une seconde étape lit les fichiers de code et de configuration à la recherche de mots de passe codés en dur qui n’ont pas de format de jeton, comme db_password = "…" en Python ou password: "…" en YAML. Elle ne s’exécute aujourd’hui que sur les organisations propres à fremforge. Elle n’est pas encore disponible pour les organisations clientes ; ce sera une décision distincte, après deux semaines de données sur les faux positifs.

Elle fonctionne en deux temps, après les règles ci-dessus, dans la vérification de pull request :

  1. Un filtre par mots-clés ne conserve que les lignes qui mentionnent un mot de passe ou contiennent user:password@ dans une URL, soit environ 1 % des lignes.
  2. Un classifieur d’apprentissage automatique attribue un score à chacune de ces lignes. Il s’agit du modèle de CredSweeper (Samsung, licence MIT), réentraîné par fremforge. Il s’exécute dans l’analyseur sans accès réseau, et aucun code ni aucune valeur n’en sort.

Un résultat porte l’identifiant de règle fremforge-password-ml. Un résultat situé dans un chemin de test (test/, __tests__/, *.test.ts, test_*.py, *_test.go et similaires) est conservé avec l’identifiant de règle fremforge-password-ml-test-path et étiqueté comme probable fixture. Les deux sont signalés uniquement.

Elle écarte :

  • la documentation, les fichiers de données et tout ce qui n’est ni du code ni de la configuration ;
  • les catalogues de traduction ;
  • les constantes qui contiennent une expression régulière portant sur les mots de passe, comme PASSWORD_RE ;
  • les lignes déjà signalées par une autre règle.

Si le classifieur ne peut pas s’exécuter ou dépasse 120 secondes, la vérification signale comme d’habitude les résultats des autres règles.

Comment elle a été mesurée. Nous avons réentraîné le modèle sur les dépôts de développement de CredData, en n’utilisant que ceux sous licence permissive, et nous l’avons évalué une seule fois sur des dépôts exclus de l’entraînement. Le seuil a été fixé avant cette évaluation.

FichiersPrécisionRappel
Code (valeurs entre guillemets)0,940,98
Configuration0,960,99

Les mots de passe dans les URL et dans les autres fichiers n’atteignaient pas une précision de 0,9 et ne sont pas signalés. Les dépôts d’entraînement et leurs licences sont listés dans le fichier NOTICE-training-data.md de l’analyseur.

Règles signalées uniquement

cloudsmith-api-key, fremforge-password-config, fremforge-password-ml et fremforge-password-ml-test-path sont signalées uniquement. Leurs résultats sont listés sous Sécurité › Résultats › Analyse des secrets, et les deux règles gitleaks figurent aussi dans l’analyse de l’historique (le classifieur ne s’exécute que dans la vérification de pull request). Dans une pull request, elles apparaissent dans fremforge/secret-scan comme advisory (not blocking) : elles ne font jamais échouer la vérification et ne bloquent jamais un push. Toutes les autres règles bloquent.

Le paramètre

Bloquer les pull requests qui ajoutent un secret détermine si la vérification de pull request s’exécute. Tant qu’une organisation n’a pas enregistré de choix, la vérification s’exécute et bloque. Lorsqu’elle est désactivée, la vérification ne s’exécute pas et son statut est retiré de la protection de branche ; la protection des push et l’analyse hebdomadaire continuent de s’exécuter. Réglez-le sous Sécurité › Résultats › Analyse des secrets ou avec PUT /_app/api/v1/orgs/{org}/code-security/secrets/settings et {"enabled": true} (policy:write).

Motifs personnalisés

Votre organisation peut ajouter ses propres formats de secrets : une forme de jeton que vous seul émettez, un préfixe de clé interne, l’identifiant d’un partenaire. Chaque motif devient une règle gitleaks supplémentaire aux trois endroits ci-dessus : une correspondance est bloquée au push, fait échouer la vérification de pull request et est trouvée dans l’historique. Un résultat issu d’un motif personnalisé porte l’identifiant de règle custom-<name> et affiche le nom du motif.

Ajoutez-les sous Sécurité › Politiques › Protection des push › Motifs de secrets personnalisés, ou avec POST /_app/api/v1/orgs/{org}/code-security/secrets/custom-patterns (policy:write ; GET les liste, PUT et DELETE sur /custom-patterns/{id} en modifient ou en suppriment un).

ChampObligatoireCe qu’il fait
nameouiAffiché sur chaque résultat. Jusqu’à 80 caractères.
regexouiSyntaxe RE2, jusqu’à 300 caractères.
keywordsnonJusqu’à 10. Le motif ne s’exécute que sur le contenu qui contient l’un d’eux, ce qui garde les push rapides.
entropynonDe 0 à 8. Les correspondances moins aléatoires que cette valeur, comme les valeurs fictives, ne sont pas signalées.
allowlist_pathsnonJusqu’à 20 globs de chemin (*, **, ?) où le motif n’est pas signalé. Un glob correspond à n’importe quel niveau de répertoire.
repositorynonUn dépôt de l’organisation, par son nom. Omettez-le pour tous les dépôts.
repository_idnonLa même chose, par identifiant de dépôt. Indiquez repository ou repository_id, pas les deux.
samplenonUn exemple auquel le motif doit correspondre. Vérifié à l’enregistrement, jamais stocké.

Envoyez "dry_run": true pour valider sans enregistrer.

Un dépôt ou tous. Un motif sans dépôt s’applique à tous les dépôts de l’organisation. Un motif avec un dépôt ne s’applique qu’à ce dépôt, aux trois endroits, en plus des motifs valables pour toute l’organisation. Dans la console, choisissez le dépôt dans la liste, ou Tous les dépôts. Le motif est lié au dépôt lui-même, et non à son nom : renommer le dépôt conserve le motif. Un dépôt supprimé puis recréé sous le même nom est un nouveau dépôt et n’en hérite pas. Un motif dont le dépôt a été supprimé ou déplacé vers une autre organisation ne s’applique nulle part et est affiché comme tel jusqu’à ce que vous le modifiiez ou le supprimiez. Les réponses portent à la fois repository_id et repository, le nom actuel du dépôt.

# Validate only
curl -sS -X POST "https://frem.sh/_app/api/v1/orgs/$ORG/code-security/secrets/custom-patterns" \
  -H "Authorization: Bearer $FREMFORGE_TOKEN" -H 'Content-Type: application/json' \
  -d '{"name": "Acme deploy key", "regex": "acme_live_[A-Za-z0-9]{24}", "sample": "KEY=acme_live_abcdefghijklmnopqrstuvwx", "dry_run": true}'
# {"valid": true, "rule_id": "custom-acme-deploy-key"}

# Create (201)
curl -sS -X POST "https://frem.sh/_app/api/v1/orgs/$ORG/code-security/secrets/custom-patterns" \
  -H "Authorization: Bearer $FREMFORGE_TOKEN" -H 'Content-Type: application/json' \
  -d '{"name": "Acme deploy key", "regex": "acme_live_[A-Za-z0-9]{24}", "keywords": ["acme_live_"], "allowlist_paths": ["test/fixtures/**"]}'

# List
curl -sS "https://frem.sh/_app/api/v1/orgs/$ORG/code-security/secrets/custom-patterns" \
  -H "Authorization: Bearer $FREMFORGE_TOKEN"

# Replace (the whole pattern) and delete
curl -sS -X PUT "https://frem.sh/_app/api/v1/orgs/$ORG/code-security/secrets/custom-patterns/$ID" \
  -H "Authorization: Bearer $FREMFORGE_TOKEN" -H 'Content-Type: application/json' \
  -d '{"name": "Acme deploy key", "regex": "acme_live_[A-Za-z0-9]{32}"}'
curl -sS -X DELETE "https://frem.sh/_app/api/v1/orgs/$ORG/code-security/secrets/custom-patterns/$ID" \
  -H "Authorization: Bearer $FREMFORGE_TOKEN"

Un motif refusé renvoie 400 avec un corps problem+json dont le type indique la raison : invalid-repository, invalid-regex, regex-too-complex, regex-matches-empty, regex-too-broad, sample-not-matched, invalid-name, invalid-keywords, invalid-entropy ou invalid-allowlist-paths. Un second motif portant le même nom renvoie 409 duplicate-name, un 101e too-many-patterns, et un 21e sans mots-clés too-many-without-keywords.

Avec le provider Terraform/OpenTofu :

resource "fremforge_secret_custom_pattern" "acme_deploy_key" {
  org             = "acme"
  name            = "Acme deploy key"
  regex           = "acme_live_[A-Za-z0-9]{24}"
  keywords        = ["acme_live_"]
  allowlist_paths = ["test/fixtures/**"]
  # repository    = "infra"   # one repository only (provider 0.7.0 and later)
}

Pourquoi uniquement RE2. Un motif s’exécute dans le hook de push, à chaque push. RE2 ne fait pas de retour arrière : son temps d’exécution croît linéairement avec l’entrée, et aucun motif ne peut bloquer un push. Les références arrière (\1) et les assertions avant/arrière ((?=, (?!, (?<=) nécessitent un retour arrière et sont refusées. Un motif est également refusé lorsqu’il :

  • correspond à une chaîne vide, ou à du texte ordinaire comme const x = 1; ;
  • se répète plus de 256 fois ({n,m}), ou imbrique des groupes sur plus de 6 niveaux.

Tester un motif avant de l’enregistrer

Tester sur les dépôts dans le formulaire d’ajout, ou l’API de simulation, exécute un motif candidat sur le dernier commit de la branche par défaut de chaque dépôt et montre où il correspondrait. Rien n’est enregistré : le motif n’est pas stocké, aucun résultat n’est créé, et le résultat de la simulation est conservé 24 heures.

Elle affiche le dépôt, le fichier, la ligne, le commit et l’empreinte de chaque correspondance, jamais le texte correspondant. Chaque dépôt est compté une seule fois, dans l’un des trois groupes suivants :

  • vérifiés ;
  • non vérifiables : échec du clonage, délai dépassé, échec de l’analyse, ou non atteint dans la limite de 10 minutes ;
  • ignorés : archivé, miroir, vide, trop volumineux (plus de 100 Mo, ou une copie de travail de plus de 256 Mo), ou au-delà de la limite de 200 dépôts.

Un motif limité à un dépôt n’est testé que sur ce dépôt.

# Start one (202); the body is the same as creating a pattern
curl -sS -X POST "https://frem.sh/_app/api/v1/orgs/$ORG/code-security/secrets/custom-patterns/dry-runs" \
  -H "Authorization: Bearer $FREMFORGE_TOKEN" -H 'Content-Type: application/json' \
  -d '{"name": "Acme deploy key", "regex": "acme_live_[A-Za-z0-9]{24}", "keywords": ["acme_live_"]}'
# {"id": "…", "status": "queued", …}

# Read it back until status is done (or failed)
curl -sS "https://frem.sh/_app/api/v1/orgs/$ORG/code-security/secrets/custom-patterns/dry-runs/$ID" \
  -H "Authorization: Bearer $FREMFORGE_TOKEN"
# {"status": "done", "repositories": {"total": 118, "scanned": 70, "not_checked": 48},
#  "matches": [{"repository": "web", "path": "config/partner.txt", "line": 1, "commit": "…", "fingerprint": "…"}], …}

Les deux nécessitent policy:write, car le résultat nomme des fichiers de tous les dépôts de l’organisation. Une seule simulation par organisation s’exécute à la fois, et une nouvelle peut démarrer toutes les 10 minutes. Une exécution qu’aucun analyseur ne prend en charge dans les 2 minutes se termine en failed avec error: expired ; relancez-la quelques minutes plus tard. Le démarrage d’une simulation est consigné dans le journal d’audit sous code-security.secret_scan.custom_pattern.dry_run_started. Un démarrage refusé renvoie les mêmes types que la création d’un motif, ou dry-run-in-progress (409), dry-run-cooldown (429) ou dry-run-unavailable (503).

Limites. Une organisation peut avoir jusqu’à 100 motifs, y compris ceux limités à un dépôt. Au plus 20 d’entre eux peuvent n’avoir aucun keywords. La réponse de liste indique les deux sous limit et limit_without_keywords.

La raison est le temps de push. Un motif avec mots-clés ne s’exécute que sur le contenu qui contient l’un d’eux : il ne coûte presque rien. Un motif sans mots-clés s’exécute sur chaque élément de chaque push. Mesuré avec notre hook de push, 200 motifs sans mots-clés ont fait passer un push de 100 commits d’environ 2 secondes à 17. Un mot-clé est généralement le préfixe fixe du secret.

Enregistrer ou supprimer un motif met en file une nouvelle analyse de tout l’historique de chaque dépôt : un nouveau motif trouve donc aussi ce qui est déjà committé. Cela prend généralement jusqu’à 15 minutes. Les modifications sont consignées dans le journal d’audit sous code-security.secret_scan.custom_pattern.created, .updated et .deleted.

Si les motifs ne peuvent pas être chargés au moment du push, par exemple lorsque l’API est brièvement injoignable, le push est tout de même analysé avec les règles intégrées, et une ligne d’audit enregistre que les motifs personnalisés n’ont pas été appliqués. L’analyse de l’historique trouve ensuite ce qui a été manqué, dès que cela atteint la branche par défaut.

Vérifications de validité

Un secret qui fonctionne encore est plus urgent qu’un secret déjà renouvelé. Une vérification de validité demande au service qui a émis un secret détecté s’il est toujours actif, et étiquette le résultat Actif, Inactif ou Inconnu. Les résultats actifs sont listés en premier, dans la console et dans l’API (validity, validity_checked_at).

Chaque fournisseur a son propre paramètre sous Sécurité › Politiques › Protection des push › Vérifications de validité, ou via PUT /_app/api/v1/orgs/{org}/code-security/secrets/validity-settings/{provider} avec {"enabled": true} (policy:write). Avant que vous n’en activiez un, la page indique l’hôte que contacte la vérification et si le fournisseur est Exploité aux États-Unis, Exploité dans l’UE, Exploité au Royaume-Uni ou Exploité au Canada. L’API renvoie la même information sous operator_jurisdiction (us, eu, uk, ca, ou null pour la vérification interne). Le Royaume-Uni et le Canada bénéficient tous deux d’une décision d’adéquation de l’UE ; aucun fournisseur exploité depuis un autre pays hors de l’UE n’est proposé dans GET .../validity-settings.

Ce tableau est la liste des fournisseurs à laquelle renvoie l’accord de traitement des données (annexe B, §B.10). fremforge ne propose une vérification que pour un fournisseur qui y figure, chacune par une seule requête en lecture seule vers un hôte fixe ou, lorsqu’un fournisseur sert ses clés depuis des hôtes régionaux (SendGrid, Mailgun, Datadog, Intercom, Postman, Square, HCP Terraform, Typeform), vers une liste fixe d’hôtes interrogés dans l’ordre ; vous ne pouvez pas ajouter votre propre URL. Un fournisseur ajouté ultérieurement démarre désactivé.

FournisseurRègles vérifiéesDestination de la vérificationLa requêteExploitationPar défaut
fremshJetons d’API frem.sh (ffp_)Au sein de frem.sh. Aucun appel externeLe hachage du jeton, comparé au magasin de jetons de frem.sh—Activé
githubJetons GitHubapi.github.comGET /rate_limitExploité aux États-UnisDésactivé
gitlabJetons d’accès personnels GitLab.com (un jeton issu d’un serveur autogéré donne Inconnu)gitlab.comGET /api/v4/personal_access_tokens/selfExploité aux États-UnisDésactivé
awsClés d’accès AWSsts.amazonaws.comGetCallerIdentity, qui ne nécessite aucune autorisationExploité aux États-UnisDésactivé
slackJetons Slackslack.comPOST /api/auth.test, sans corpsExploité aux États-UnisDésactivé
stripeClés d’API Stripeapi.stripe.comGET /v1/balanceExploité aux États-UnisDésactivé
hetznerJetons d’API Hetzner Cloudapi.hetzner.cloudGET /v1/locationsExploité dans l’UEDésactivé
scalewayClés secrètes Scalewayapi.scaleway.comGET /account/v3/projects?page_size=1Exploité dans l’UEDésactivé
ionosJetons d’API IONOS Cloudapi.ionos.comGET /cloudapi/v6/Exploité dans l’UEDésactivé
npmJetons d’accès npmregistry.npmjs.orgGET /-/whoamiExploité aux États-UnisDésactivé
openaiClés d’API OpenAIapi.openai.comGET /v1/modelsExploité aux États-UnisDésactivé
anthropicClés d’API Anthropic (pas les clés Admin API)api.anthropic.comGET /v1/modelsExploité aux États-UnisDésactivé
huggingfaceJetons d’accès Hugging Face (pas les anciens jetons api_org_)huggingface.coGET /api/whoami-v2Exploité aux États-UnisDésactivé
digitaloceanJetons DigitalOceanapi.digitalocean.comGET /v2/accountExploité aux États-UnisDésactivé
sendgridClés d’API SendGridapi.sendgrid.com, puis api.eu.sendgrid.com, jusqu’à ce que l’un répondeGET /v3/scopesExploité aux États-UnisDésactivé
mailgunClés d’API privées Mailgunapi.mailgun.net, puis api.eu.mailgun.net, jusqu’à ce que l’un répondeGET /v3/domainsExploité aux États-UnisDésactivé
datadogClés d’API DatadogLes six sites de Datadog, dans cet ordre, jusqu’à ce que l’un réponde : api.datadoghq.com, api.datadoghq.eu, api.us3.datadoghq.com, api.us5.datadoghq.com, api.ap1.datadoghq.com, api.ddog-gov.comGET /api/v1/validateExploité aux États-UnisDésactivé
azuredevopsJetons d’accès personnels Azure DevOps. Actif ou Inconnu uniquementapp.vssps.visualstudio.comGET /_apis/profile/profiles/meExploité aux États-UnisDésactivé
ovhIdentifiants d’API OVHcloud. Actif ou Inconnu uniquementeu.api.ovh.comGET /1.0/auth/currentCredential, signéeExploité dans l’UEDésactivé
adafruitClés Adafruit IOio.adafruit.comGET /api/v2/userExploité aux États-UnisDésactivé
airtableJetons d’accès personnels Airtableapi.airtable.comGET /v0/meta/whoamiExploité aux États-UnisDésactivé
asanaJetons d’accès personnels Asanaapp.asana.comGET /api/1.0/users/meExploité aux États-UnisDésactivé
dopplerJetons personnels Dopplerapi.doppler.comGET /v3/meExploité aux États-UnisDésactivé
dropboxJetons d’accès Dropboxapi.dropboxapi.comPOST /2/users/get_current_account, qui lit le compteExploité aux États-UnisDésactivé
easypostClés d’API EasyPostapi.easypost.comGET /v2/addresses?page_size=1Exploité aux États-UnisDésactivé
fastlyJetons d’API Fastlyapi.fastly.comGET /tokens/selfExploité aux États-UnisDésactivé
herokuClés d’API Herokuapi.heroku.comGET /accountExploité aux États-UnisDésactivé
intercomJetons d’accès Intercomapi.intercom.io, puis api.eu.intercom.io, puis api.au.intercom.io, jusqu’à ce que l’un répondeGET /meExploité aux États-UnisDésactivé
lobClés d’API Lobapi.lob.comGET /v1/addresses?limit=1Exploité aux États-UnisDésactivé
notionJetons d’intégration Notionapi.notion.comGET /v1/users/meExploité aux États-UnisDésactivé
postmanClés d’API Postmanapi.getpostman.com, puis api.eu.postman.com, jusqu’à ce que l’un répondeGET /meExploité aux États-UnisDésactivé
readmeClés d’API ReadMedash.readme.comGET /api/v1Exploité aux États-UnisDésactivé
rubygemsClés d’API RubyGemsrubygems.orgGET /api/v1/gems.jsonExploité aux États-UnisDésactivé
shippoJetons d’API Shippoapi.goshippo.comGET /addresses/?results=1Exploité aux États-UnisDésactivé
squareJetons d’accès Squareconnect.squareup.com, puis connect.squareupsandbox.com, jusqu’à ce que l’un répondeGET /v2/merchants/meExploité aux États-UnisDésactivé
brevoClés d’API Brevo (Sendinblue)api.brevo.comGET /v3/accountExploité dans l’UEDésactivé
gocardlessJetons d’accès GoCardless (live)api.gocardless.comGET /creditors?limit=1Exploité au Royaume-UniDésactivé
duffelJetons d’accès Duffelapi.duffel.comGET /air/airlines?limit=1Exploité au Royaume-UniDésactivé
cohereClés d’API Cohereapi.cohere.comPOST /v1/check-api-key, la vérification de clé propre à CohereExploité au CanadaDésactivé
hcpterraformJetons d’API HCP Terraform. Actif ou Inconnu uniquementapp.terraform.io, puis app.eu.terraform.io, jusqu’à ce que l’un répondeGET /api/v2/account/detailsExploité aux États-UnisDésactivé
pulumiJetons d’accès Pulumi. Actif ou Inconnu uniquementapi.pulumi.comGET /api/userExploité aux États-UnisDésactivé
sentryJetons d’authentification utilisateur Sentry. Actif ou Inconnu uniquementsentry.ioGET /api/0/Exploité aux États-UnisDésactivé
messagebirdClés d’accès MessageBird. Actif ou Inconnu uniquementrest.messagebird.comGET /balanceExploité dans l’UEDésactivé
typeformJetons d’accès personnels Typeform. Actif ou Inconnu uniquementapi.typeform.com, puis api.eu.typeform.com, jusqu’à ce que l’un répondeGET /meExploité dans l’UEDésactivé

Les jetons d’accès personnels Azure DevOps, les clés d’API Datadog et les jetons d’accès personnels Asana sont trouvés par trois règles fremforge, azure-devops-pat, datadog-api-key et asana-personal-access-token, qui s’exécutent aux trois endroits ci-dessus.

Certaines vérifications peuvent montrer qu’un secret est actif, jamais qu’il ne l’est pas. Azure DevOps refuse un jeton actif limité à une seule organisation ou dépourvu de la portée profile : un refus y donne donc Inconnu, jamais Inactif. Une requête OVHcloud doit être signée avec la clé d’application, le secret d’application et la clé consommateur ensemble. La vérification ne s’exécute que lorsque l’analyse de l’historique trouve les trois dans le même fichier, comme un ovh.conf, et toute réponse autre qu’un succès donne Inconnu. Les jetons HCP Terraform, Pulumi et Sentry peuvent appartenir à un Terraform Enterprise, un Pulumi ou un Sentry autohébergé, qui les refuse sur le service public. Les clés MessageBird issues de la nouvelle plateforme Bird peuvent être refusées par l’API d’origine de MessageBird. Un jeton Typeform dépourvu de la portée account peut être refusé de la même manière qu’un jeton révoqué. Ces cinq fournisseurs donnent donc Actif ou Inconnu, jamais Inactif.

Un secret n’est inactif que lorsque tous les hôtes le confirment. Une clé SendGrid rattachée à la région UE échoue sur l’hôte global, une clé Datadog appartient à l’un des sites de Datadog, et une clé Intercom, Postman ou Typeform à l’une de leurs régions ; un jeton sandbox Square échoue en production. La vérification interroge donc les hôtes dans l’ordre indiqué et s’arrête au premier qui répond que le secret est actif. Elle ne donne Inactif que lorsque tous les hôtes le refusent.

Pas de vérification automatique : Atlassian, Twilio, Flutterwave, Mailchimp, Telegram, GitLab autogéré et T Cloud. Leurs résultats ne proposent que Vérifier vous-même. Un jeton Atlassian nécessite aussi le site et l’adresse e-mail du compte, et une clé d’API Twilio nécessite son SID. La règle Flutterwave ne trouve que des clés de test. Une clé Mailchimp désigne son propre hôte de centre de données : il n’y a donc pas d’hôte fixe à vérifier. Telegram est exploité depuis l’extérieur de l’UE, du Royaume-Uni, du Canada et des États-Unis. Un serveur GitLab autogéré, ou tout autre hôte de votre choix, est une destination que fremforge ne contacte pas en votre nom. T Cloud est le cloud sur lequel fremforge lui-même s’exécute, et son service d’identité n’est volontairement pas joignable depuis les vérifications de fremforge. Pour une clé T Cloud, la commande demande à OBS de lister vos buckets : une clé inconnue renvoie InvalidAccessKeyId.

Une clé active mais non autorisée à effectuer la lecture de la vérification compte comme Actif : une clé Stripe à qui le solde est refusé, une clé Scaleway à qui les projets sont refusés, une clé Anthropic refusée avec permission_error, une clé OpenAI refusée faute d’autorisations, un jeton Dropbox refusé avec missing_scope, ou une clé Heroku, RubyGems, Square, Asana, GoCardless ou Duffel refusée avec 403. Pour les fournisseurs ajoutés le 2026-10-24, seule la réponse exacte que chacun renvoie pour une clé inexistante donne Inactif ; toute autre réponse donne Inconnu. invalid_api_key est la seule réponse d’OpenAI interprétée comme Inactif. Les clés Admin API d’Anthropic et les anciens jetons d’organisation Hugging Face ne sont pas vérifiés, car les points de terminaison ci-dessus ne permettent pas de savoir s’ils sont actifs.

Fonctionnement d’une vérification, et ce qui est stocké. Chaque vérification est en lecture seule. Elle demande à qui appartient un jeton ou s’il est valide, et ne modifie jamais rien chez le fournisseur. Le secret n’est toujours jamais stocké. Pendant que l’analyse de l’historique lit un dépôt, l’analyseur chiffre chaque valeur correspondante avec une clé que l’API crée pour cette seule analyse. L’API la déchiffre, exécute la vérification, ne conserve que le résultat et sa date, puis supprime la valeur. La clé est supprimée à la fin de l’analyse. L’analyseur lui-même n’a aucun accès réseau aux fournisseurs.

Un jeton frem.sh est vérifié dans le magasin de jetons propre à frem.sh, et son état est lu en direct : révoquer le jeton affiche immédiatement le résultat comme Inactif. Pour les autres fournisseurs, l’état est celui de la dernière analyse qui a lu le secret.

Inconnu signifie que le fournisseur n’a pas donné de réponse claire, par exemple un délai dépassé ou une limite de fréquence, une clé AWS dont la partie secrète n’a pas été trouvée dans les cinq lignes qui l’entourent, ou un identifiant OVHcloud dont les trois parties n’ont pas été trouvées ensemble. Inconnu n’est jamais affiché comme Inactif.

Activer un fournisseur relance l’analyse de l’historique de chaque dépôt, car la valeur d’un secret trouvé n’existe que pendant que l’analyse la lit. Le désactiver efface les résultats de ce fournisseur. Les modifications sont consignées dans le journal d’audit sous code-security.secret_scan.validity.updated.

Vérifier vous-même, puis marquer

Vous n’avez pas besoin d’activer une vérification de validité pour savoir si un secret fonctionne encore. Pour un secret émis par l’un des fournisseurs ci-dessus, ou par Atlassian, Twilio, Flutterwave, Mailchimp, Telegram, un serveur GitLab autogéré ou T Cloud, le résultat propose Vérifier vous-même : la requête en lecture seule propre au fournisseur, sous forme de commande à exécuter dans votre terminal, par exemple pour un jeton GitHub :

read -rs TOKEN && curl -s -o /dev/null -w '%{http_code}\n' -H "Authorization: Bearer $TOKEN" https://api.github.com/rate_limit

La commande ne contient jamais le secret. Elle attend que vous le saisissiez ou le colliez, ne l’affiche pas et le tient à l’écart de l’historique de votre shell. Il n’est envoyé qu’au fournisseur, jamais à fremforge. Le résultat indique ce qu’affiche la commande lorsque le secret est actif (ici 200) et lorsqu’il ne l’est pas (401).

Enregistrez ensuite ce que vous avez constaté avec Marquer actif ou Marquer renouvelé dans le menu du résultat, ou avec PUT /_app/api/v1/orgs/{org}/code-security/secrets/findings/{id}/mark et {"mark": "active"}, {"mark": "rotated"} ou {"mark": null} (findings:write). Un résultat marqué actif est listé avec les résultats actifs. Chaque emplacement est marqué séparément : une marque appartient à un résultat (un fichier, une ligne et un commit) et est conservée d’une analyse de l’historique à l’autre, comme le fait d’ignorer un résultat. Si le même secret a été trouvé dans deux commits, marquez les deux. fremforge ne stocke rien de dérivé du secret qui permettrait de relier les emplacements entre eux. Une marque n’ignore pas le résultat. Chaque marque est consignée dans le journal d’audit sous code-security.secrets.marked. L’API des résultats renvoie la marque sous mark et la commande sous self_check.

Renouveler par défaut, et lesquels renouveler en premier

Considérez chaque secret trouvé ici comme compromis, et renouvelez-le, sauf si son fournisseur ou vous-même avez confirmé qu’il ne fonctionne plus. Retirer un secret du code ne le révoque pas : il reste dans l’historique, où toute personne disposant d’un accès en lecture peut le trouver.

Pour vous aider à décider lesquels renouveler en premier lorsqu’aucun fournisseur n’a répondu, les résultats sont triés selon la probabilité que le secret soit encore actif. L’estimation n’utilise que ce que fremforge sait déjà, jamais le secret lui-même, et ne contacte personne :

SignalEffet
La ligne exacte figure toujours dans le code actuel de la branche par défautProbablement actif
La protection des push a bloqué le même type de secret dans ce dépôt au cours des 90 derniers joursProbablement actif
La ligne a été modifiée ou supprimée par la suite sur la branche par défautPeut-être actif : la supprimer ne l’a pas révoqué
Le commit ne se trouve que sur une branche autre que la branche par défautPeut-être actif
Committé au cours des 90 derniers joursAugmente l’estimation
Committé il y a plus de deux ans, et absent du code actuelMoins probablement actif

La réponse propre d’un fournisseur (Actif ou Inactif) et votre propre marque priment toujours. L’estimation est affichée, avec au plus deux de ses raisons, sur chaque résultat auquel aucun fournisseur n’a répondu et que vous n’avez pas marqué. Les signaux issus du code sont enregistrés par l’analyse hebdomadaire de l’historique : un résultat analysé avant l’analyse qui les a ajoutés n’affiche que le signal de la protection des push jusqu’à l’analyse suivante. L’API des résultats la renvoie sous likely_live (level et reasons), avec committed_at. Elle vaut null lorsqu’un fournisseur a répondu ou que le résultat est marqué. C’est une estimation, pas une vérification : « Moins probablement actif » signifie quand même qu’il faut renouveler.

Ce qui est stocké

Seule une empreinte de la correspondance est stockée, jamais le texte correspondant. La règle, le chemin, la ligne et le commit suffisent pour ouvrir le fichier à ce commit. Une vérification de validité y ajoute le résultat et sa date. Pour un jeton frem.sh, elle stocke aussi l’identifiant de l’enregistrement de jeton correspondant, jamais le jeton lui-même.

Lorsqu’un secret est trouvé

Renouvelez d’abord l’identifiant à sa source. Supprimer la ligne n’annule pas la fuite de la valeur : quiconque possède l’historique l’a toujours. Retirez-la ensuite du code : Ouvrir une demande de correctif dans la colonne Correctif du résultat déplace un identifiant en Go, Python, JavaScript ou TypeScript vers une variable d’environnement, sans IA ; voir Correctifs à base de règles. La pull request ne retire pas la valeur de l’historique : le renouvellement vient donc en premier dans tous les cas.

Voir aussi