Secretscanning
fremforge zoekt op drie plekken met gitleaks naar gecommitte inloggegevens:
| Waar | Wat het doet | Kan het uit? |
|---|---|---|
| Push-bescherming | Een pre-receive-hook scant elke push en weigert hem direct | Nee |
| Controle op pull requests | fremforge/secret-scan laat een pull request die een secret toevoegt falen, ook in archieven en Git LFS-bestanden | Ja, per organisatie |
| Doorzoeking van de geschiedenis | Eén keer per week leest gitleaks git de volledige geschiedenis van elke repository en vermeldt wat al gecommit is | Nee |
Bevindingen uit alle drie staan onder Beveiliging › Bevindingen › Secretscan (/<org>/_admin/security/findings/secrets), en op GET /_app/api/v1/orgs/{org}/code-security/secrets/findings.
Een secret dat al op de branch staat waarin de pull request wordt samengevoegd, laat de controle niet falen; zie Controles op pull requests: alleen nieuwe bevindingen blokkeren. Het wordt nog steeds vermeld, en de wekelijkse doorzoeking en push-bescherming blijven ongewijzigd.
Europese cloudproviders
De eigen regels van gitleaks dekken enkele honderden formaten van inloggegevens, maar geen enkel formaat van de Europese cloudproviders. fremforge voegt daar regels voor toe, en die draaien op alle drie de plekken hierboven:
| Provider | Regel-id | Wat hij vindt |
|---|---|---|
| Hetzner Cloud | hetzner-cloud-api-token | Een API-token onder een naam met HCLOUD_ of HETZNER_, waaronder TF_VAR_hcloud_token |
| Scaleway | scaleway-secret-key | Een geheime sleutel onder een naam met SCW_ of SCALEWAY_ |
| Scaleway | scaleway-key-pair | Een access key gevolgd door zijn secret_key, zoals in ~/.config/scw/config.yaml |
| OVHcloud | ovh-application-secret | OVH_APPLICATION_SECRET, OVH_CONSUMER_KEY of OVH_CLIENT_SECRET |
| OVHcloud | ovh-conf-application-secret | application_secret, consumer_key of client_secret in een ovh.conf-bestand |
| IONOS Cloud | ionos-cloud-token | Een token onder een naam met IONOS_ |
| T Cloud / Open Telekom Cloud | otc-secret-access-key | Een secret access key onder een naam voor een geheime sleutel met OTC_, OS_, HW_ of OBS_, of een naam op *_SK die een daarvan bevat |
| T Cloud / Open Telekom Cloud | otc-terraform-provider-secret-key | secret_key rechtstreeks uitgeschreven in een providerblok opentelekomcloud |
| T Cloud / Open Telekom Cloud | otc-credentials-csv | Het bestand credentials.csv dat de console downloadt als je een access key aanmaakt |
Deze tokens hebben geen vast voorvoegsel, dus een regel matcht alleen waar de naam van de provider of het bestandsformaat zegt wat de waarde is. Een losse string van 64 tekens of een UUID elders wordt niet gemeld. Placeholders (xxxx…, de nul-UUID) en verwijzingen naar variabelen (${HCLOUD_TOKEN}) worden genegeerd. Voor een inloggegeven dat deze regels niet dekken, voeg je een eigen patroon toe.
Meer detectoren
fremforge voegt ook regels voor deze inloggegevens toe, naast de eigen regels van gitleaks:
| Provider | Regel-id | Wat hij vindt | Bij het pushen |
|---|---|---|---|
| Supabase | supabase-access-token | Een persoonlijk toegangstoken, klassiek of met scope, of een OAuth-toegangstoken: sbp_, sbp_oauth_ of sbp_v0_ gevolgd door 40 hexadecimale tekens | Blokkeert |
| Supabase | supabase-secret-key | Een geheime projectsleutel (sb_secret_…), die Row Level Security omzeilt. Publiceerbare sleutels (sb_publishable_…) zijn bedoeld voor browsers en worden niet gemeld, net zomin als de gepubliceerde lokale standaardsleutel van de Supabase CLI | Blokkeert |
| Lovable | lovable-api-key | Een API-sleutel van een workspace (lov_…) | Blokkeert |
| Pydantic | pydantic-logfire-token | Een schrijftoken of API-sleutel van Logfire, of een sleutel van de Pydantic AI Gateway (pylf_v…) | Blokkeert |
| Cloudsmith | cloudsmith-api-key | Een API-sleutel: 40 hexadecimale tekens onder een Cloudsmith-naam zoals CLOUDSMITH_API_KEY | Alleen gemeld |
Elke regel matcht het eigen vaste voorvoegsel of formaat van het token, met grenzen voor lengte en entropie, dus hij blokkeert bij het pushen zoals elke andere regel, en de routes voor uitzonderingen en omzeilingen gelden ervoor. Een Cloudsmith-sleutel heeft geen voorvoegsel, dus die regel wordt alleen gemeld. Een Cloudsmith-sleutel die aan een naam als CLOUDSMITH_API_KEY is toegewezen, wordt meestal toch geblokkeerd, door de generieke API-sleutelregel van gitleaks.
Dit zijn alleen detectieregels. fremforge biedt er geen geldigheidscontrole voor.
Wachtwoorden in configuratiebestanden
fremforge-password-config vindt een wachtwoord dat rechtstreeks in een configuratie- of scriptbestand staat, bijvoorbeeld DB_PASSWORD=… in een .env-bestand, password: … in YAML, spring.datasource.password=… of ENV REDIS_PASS=… in een Dockerfile. Hij leest .env-bestanden, YAML, .properties, .ini, .conf, .cfg, .toml, shell-, PowerShell- en batchscripts, Dockerfiles en compose-bestanden. Hij zoekt naar een sleutel die naar een wachtwoord is genoemd (password, passwd, passphrase, pwd, pass) met een kale waarde die de regel afsluit. Hij laat weg:
- verwijzingen naar een andere waarde (
${DB_PASSWORD},%(password)s,{{ … }}); - placeholders (
changeme,example,xxx); - vertaalcatalogi;
- instellingen die een hash of algoritme noemen (
root_password_sha2,password.encoding.alg).
Het is een heuristiek, geen tokenformaat. We hebben hem gemeten op CredData, een openbare, gelabelde set inloggegevens in opensourcerepository’s, tegenover de generieke API-sleutelregel van gitleaks op dezelfde bestanden. Hij was preciezer en vond meer van de echte wachtwoorden, op gegevens die buiten het afstemmen waren gehouden (precisie 0,97 tegenover 0,95, recall 8 % tegenover 5 % van alle gelabelde wachtwoorden). Hij wordt alleen gemeld. Hij leest geen broncode, en een waarde tussen aanhalingstekens in YAML (password: "…") valt er niet onder: die vormen zijn ook gemeten en deden het niet beter dan de generieke regel.
Wachtwoorden gevonden door een classifier (de eigen organisaties van fremforge)
Een tweede stap leest code- en configuratiebestanden op hardgecodeerde wachtwoorden zonder tokenformaat, zoals db_password = "…" in Python of password: "…" in YAML. Die draait vandaag alleen op de eigen organisaties van fremforge. Hij is nog niet beschikbaar voor organisaties van klanten; daarover wordt apart besloten na twee weken aan gegevens over vals-positieven.
Hij werkt in twee stappen, na de regels hierboven, in de controle op pull requests:
- Een trefwoordfilter houdt alleen de regels over die een wachtwoord noemen of
user:password@in een URL bevatten, ongeveer 1 % van de regels. - Een machinelearning-classifier geeft elk van die regels een score. Het is het model van CredSweeper (Samsung, MIT-licentie), opnieuw getraind door fremforge. Het draait in de scanner zonder netwerktoegang, en er verlaat geen code of waarde de scanner.
Een bevinding heeft het regel-id fremforge-password-ml. Een bevinding in een testpad (test/, __tests__/, *.test.ts, test_*.py, *_test.go en vergelijkbaar) wordt bewaard met het regel-id fremforge-password-ml-test-path en gelabeld als waarschijnlijk een fixture. Beide worden alleen gemeld.
Hij laat weg:
- documentatie, databestanden en alles wat geen code of configuratie is;
- vertaalcatalogi;
- constanten die een reguliere expressie over wachtwoorden bevatten, zoals
PASSWORD_RE; - regels die al door een andere regel zijn gemeld.
Als de classifier niet kan draaien of langer dan 120 seconden duurt, meldt de controle zoals gewoonlijk de bevindingen van de andere regels.
Hoe het is gemeten. We hebben het model opnieuw getraind op de ontwikkelrepository’s van CredData, alleen die onder een permissieve licentie, en het één keer gescoord op repository’s die buiten de training waren gehouden. De drempel lag vast voordat die score werd bepaald.
| Bestanden | Precisie | Recall |
|---|---|---|
| Code (waarden tussen aanhalingstekens) | 0,94 | 0,98 |
| Configuratie | 0,96 | 0,99 |
Wachtwoorden in URL’s en in andere bestanden haalden geen precisie van 0,9 en worden niet gemeld. De trainingsrepository’s en hun licenties staan in het bestand NOTICE-training-data.md van de scanner.
Regels die alleen melden
cloudsmith-api-key, fremforge-password-config, fremforge-password-ml en fremforge-password-ml-test-path worden alleen gemeld. Hun bevindingen staan onder Beveiliging › Bevindingen › Secretscan, en die van de twee gitleaks-regels ook in de doorzoeking van de geschiedenis (de classifier draait alleen in de controle op pull requests). In een pull request verschijnen ze in fremforge/secret-scan als advisory (not blocking): ze laten de controle nooit falen en blokkeren nooit een push. Elke andere regel blokkeert.
De instelling
Pull requests blokkeren die een secret toevoegen bepaalt of de controle op pull requests draait. Zolang een organisatie geen keuze heeft opgeslagen, draait de controle en blokkeert ze. Staat de instelling uit, dan draait de controle niet en wordt haar status uit de branchbescherming verwijderd; push-bescherming en de wekelijkse doorzoeking blijven draaien. Stel het in onder Beveiliging › Bevindingen › Secretscan of met PUT /_app/api/v1/orgs/{org}/code-security/secrets/settings en {"enabled": true} (policy:write).
Eigen patronen
Je organisatie kan eigen secretformaten toevoegen: een tokenvorm die alleen jij uitgeeft, een intern sleutelvoorvoegsel, een inloggegeven van een partner. Elk patroon wordt een extra gitleaks-regel op alle drie de plekken hierboven, dus een match wordt geblokkeerd bij het pushen, laat de controle op pull requests falen en wordt gevonden in de geschiedenis. Een bevinding van een eigen patroon heeft het regel-id custom-<name> en toont de naam van het patroon.
Voeg ze toe onder Beveiliging › Beleid › Push-bescherming › Eigen geheimpatronen, of met POST /_app/api/v1/orgs/{org}/code-security/secrets/custom-patterns (policy:write; GET toont de lijst, PUT en DELETE op /custom-patterns/{id} wijzigen en verwijderen er een).
| Veld | Verplicht | Wat het doet |
|---|---|---|
name | ja | Wordt bij elke bevinding getoond. Hoogstens 80 tekens. |
regex | ja | RE2-syntaxis, hoogstens 300 tekens. |
keywords | nee | Hoogstens 10. Het patroon draait alleen op inhoud die een ervan bevat, wat pushes snel houdt. |
entropy | nee | 0 tot 8. Matches met minder willekeur dan dit, zoals placeholders, worden niet gemeld. |
allowlist_paths | nee | Hoogstens 20 padglobs (*, **, ?) waar het patroon niet wordt gemeld. Een glob matcht op elk mapniveau. |
repository | nee | Eén repository van de organisatie, op naam. Laat het weg voor elke repository. |
repository_id | nee | Hetzelfde, op repository-ID. Geef repository of repository_id, niet allebei. |
sample | nee | Een voorbeeld dat het patroon moet matchen. Wordt bij het opslaan gecontroleerd, nooit bewaard. |
Stuur "dry_run": true mee om te valideren zonder op te slaan.
Eén repository of allemaal. Een patroon zonder repository geldt voor elke repository van de organisatie. Een patroon met een repository geldt alleen voor die repository, op alle drie de plekken, samen met de patronen voor de hele organisatie. Kies in de console de repository uit de lijst, of Alle repository’s. Het patroon is gekoppeld aan de repository zelf, niet aan de naam: als je de repository hernoemt, blijft het patroon erop staan. Een repository die is verwijderd en later onder dezelfde naam opnieuw is aangemaakt, is een nieuwe repository en erft het patroon niet. Een patroon waarvan de repository is verwijderd of naar een andere organisatie is verplaatst, geldt nergens en wordt zo getoond totdat je het wijzigt of verwijdert. Antwoorden bevatten zowel repository_id als repository, de huidige naam van de repository.
# 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"Een geweigerd patroon krijgt 400 met een problem+json-body waarvan het type de reden noemt: invalid-repository, invalid-regex, regex-too-complex, regex-matches-empty, regex-too-broad, sample-not-matched, invalid-name, invalid-keywords, invalid-entropy of invalid-allowlist-paths. Een tweede patroon met dezelfde naam krijgt 409 duplicate-name, een 101e too-many-patterns, en een 21e zonder trefwoorden too-many-without-keywords.
Met de Terraform/OpenTofu-provider:
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)
}Waarom alleen RE2. Een patroon draait in de push-hook bij elke push. RE2 kent geen backtracking, dus de looptijd groeit lineair met de invoer en geen enkel patroon kan een push laten vastlopen. Backreferences (\1) en lookarounds ((?=, (?!, (?<=) hebben backtracking nodig en worden geweigerd. Een patroon wordt ook geweigerd als het:
- een lege string matcht, of gewone tekst zoals
const x = 1;; - meer dan 256 keer herhaalt (
{n,m}), of groepen meer dan 6 niveaus diep nest.
Een patroon testen voordat je het opslaat
Testen op repository’s in het toevoegformulier, of de dry-run-API, laat een kandidaatpatroon los op de laatste commit van de standaardbranch van elke repository en toont waar het zou matchen. Er wordt niets opgeslagen: het patroon wordt niet bewaard, er ontstaat geen bevinding, en het resultaat blijft 24 uur bewaard.
Het toont de repository, het bestand, de regel, de commit en de vingerafdruk van elke match, nooit de gematchte tekst. Elke repository wordt één keer geteld, in een van drie groepen:
- gecontroleerd;
- niet te controleren: klonen mislukt, time-out, scan mislukt, of niet bereikt binnen de limiet van 10 minuten;
- overgeslagen: gearchiveerd, een mirror, leeg, te groot (meer dan 100 MB, of een checkout van meer dan 256 MB), of boven de limiet van 200 repository’s.
Een patroon dat tot één repository is beperkt, wordt alleen op die repository getest.
# 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": "…"}], …}Beide vereisen policy:write, omdat het resultaat bestanden noemt in elke repository van de organisatie. Per organisatie draait er één dry run tegelijk, en er kan er één per 10 minuten starten. Een run die binnen 2 minuten door geen scanner wordt opgepakt, eindigt als failed met error: expired; start hem een paar minuten later opnieuw. Het starten wordt in het auditlog vastgelegd als code-security.secret_scan.custom_pattern.dry_run_started. Een geweigerde start krijgt dezelfde types als het aanmaken van een patroon, of dry-run-in-progress (409), dry-run-cooldown (429) of dry-run-unavailable (503).
Limieten. Een organisatie kan hoogstens 100 patronen hebben, die voor één repository meegerekend. Hoogstens 20 daarvan mogen geen keywords hebben. Het antwoord van de lijst geeft beide als limit en limit_without_keywords.
De reden is de duur van een push. Een patroon met trefwoorden draait alleen op inhoud die een ervan bevat, en kost dus bijna niets. Een patroon zonder trefwoorden draait op elk stuk van elke push. Gemeten met onze push-hook maakten 200 patronen zonder trefwoorden van een push van 100 commits er 17 seconden van in plaats van ongeveer 2. Een trefwoord is meestal het vaste voorvoegsel van het secret.
Het opslaan of verwijderen van een patroon zet een nieuwe scan van de volledige geschiedenis van elke repository in de wachtrij, zodat een nieuw patroon ook vindt wat al gecommit is. Dat duurt meestal tot 15 minuten. Wijzigingen worden in het auditlog vastgelegd als code-security.secret_scan.custom_pattern.created, .updated en .deleted.
Als de patronen bij het pushen niet kunnen worden geladen, bijvoorbeeld als de api even onbereikbaar is, wordt de push toch gescand met de ingebouwde regels, en legt een auditregel vast dat de eigen patronen niet zijn toegepast. De doorzoeking van de geschiedenis vindt dan wat is gemist zodra het de standaardbranch bereikt.
Geldigheidscontroles
Een secret dat nog werkt, is urgenter dan een dat al is geroteerd. Een geldigheidscontrole vraagt de dienst die een gevonden secret uitgaf of het nog actief is, en labelt de bevinding als Actief, Inactief of Onbekend. Actieve bevindingen staan bovenaan, in de console en in de API (validity, validity_checked_at).
Elke provider is een eigen instelling onder Beveiliging › Beleid › Push-bescherming › Geldigheidscontroles, of PUT /_app/api/v1/orgs/{org}/code-security/secrets/validity-settings/{provider} met {"enabled": true} (policy:write). Voordat je er een aanzet, noemt de pagina de host die de controle benadert en of de provider Door de VS beheerd, Door de EU beheerd, Door het VK beheerd of Door Canada beheerd wordt. De API meldt hetzelfde als operator_jurisdiction (us, eu, uk, ca, of null voor de interne controle). Het Verenigd Koninkrijk en Canada hebben allebei een adequaatheidsbesluit van de EU; geen enkele provider die vanuit een ander land buiten de EU wordt beheerd, wordt aangeboden op GET .../validity-settings.
Deze tabel is de lijst met providers waarnaar de verwerkersovereenkomst (bijlage B §B.10) verwijst. fremforge biedt alleen een controle aan voor een provider in deze lijst, telkens met één alleen-lezen verzoek naar een vaste host, of, waar een provider zijn sleutels vanaf regionale hosts bedient (SendGrid, Mailgun, Datadog, Intercom, Postman, Square, HCP Terraform, Typeform), naar een vaste lijst hosts die op volgorde worden bevraagd; je kunt geen eigen URL toevoegen. Een provider die later wordt toegevoegd, staat eerst uit.
| Provider | Gecontroleerde regels | Waar de controle heen gaat | Het verzoek | Beheerd | Standaard |
|---|---|---|---|---|---|
fremsh | API-tokens van frem.sh (ffp_) | Binnen frem.sh. Geen externe aanroep | De hash van het token tegen de tokenopslag van frem.sh | — | Aan |
github | GitHub-tokens | api.github.com | GET /rate_limit | Door de VS beheerd | Uit |
gitlab | Persoonlijke toegangstokens van GitLab.com (een token van een zelfbeheerde server leest Onbekend) | gitlab.com | GET /api/v4/personal_access_tokens/self | Door de VS beheerd | Uit |
aws | AWS-access keys | sts.amazonaws.com | GetCallerIdentity, waarvoor geen rechten nodig zijn | Door de VS beheerd | Uit |
slack | Slack-tokens | slack.com | POST /api/auth.test, zonder body | Door de VS beheerd | Uit |
stripe | Stripe-API-sleutels | api.stripe.com | GET /v1/balance | Door de VS beheerd | Uit |
hetzner | Hetzner Cloud-API-tokens | api.hetzner.cloud | GET /v1/locations | Door de EU beheerd | Uit |
scaleway | Geheime sleutels van Scaleway | api.scaleway.com | GET /account/v3/projects?page_size=1 | Door de EU beheerd | Uit |
ionos | IONOS Cloud-API-tokens | api.ionos.com | GET /cloudapi/v6/ | Door de EU beheerd | Uit |
npm | npm-toegangstokens | registry.npmjs.org | GET /-/whoami | Door de VS beheerd | Uit |
openai | OpenAI-API-sleutels | api.openai.com | GET /v1/models | Door de VS beheerd | Uit |
anthropic | Anthropic-API-sleutels (geen Admin API-sleutels) | api.anthropic.com | GET /v1/models | Door de VS beheerd | Uit |
huggingface | Hugging Face-toegangstokens (geen verouderde api_org_-tokens) | huggingface.co | GET /api/whoami-v2 | Door de VS beheerd | Uit |
digitalocean | DigitalOcean-tokens | api.digitalocean.com | GET /v2/account | Door de VS beheerd | Uit |
sendgrid | SendGrid-API-sleutels | api.sendgrid.com, daarna api.eu.sendgrid.com, tot er een antwoordt | GET /v3/scopes | Door de VS beheerd | Uit |
mailgun | Privé-API-sleutels van Mailgun | api.mailgun.net, daarna api.eu.mailgun.net, tot er een antwoordt | GET /v3/domains | Door de VS beheerd | Uit |
datadog | Datadog-API-sleutels | De zes sites van Datadog in deze volgorde, tot er een antwoordt: 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 | Door de VS beheerd | Uit |
azuredevops | Persoonlijke toegangstokens van Azure DevOps. Alleen Actief of Onbekend | app.vssps.visualstudio.com | GET /_apis/profile/profiles/me | Door de VS beheerd | Uit |
ovh | API-inloggegevens van OVHcloud. Alleen Actief of Onbekend | eu.api.ovh.com | GET /1.0/auth/currentCredential, ondertekend | Door de EU beheerd | Uit |
adafruit | Adafruit IO-sleutels | io.adafruit.com | GET /api/v2/user | Door de VS beheerd | Uit |
airtable | Persoonlijke toegangstokens van Airtable | api.airtable.com | GET /v0/meta/whoami | Door de VS beheerd | Uit |
asana | Persoonlijke toegangstokens van Asana | app.asana.com | GET /api/1.0/users/me | Door de VS beheerd | Uit |
doppler | Persoonlijke tokens van Doppler | api.doppler.com | GET /v3/me | Door de VS beheerd | Uit |
dropbox | Dropbox-toegangstokens | api.dropboxapi.com | POST /2/users/get_current_account, dat het account leest | Door de VS beheerd | Uit |
easypost | EasyPost-API-sleutels | api.easypost.com | GET /v2/addresses?page_size=1 | Door de VS beheerd | Uit |
fastly | Fastly-API-tokens | api.fastly.com | GET /tokens/self | Door de VS beheerd | Uit |
heroku | Heroku-API-sleutels | api.heroku.com | GET /account | Door de VS beheerd | Uit |
intercom | Intercom-toegangstokens | api.intercom.io, daarna api.eu.intercom.io, daarna api.au.intercom.io, tot er een antwoordt | GET /me | Door de VS beheerd | Uit |
lob | Lob-API-sleutels | api.lob.com | GET /v1/addresses?limit=1 | Door de VS beheerd | Uit |
notion | Integratietokens van Notion | api.notion.com | GET /v1/users/me | Door de VS beheerd | Uit |
postman | Postman-API-sleutels | api.getpostman.com, daarna api.eu.postman.com, tot er een antwoordt | GET /me | Door de VS beheerd | Uit |
readme | ReadMe-API-sleutels | dash.readme.com | GET /api/v1 | Door de VS beheerd | Uit |
rubygems | RubyGems-API-sleutels | rubygems.org | GET /api/v1/gems.json | Door de VS beheerd | Uit |
shippo | Shippo-API-tokens | api.goshippo.com | GET /addresses/?results=1 | Door de VS beheerd | Uit |
square | Square-toegangstokens | connect.squareup.com, daarna connect.squareupsandbox.com, tot er een antwoordt | GET /v2/merchants/me | Door de VS beheerd | Uit |
brevo | Brevo-API-sleutels (Sendinblue) | api.brevo.com | GET /v3/account | Door de EU beheerd | Uit |
gocardless | GoCardless-toegangstokens (live) | api.gocardless.com | GET /creditors?limit=1 | Door het VK beheerd | Uit |
duffel | Duffel-toegangstokens | api.duffel.com | GET /air/airlines?limit=1 | Door het VK beheerd | Uit |
cohere | Cohere-API-sleutels | api.cohere.com | POST /v1/check-api-key, de eigen sleutelcontrole van Cohere | Door Canada beheerd | Uit |
hcpterraform | API-tokens van HCP Terraform. Alleen Actief of Onbekend | app.terraform.io, daarna app.eu.terraform.io, tot er een antwoordt | GET /api/v2/account/details | Door de VS beheerd | Uit |
pulumi | Pulumi-toegangstokens. Alleen Actief of Onbekend | api.pulumi.com | GET /api/user | Door de VS beheerd | Uit |
sentry | Gebruikerstokens van Sentry. Alleen Actief of Onbekend | sentry.io | GET /api/0/ | Door de VS beheerd | Uit |
messagebird | MessageBird-toegangssleutels. Alleen Actief of Onbekend | rest.messagebird.com | GET /balance | Door de EU beheerd | Uit |
typeform | Persoonlijke toegangstokens van Typeform. Alleen Actief of Onbekend | api.typeform.com, daarna api.eu.typeform.com, tot er een antwoordt | GET /me | Door de EU beheerd | Uit |
Persoonlijke toegangstokens van Azure DevOps, Datadog-API-sleutels en persoonlijke toegangstokens van Asana worden gevonden door drie regels van fremforge, azure-devops-pat, datadog-api-key en asana-personal-access-token, die op alle drie de plekken hierboven draaien.
Sommige controles kunnen aantonen dat een secret actief is, nooit dat het dat niet is. Azure DevOps weigert een actief token dat tot één organisatie is beperkt of de profile-scope mist, dus een weigering daar leest Onbekend, nooit Inactief. Een verzoek aan OVHcloud moet worden ondertekend met de application key, het application secret en de consumer key samen. De controle draait alleen als de doorzoeking van de geschiedenis alle drie in hetzelfde bestand vindt, zoals een ovh.conf, en elk ander antwoord dan succes leest Onbekend. Tokens van HCP Terraform, Pulumi en Sentry kunnen horen bij een zelf gehoste Terraform Enterprise, Pulumi of Sentry, en worden dan door de openbare dienst geweigerd. Sleutels van het nieuwere Bird-platform kunnen worden geweigerd door de oorspronkelijke API van MessageBird. Een Typeform-token zonder de account-scope kan op dezelfde manier worden geweigerd als een dood token. Daarom lezen deze vijf Actief of Onbekend, nooit Inactief.
Een secret is alleen inactief als elke host dat zegt. Een SendGrid-sleutel die aan de EU-regio is gebonden, faalt op de globale host, een Datadog-sleutel hoort bij een van de sites van Datadog, en een sleutel van Intercom, Postman of Typeform bij een van hun regio’s; een sandbox-token van Square faalt in productie. Daarom bevraagt de controle de hosts in de getoonde volgorde en stopt bij de eerste die antwoordt dat het secret actief is. Ze leest alleen Inactief als elke host het weigert.
Geen automatische controle: Atlassian, Twilio, Flutterwave, Mailchimp, Telegram, zelfbeheerde GitLab en T Cloud. Hun bevindingen hebben alleen Zelf controleren. Een Atlassian-token heeft ook de site en het e-mailadres van het account nodig, en een Twilio-API-sleutel zijn SID. De Flutterwave-regel vindt alleen testsleutels. Een Mailchimp-sleutel noemt zijn eigen datacenterhost, dus er is geen vaste host om te controleren. Telegram wordt beheerd vanuit een land buiten de EU, het VK, Canada en de VS. Een zelfbeheerde GitLab-server, of een andere host die jij kiest, is een bestemming waarmee fremforge niet namens jou contact opneemt. T Cloud is de cloud waarop fremforge zelf draait, en de identiteitsdienst ervan is bewust niet bereikbaar voor de controles van fremforge. Voor een T Cloud-sleutel vraagt het commando OBS om je buckets te tonen: een onbekende sleutel krijgt InvalidAccessKeyId.
Een sleutel die actief is maar de leesactie van de controle niet mag uitvoeren, telt als Actief: een Stripe-sleutel die het saldo wordt geweigerd, een Scaleway-sleutel die de projecten wordt geweigerd, een Anthropic-sleutel die wordt geweigerd met permission_error, een OpenAI-sleutel die wordt geweigerd wegens ontbrekende rechten, een Dropbox-token dat wordt geweigerd met missing_scope, of een sleutel van Heroku, RubyGems, Square, Asana, GoCardless of Duffel die wordt geweigerd met 403. Voor de providers die op 2026-10-24 zijn toegevoegd, leest alleen het exacte antwoord dat elk ervan geeft op een sleutel die niet bestaat Inactief; elk ander antwoord leest Onbekend. invalid_api_key van OpenAI is het enige antwoord van OpenAI dat als Inactief wordt gelezen. Admin API-sleutels van Anthropic en verouderde organisatietokens van Hugging Face worden niet gecontroleerd, omdat de endpoints hierboven niet kunnen zien of ze actief zijn.
Hoe een controle werkt, en wat er wordt opgeslagen. Elke controle is alleen-lezen. Ze vraagt van wie een token is of of het geldig is, en verandert nooit iets bij de provider. Het secret wordt nog steeds nooit opgeslagen. Terwijl de doorzoeking van de geschiedenis een repository leest, versleutelt de scanner elke gematchte waarde met een sleutel die de api voor die ene scan aanmaakt. De api ontsleutelt de waarde, voert de controle uit, bewaart alleen de uitkomst en het tijdstip van de controle, en gooit de waarde weg. De sleutel wordt verwijderd als de scan eindigt. De scanner zelf heeft geen netwerktoegang tot de providers.
Een frem.sh-token wordt gecontroleerd tegen de eigen tokenopslag van frem.sh, en de status ervan wordt live gelezen: als je het token intrekt, staat de bevinding direct op Inactief. Voor de andere providers komt de status van de laatste doorzoeking die het secret las.
Onbekend betekent dat de provider geen duidelijk antwoord gaf, bijvoorbeeld door een time-out of een rate limit, een AWS-sleutel waarvan de geheime helft niet binnen vijf regels ervan is gevonden, of OVHcloud-inloggegevens waarvan de drie delen niet samen zijn gevonden. Onbekend wordt nooit als Inactief getoond.
Een provider aanzetten scant de geschiedenis van elke repository opnieuw, omdat de waarde van een gevonden secret alleen bestaat zolang de doorzoeking die leest. Een provider uitzetten wist de uitkomsten van die provider. Wijzigingen worden in het auditlog vastgelegd als code-security.secret_scan.validity.updated.
Zelf controleren, en dan markeren
Je hoeft geen geldigheidscontrole aan te zetten om te weten of een secret nog werkt. Voor een secret van een van de providers hierboven, of van Atlassian, Twilio, Flutterwave, Mailchimp, Telegram, een zelfbeheerde GitLab-server of T Cloud, heeft de bevinding Zelf controleren: het eigen alleen-lezen verzoek van de provider als commando om in je terminal uit te voeren, bijvoorbeeld voor een GitHub-token:
read -rs TOKEN && curl -s -o /dev/null -w '%{http_code}\n' -H "Authorization: Bearer $TOKEN" https://api.github.com/rate_limitHet commando bevat nooit het secret. Het wacht tot je het typt of plakt, toont het niet en houdt het uit je shellgeschiedenis. Het gaat alleen naar de provider, nooit naar fremforge. De bevinding zegt wat het commando afdrukt als het secret actief is (hier 200) en als het dat niet is (401).
Leg daarna vast wat je vond met Markeren als actief of Markeren als geroteerd in het menu van de bevinding, of met PUT /_app/api/v1/orgs/{org}/code-security/secrets/findings/{id}/mark en {"mark": "active"}, {"mark": "rotated"} of {"mark": null} (findings:write). Een bevinding die als actief is gemarkeerd, staat bij de actieve. Elke locatie wordt apart gemarkeerd: een markering hoort bij één bevinding (één bestand, regel en commit) en blijft behouden over doorzoekingen van de geschiedenis heen, net als het negeren van een bevinding. Is hetzelfde secret in twee commits gevonden, markeer dan beide. fremforge bewaart niets wat van het secret is afgeleid en de locaties aan elkaar zou kunnen koppelen. Een markering negeert de bevinding niet. Elke markering wordt in het auditlog vastgelegd als code-security.secrets.marked. De bevindingen-API geeft de markering terug als mark en het commando als self_check.
Standaard roteren, en welke eerst
Behandel elk secret dat hier wordt gevonden als gecompromitteerd, en roteer het, tenzij de provider of jij hebt bevestigd dat het niet meer werkt. Een secret uit de code verwijderen trekt het niet in: het blijft in de geschiedenis staan, waar iedereen met leesrechten het kan vinden.
Om te helpen bepalen welke je eerst roteert als geen provider heeft geantwoord, worden bevindingen gesorteerd op hoe waarschijnlijk het is dat het secret nog actief is. De schatting gebruikt alleen wat fremforge al weet, nooit het secret zelf, en neemt met niemand contact op:
| Signaal | Effect |
|---|---|
| Exact dezelfde regel staat nog in de huidige code van de standaardbranch | Waarschijnlijk actief |
| Push-bescherming heeft in de afgelopen 90 dagen hetzelfde soort secret in deze repository geblokkeerd | Waarschijnlijk actief |
| De regel is later op de standaardbranch gewijzigd of verwijderd | Mogelijk actief: verwijderen heeft het niet ingetrokken |
| De commit staat alleen op een andere branch dan de standaardbranch | Mogelijk actief |
| Gecommit in de afgelopen 90 dagen | Verhoogt de schatting |
| Meer dan twee jaar geleden gecommit, en niet in de huidige code | Minder waarschijnlijk actief |
Het eigen antwoord van een provider (Actief of Inactief) en je eigen markering gaan altijd voor. De schatting wordt getoond, met hoogstens twee van de redenen, bij elke bevinding waarop geen provider heeft geantwoord en die je niet hebt gemarkeerd. De signalen uit de code worden vastgelegd door de wekelijkse doorzoeking van de geschiedenis, dus een bevinding die vóór de doorzoeking die ze toevoegde is gevonden, toont tot de volgende doorzoeking alleen het signaal van push-bescherming. De bevindingen-API geeft de schatting terug als likely_live (level en reasons), samen met committed_at. Ze is null als een provider heeft geantwoord of de bevinding is gemarkeerd. Het is een schatting, geen controle: “Minder waarschijnlijk actief” betekent nog steeds roteren.
Wat er wordt opgeslagen
Alleen een vingerafdruk van de match wordt opgeslagen, nooit de gematchte tekst. De regel, het pad, het regelnummer en de commit zijn genoeg om het bestand bij die commit te openen. Een geldigheidscontrole voegt de uitkomst en het tijdstip toe. Voor een frem.sh-token wordt ook het id van het bijbehorende tokenrecord opgeslagen, nooit het token zelf.
Als er een secret wordt gevonden
Roteer het inloggegeven eerst bij de bron. De regel verwijderen maakt het lek niet ongedaan: wie de geschiedenis heeft, heeft de waarde nog. Haal het daarna uit de code: Fix-PR openen in de kolom Fix van de bevinding verplaatst een inloggegeven in Go, Python, JavaScript of TypeScript naar een omgevingsvariabele, zonder AI; zie Fixes op basis van regels. De pull request verwijdert de waarde niet uit de geschiedenis, dus roteren komt hoe dan ook eerst.