Analyse des secrets
fremforge recherche les identifiants committés avec gitleaks à trois endroits :
| Où | Ce qu’elle fait | Peut-on la désactiver ? |
|---|---|---|
| Protection des push | Un hook pre-receive analyse chaque push et le rejette purement et simplement | Non |
| Vérification de pull request | fremforge/secret-scan fait échouer une pull request qui ajoute un secret, y compris dans les archives et les fichiers Git LFS | Oui, par organisation |
| Analyse de l’historique | Une 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 :
| Fournisseur | Identifiant de règle | Ce qu’elle trouve |
|---|---|---|
| Hetzner Cloud | hetzner-cloud-api-token | Un jeton d’API sous un nom HCLOUD_ ou HETZNER_, y compris TF_VAR_hcloud_token |
| Scaleway | scaleway-secret-key | Une clé secrète sous un nom SCW_ ou SCALEWAY_ |
| Scaleway | scaleway-key-pair | Une clé d’accès suivie de sa secret_key, comme dans ~/.config/scw/config.yaml |
| OVHcloud | ovh-application-secret | OVH_APPLICATION_SECRET, OVH_CONSUMER_KEY ou OVH_CLIENT_SECRET |
| OVHcloud | ovh-conf-application-secret | application_secret, consumer_key ou client_secret dans un fichier ovh.conf |
| IONOS Cloud | ionos-cloud-token | Un jeton sous un nom IONOS_ |
| T Cloud / Open Telekom Cloud | otc-secret-access-key | Une 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 Cloud | otc-terraform-provider-secret-key | secret_key écrit en clair dans un bloc de provider opentelekomcloud |
| T Cloud / Open Telekom Cloud | otc-credentials-csv | Le 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 :
| Fournisseur | Identifiant de règle | Ce qu’elle trouve | Au push |
|---|---|---|---|
| Supabase | supabase-access-token | Un 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écimaux | Bloque |
| Supabase | supabase-secret-key | Une 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 Supabase | Bloque |
| Lovable | lovable-api-key | Une clé d’API d’espace de travail (lov_…) | Bloque |
| Pydantic | pydantic-logfire-token | Un jeton d’écriture ou une clé d’API Logfire, ou une clé Pydantic AI Gateway (pylf_v…) | Bloque |
| Cloudsmith | cloudsmith-api-key | Une clé d’API : 40 caractères hexadécimaux sous un nom Cloudsmith tel que CLOUDSMITH_API_KEY | Signalé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 :
- 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. - 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.
| Fichiers | Précision | Rappel |
|---|---|---|
| Code (valeurs entre guillemets) | 0,94 | 0,98 |
| Configuration | 0,96 | 0,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).
| Champ | Obligatoire | Ce qu’il fait |
|---|---|---|
name | oui | Affiché sur chaque résultat. Jusqu’à 80 caractères. |
regex | oui | Syntaxe RE2, jusqu’à 300 caractères. |
keywords | non | Jusqu’à 10. Le motif ne s’exécute que sur le contenu qui contient l’un d’eux, ce qui garde les push rapides. |
entropy | non | De 0 à 8. Les correspondances moins aléatoires que cette valeur, comme les valeurs fictives, ne sont pas signalées. |
allowlist_paths | non | Jusqu’à 20 globs de chemin (*, **, ?) où le motif n’est pas signalé. Un glob correspond à n’importe quel niveau de répertoire. |
repository | non | Un dépôt de l’organisation, par son nom. Omettez-le pour tous les dépôts. |
repository_id | non | La même chose, par identifiant de dépôt. Indiquez repository ou repository_id, pas les deux. |
sample | non | Un 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é.
| Fournisseur | Règles vérifiées | Destination de la vérification | La requête | Exploitation | Par défaut |
|---|---|---|---|---|---|
fremsh | Jetons d’API frem.sh (ffp_) | Au sein de frem.sh. Aucun appel externe | Le hachage du jeton, comparé au magasin de jetons de frem.sh | — | Activé |
github | Jetons GitHub | api.github.com | GET /rate_limit | Exploité aux États-Unis | Désactivé |
gitlab | Jetons d’accès personnels GitLab.com (un jeton issu d’un serveur autogéré donne Inconnu) | gitlab.com | GET /api/v4/personal_access_tokens/self | Exploité aux États-Unis | Désactivé |
aws | Clés d’accès AWS | sts.amazonaws.com | GetCallerIdentity, qui ne nécessite aucune autorisation | Exploité aux États-Unis | Désactivé |
slack | Jetons Slack | slack.com | POST /api/auth.test, sans corps | Exploité aux États-Unis | Désactivé |
stripe | Clés d’API Stripe | api.stripe.com | GET /v1/balance | Exploité aux États-Unis | Désactivé |
hetzner | Jetons d’API Hetzner Cloud | api.hetzner.cloud | GET /v1/locations | Exploité dans l’UE | Désactivé |
scaleway | Clés secrètes Scaleway | api.scaleway.com | GET /account/v3/projects?page_size=1 | Exploité dans l’UE | Désactivé |
ionos | Jetons d’API IONOS Cloud | api.ionos.com | GET /cloudapi/v6/ | Exploité dans l’UE | Désactivé |
npm | Jetons d’accès npm | registry.npmjs.org | GET /-/whoami | Exploité aux États-Unis | Désactivé |
openai | Clés d’API OpenAI | api.openai.com | GET /v1/models | Exploité aux États-Unis | Désactivé |
anthropic | Clés d’API Anthropic (pas les clés Admin API) | api.anthropic.com | GET /v1/models | Exploité aux États-Unis | Désactivé |
huggingface | Jetons d’accès Hugging Face (pas les anciens jetons api_org_) | huggingface.co | GET /api/whoami-v2 | Exploité aux États-Unis | Désactivé |
digitalocean | Jetons DigitalOcean | api.digitalocean.com | GET /v2/account | Exploité aux États-Unis | Désactivé |
sendgrid | Clés d’API SendGrid | api.sendgrid.com, puis api.eu.sendgrid.com, jusqu’à ce que l’un réponde | GET /v3/scopes | Exploité aux États-Unis | Désactivé |
mailgun | Clés d’API privées Mailgun | api.mailgun.net, puis api.eu.mailgun.net, jusqu’à ce que l’un réponde | GET /v3/domains | Exploité aux États-Unis | Désactivé |
datadog | Clés d’API Datadog | Les 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.com | GET /api/v1/validate | Exploité aux États-Unis | Désactivé |
azuredevops | Jetons d’accès personnels Azure DevOps. Actif ou Inconnu uniquement | app.vssps.visualstudio.com | GET /_apis/profile/profiles/me | Exploité aux États-Unis | Désactivé |
ovh | Identifiants d’API OVHcloud. Actif ou Inconnu uniquement | eu.api.ovh.com | GET /1.0/auth/currentCredential, signée | Exploité dans l’UE | Désactivé |
adafruit | Clés Adafruit IO | io.adafruit.com | GET /api/v2/user | Exploité aux États-Unis | Désactivé |
airtable | Jetons d’accès personnels Airtable | api.airtable.com | GET /v0/meta/whoami | Exploité aux États-Unis | Désactivé |
asana | Jetons d’accès personnels Asana | app.asana.com | GET /api/1.0/users/me | Exploité aux États-Unis | Désactivé |
doppler | Jetons personnels Doppler | api.doppler.com | GET /v3/me | Exploité aux États-Unis | Désactivé |
dropbox | Jetons d’accès Dropbox | api.dropboxapi.com | POST /2/users/get_current_account, qui lit le compte | Exploité aux États-Unis | Désactivé |
easypost | Clés d’API EasyPost | api.easypost.com | GET /v2/addresses?page_size=1 | Exploité aux États-Unis | Désactivé |
fastly | Jetons d’API Fastly | api.fastly.com | GET /tokens/self | Exploité aux États-Unis | Désactivé |
heroku | Clés d’API Heroku | api.heroku.com | GET /account | Exploité aux États-Unis | Désactivé |
intercom | Jetons d’accès Intercom | api.intercom.io, puis api.eu.intercom.io, puis api.au.intercom.io, jusqu’à ce que l’un réponde | GET /me | Exploité aux États-Unis | Désactivé |
lob | Clés d’API Lob | api.lob.com | GET /v1/addresses?limit=1 | Exploité aux États-Unis | Désactivé |
notion | Jetons d’intégration Notion | api.notion.com | GET /v1/users/me | Exploité aux États-Unis | Désactivé |
postman | Clés d’API Postman | api.getpostman.com, puis api.eu.postman.com, jusqu’à ce que l’un réponde | GET /me | Exploité aux États-Unis | Désactivé |
readme | Clés d’API ReadMe | dash.readme.com | GET /api/v1 | Exploité aux États-Unis | Désactivé |
rubygems | Clés d’API RubyGems | rubygems.org | GET /api/v1/gems.json | Exploité aux États-Unis | Désactivé |
shippo | Jetons d’API Shippo | api.goshippo.com | GET /addresses/?results=1 | Exploité aux États-Unis | Désactivé |
square | Jetons d’accès Square | connect.squareup.com, puis connect.squareupsandbox.com, jusqu’à ce que l’un réponde | GET /v2/merchants/me | Exploité aux États-Unis | Désactivé |
brevo | Clés d’API Brevo (Sendinblue) | api.brevo.com | GET /v3/account | Exploité dans l’UE | Désactivé |
gocardless | Jetons d’accès GoCardless (live) | api.gocardless.com | GET /creditors?limit=1 | Exploité au Royaume-Uni | Désactivé |
duffel | Jetons d’accès Duffel | api.duffel.com | GET /air/airlines?limit=1 | Exploité au Royaume-Uni | Désactivé |
cohere | Clés d’API Cohere | api.cohere.com | POST /v1/check-api-key, la vérification de clé propre à Cohere | Exploité au Canada | Désactivé |
hcpterraform | Jetons d’API HCP Terraform. Actif ou Inconnu uniquement | app.terraform.io, puis app.eu.terraform.io, jusqu’à ce que l’un réponde | GET /api/v2/account/details | Exploité aux États-Unis | Désactivé |
pulumi | Jetons d’accès Pulumi. Actif ou Inconnu uniquement | api.pulumi.com | GET /api/user | Exploité aux États-Unis | Désactivé |
sentry | Jetons d’authentification utilisateur Sentry. Actif ou Inconnu uniquement | sentry.io | GET /api/0/ | Exploité aux États-Unis | Désactivé |
messagebird | Clés d’accès MessageBird. Actif ou Inconnu uniquement | rest.messagebird.com | GET /balance | Exploité dans l’UE | Désactivé |
typeform | Jetons d’accès personnels Typeform. Actif ou Inconnu uniquement | api.typeform.com, puis api.eu.typeform.com, jusqu’à ce que l’un réponde | GET /me | Exploité dans l’UE | Dé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_limitLa 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 :
| Signal | Effet |
|---|---|
| La ligne exacte figure toujours dans le code actuel de la branche par défaut | Probablement actif |
| La protection des push a bloqué le même type de secret dans ce dépôt au cours des 90 derniers jours | Probablement actif |
| La ligne a été modifiée ou supprimée par la suite sur la branche par défaut | Peut-ê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éfaut | Peut-être actif |
| Committé au cours des 90 derniers jours | Augmente l’estimation |
| Committé il y a plus de deux ans, et absent du code actuel | Moins 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.