Secret-Scanning
fremforge sucht mit gitleaks an drei Stellen nach committeten Zugangsdaten:
| Wo | Was passiert | Lässt es sich ausschalten? |
|---|---|---|
| Push-Schutz | Ein Pre-Receive-Hook scannt jeden Push und weist ihn sofort ab | Nein |
| Pull-Request-Check | fremforge/secret-scan lässt einen Pull Request durchfallen, der ein Secret hinzufügt, auch in Archiven und Git-LFS-Dateien | Ja, pro Organisation |
| Durchsuchen der Historie | Einmal pro Woche liest gitleaks git die vollständige Historie jedes Repositorys und listet auf, was bereits committet ist | Nein |
Die Befunde aller drei stehen unter Sicherheit › Befunde › Secret-Scanning (/<org>/_admin/security/findings/secrets) und unter GET /_app/api/v1/orgs/{org}/code-security/secrets/findings.
Ein Secret, das bereits auf dem Branch liegt, in den der Pull Request gemergt wird, lässt den Check nicht durchfallen; siehe Pull-Request-Checks: Nur neue Befunde blockieren. Es wird trotzdem aufgeführt, und das wöchentliche Durchsuchen und der Push-Schutz bleiben unverändert.
Europäische Cloud-Anbieter
Die eigenen Regeln von gitleaks decken mehrere hundert Formate von Zugangsdaten ab, aber keines der europäischen Cloud-Anbieter. fremforge ergänzt Regeln für sie, und sie laufen an allen drei oben genannten Stellen:
| Anbieter | Regel-ID | Was sie findet |
|---|---|---|
| Hetzner Cloud | hetzner-cloud-api-token | Ein API-Token unter einem Namen mit HCLOUD_ oder HETZNER_, einschließlich TF_VAR_hcloud_token |
| Scaleway | scaleway-secret-key | Einen Secret Key unter einem Namen mit SCW_ oder SCALEWAY_ |
| Scaleway | scaleway-key-pair | Einen Access Key, gefolgt von seinem secret_key, wie in ~/.config/scw/config.yaml |
| OVHcloud | ovh-application-secret | OVH_APPLICATION_SECRET, OVH_CONSUMER_KEY oder OVH_CLIENT_SECRET |
| OVHcloud | ovh-conf-application-secret | application_secret, consumer_key oder client_secret in einer ovh.conf-Datei |
| IONOS Cloud | ionos-cloud-token | Ein Token unter einem Namen mit IONOS_ |
| T Cloud / Open Telekom Cloud | otc-secret-access-key | Einen Secret Access Key unter einem Secret-Key-Namen mit OTC_, OS_, HW_ oder OBS_, oder unter einem *_SK-Namen, der einen davon enthält |
| T Cloud / Open Telekom Cloud | otc-terraform-provider-secret-key | Einen secret_key, der direkt in einen opentelekomcloud-Provider-Block geschrieben ist |
| T Cloud / Open Telekom Cloud | otc-credentials-csv | Die credentials.csv, die die Konsole beim Anlegen eines Access Keys herunterlädt |
Diese Tokens haben kein festes Präfix, daher trifft eine Regel nur dort zu, wo der Name des Anbieters oder das Dateiformat sagt, was der Wert ist. Eine bloße Zeichenkette aus 64 Zeichen oder eine UUID an anderer Stelle wird nicht gemeldet. Platzhalter (xxxx…, die Null-UUID) und Variablenverweise (${HCLOUD_TOKEN}) werden ignoriert. Für Zugangsdaten, die diese Regeln nicht abdecken, füge ein eigenes Muster hinzu.
Weitere Detektoren
fremforge ergänzt über die eigenen Regeln von gitleaks hinaus auch Regeln für diese Zugangsdaten:
| Anbieter | Regel-ID | Was sie findet | Beim Push |
|---|---|---|---|
| Supabase | supabase-access-token | Ein Personal Access Token, klassisch oder mit Scopes, oder ein OAuth-Access-Token: sbp_, sbp_oauth_ oder sbp_v0_, gefolgt von 40 Hexadezimalzeichen | Blockiert |
| Supabase | supabase-secret-key | Einen Secret Key eines Projekts (sb_secret_…), der Row Level Security umgeht. Publishable Keys (sb_publishable_…) sind für Browser gedacht und werden nicht gemeldet, ebenso wenig der veröffentlichte lokale Standardschlüssel der Supabase CLI | Blockiert |
| Lovable | lovable-api-key | Einen API-Schlüssel eines Workspaces (lov_…) | Blockiert |
| Pydantic | pydantic-logfire-token | Ein Logfire-Schreib-Token oder einen API-Schlüssel, oder einen Schlüssel für das Pydantic AI Gateway (pylf_v…) | Blockiert |
| Cloudsmith | cloudsmith-api-key | Einen API-Schlüssel: 40 Hexadezimalzeichen unter einem Cloudsmith-Namen wie CLOUDSMITH_API_KEY | Nur gemeldet |
Jede Regel trifft auf das feste Präfix oder Format des Tokens zu, mit Grenzen für Länge und Entropie, sodass sie beim Push wie jede andere Regel blockiert und die Wege für Ausnahmen und Umgehungen für sie gelten. Ein Cloudsmith-Schlüssel hat kein Präfix, daher wird seine Regel nur gemeldet. Ein Cloudsmith-Schlüssel, der einem Namen wie CLOUDSMITH_API_KEY zugewiesen ist, wird meist ohnehin blockiert, durch die generische API-Key-Regel von gitleaks.
Das sind reine Erkennungsregeln. fremforge bietet für sie keine Gültigkeitsprüfung an.
Passwörter in Konfigurationsdateien
fremforge-password-config findet ein Passwort, das direkt in eine Konfigurations- oder Skriptdatei geschrieben ist, zum Beispiel DB_PASSWORD=… in einer .env-Datei, password: … in YAML, spring.datasource.password=… oder ENV REDIS_PASS=… in einem Dockerfile. Sie liest .env-Dateien, YAML, .properties, .ini, .conf, .cfg, .toml, Shell-, PowerShell- und Batch-Skripte, Dockerfiles und Compose-Dateien. Sie sucht nach einem Schlüssel, der nach einem Passwort benannt ist (password, passwd, passphrase, pwd, pass), mit einem bloßen Wert, der die Zeile beendet. Sie lässt aus:
- Verweise auf einen anderen Wert (
${DB_PASSWORD},%(password)s,{{ … }}); - Platzhalter (
changeme,example,xxx); - Übersetzungskataloge;
- Einstellungen, die einen Hash oder einen Algorithmus nennen (
root_password_sha2,password.encoding.alg).
Sie ist eine Heuristik, kein Tokenformat. Wir haben sie auf CredData gemessen, einem öffentlichen, gelabelten Datensatz von Zugangsdaten in Open-Source-Repositorys, im Vergleich zur generischen API-Key-Regel von gitleaks auf denselben Dateien. Auf Daten, die von ihrer Abstimmung zurückgehalten wurden, war sie präziser und fand mehr der echten Passwörter (Precision 0,97 gegenüber 0,95, Recall 8 % gegenüber 5 % aller gelabelten Passwörter). Sie wird nur gemeldet. Sie liest keinen Quellcode, und ein Wert in Anführungszeichen in YAML (password: "…") ist nicht abgedeckt: Auch diese Formen wurden gemessen und schnitten nicht besser ab als die generische Regel.
Passwörter, die ein Klassifikator findet (nur fremforges eigene Organisationen)
Eine zweite Stufe liest Code- und Konfigurationsdateien nach fest eingetragenen Passwörtern, die kein Tokenformat haben, etwa db_password = "…" in Python oder password: "…" in YAML. Sie läuft derzeit nur in fremforges eigenen Organisationen. Sie ist für Kundenorganisationen noch nicht verfügbar; darüber wird nach zwei Wochen mit Daten zu Fehlalarmen gesondert entschieden.
Sie arbeitet in zwei Schritten, hinter den obigen Regeln, im Pull-Request-Check:
- Ein Schlüsselwortfilter behält nur die Zeilen, die ein Passwort erwähnen oder
user:password@in einer URL enthalten, etwa 1 % der Zeilen. - Ein Klassifikator auf Basis von maschinellem Lernen bewertet jede dieser Zeilen. Es ist das Modell von CredSweeper (Samsung, MIT-Lizenz), von fremforge neu trainiert. Es läuft im Scanner ohne Netzwerkzugang, und weder Code noch Werte verlassen ihn.
Ein Befund trägt die Regel-ID fremforge-password-ml. Ein Befund in einem Testpfad (test/, __tests__/, *.test.ts, test_*.py, *_test.go und ähnliche) wird mit der Regel-ID fremforge-password-ml-test-path behalten und als wahrscheinliches Fixture gekennzeichnet. Beide werden nur gemeldet.
Er lässt aus:
- Dokumentation, Datendateien und alles, was weder Code noch Konfiguration ist;
- Übersetzungskataloge;
- Konstanten, die einen regulären Ausdruck über Passwörter enthalten, etwa
PASSWORD_RE; - Zeilen, die bereits von einer anderen Regel gemeldet wurden.
Wenn der Klassifikator nicht laufen kann oder länger als 120 Sekunden braucht, meldet der Check die Befunde der übrigen Regeln wie gewohnt.
Wie gemessen wurde. Wir haben das Modell auf den Entwicklungs-Repositorys von CredData neu trainiert, und zwar nur auf denen unter einer permissiven Lizenz, und es einmal auf Repositorys bewertet, die vom Training zurückgehalten wurden. Der Schwellenwert war festgelegt, bevor diese Bewertung erfolgte.
| Dateien | Precision | Recall |
|---|---|---|
| Code (Werte in Anführungszeichen) | 0,94 | 0,98 |
| Konfiguration | 0,96 | 0,99 |
Passwörter in URLs und in anderen Dateien erreichten keine Precision von 0,9 und werden nicht gemeldet. Die Trainings-Repositorys und ihre Lizenzen stehen in NOTICE-training-data.md des Scanners.
Regeln, die nur gemeldet werden
cloudsmith-api-key, fremforge-password-config, fremforge-password-ml und fremforge-password-ml-test-path werden nur gemeldet. Ihre Befunde stehen unter Sicherheit › Befunde › Secret-Scanning, und die beiden gitleaks-Regeln auch beim Durchsuchen der Historie (der Klassifikator läuft nur im Pull-Request-Check). In einem Pull Request erscheinen sie in fremforge/secret-scan als advisory (not blocking): Sie lassen den Check nie durchfallen und blockieren nie einen Push. Jede andere Regel blockiert.
Die Einstellung
Pull Requests blockieren, die ein Secret hinzufügen legt fest, ob der Pull-Request-Check läuft. Solange eine Organisation keine Wahl gespeichert hat, läuft der Check und blockiert. Ist die Einstellung aus, läuft der Check nicht, und sein Status wird aus dem Branch-Schutz entfernt; Push-Schutz und das wöchentliche Durchsuchen laufen weiter. Stelle sie unter Sicherheit › Befunde › Secret-Scanning ein oder mit PUT /_app/api/v1/orgs/{org}/code-security/secrets/settings und {"enabled": true} (policy:write).
Eigene Muster
Deine Organisation kann eigene Secret-Formate hinzufügen: eine Token-Form, die nur du ausgibst, ein internes Schlüsselpräfix, die Zugangsdaten eines Partners. Jedes Muster wird zu einer zusätzlichen gitleaks-Regel an allen drei oben genannten Stellen, sodass ein Treffer beim Push blockiert wird, den Pull-Request-Check durchfallen lässt und in der Historie gefunden wird. Ein Befund aus einem eigenen Muster trägt die Regel-ID custom-<name> und zeigt den Namen des Musters.
Füge sie unter Sicherheit › Richtlinien › Push-Schutz › Eigene Secret-Muster hinzu oder mit POST /_app/api/v1/orgs/{org}/code-security/secrets/custom-patterns (policy:write; GET listet sie auf, PUT und DELETE auf /custom-patterns/{id} ändern und entfernen eines).
| Feld | Pflicht | Was es tut |
|---|---|---|
name | ja | Wird bei jedem Befund angezeigt. Bis zu 80 Zeichen. |
regex | ja | RE2-Syntax, bis zu 300 Zeichen. |
keywords | nein | Bis zu 10. Das Muster läuft nur auf Inhalten, die eines davon enthalten, was Pushes schnell hält. |
entropy | nein | 0 bis 8. Treffer mit weniger Zufälligkeit als diesem Wert, etwa Platzhalter, werden nicht gemeldet. |
allowlist_paths | nein | Bis zu 20 Pfad-Globs (*, **, ?), in denen das Muster nicht gemeldet wird. Ein Glob trifft auf jeder Verzeichnisebene zu. |
repository | nein | Ein Repository der Organisation, nach Name. Lass es weg für alle Repositorys. |
repository_id | nein | Dasselbe, nach Repository-ID. Gib repository oder repository_id an, nicht beides. |
sample | nein | Ein Beispiel, auf das das Muster zutreffen muss. Wird beim Speichern geprüft, nie gespeichert. |
Sende "dry_run": true, um ohne Speichern zu validieren.
Ein Repository oder alle. Ein Muster ohne Repository gilt für jedes Repository der Organisation. Ein Muster mit Repository gilt nur für dieses Repository, an allen drei Stellen, zusammen mit den organisationsweiten. Wähle in der Konsole das Repository aus der Liste oder Alle Repositorys. Das Muster ist an das Repository selbst gebunden, nicht an seinen Namen: Eine Umbenennung des Repositorys behält das Muster bei. Ein Repository, das gelöscht und später unter demselben Namen neu angelegt wird, ist ein neues Repository und erbt es nicht. Ein Muster, dessen Repository gelöscht oder in eine andere Organisation verschoben wurde, gilt nirgends und wird als solches angezeigt, bis du es änderst oder löschst. Antworten enthalten sowohl repository_id als auch repository, den aktuellen Namen des Repositorys.
# 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"Ein abgelehntes Muster antwortet mit 400 und einem problem+json-Body, dessen type den Grund nennt: invalid-repository, invalid-regex, regex-too-complex, regex-matches-empty, regex-too-broad, sample-not-matched, invalid-name, invalid-keywords, invalid-entropy oder invalid-allowlist-paths. Ein zweites Muster mit demselben Namen antwortet mit 409 duplicate-name, ein 101. mit too-many-patterns und ein 21. ohne Schlüsselwörter mit too-many-without-keywords.
Mit dem 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)
}Warum nur RE2. Ein Muster läuft bei jedem Push im Push-Hook. RE2 kennt kein Backtracking, daher wächst seine Laufzeit linear mit der Eingabe, und kein Muster kann einen Push zum Stehen bringen. Rückverweise (\1) und Lookarounds ((?=, (?!, (?<=) brauchen Backtracking und werden abgelehnt. Ein Muster wird außerdem abgelehnt, wenn es:
- auf eine leere Zeichenkette oder auf gewöhnlichen Text wie
const x = 1;zutrifft; - mehr als 256 Wiederholungen hat (
{n,m}) oder Gruppen tiefer als 6 Ebenen verschachtelt.
Ein Muster vor dem Speichern testen
Auf Repositorys testen im Formular zum Hinzufügen, oder die Testlauf-API, führt ein Kandidatenmuster über den neuesten Commit des Standard-Branches jedes Repositorys aus und zeigt, wo es treffen würde. Nichts wird gespeichert: Das Muster wird nicht abgelegt, es entsteht kein Befund, und das Ergebnis bleibt 24 Stunden abrufbar.
Angezeigt werden Repository, Datei, Zeile, Commit und Fingerabdruck jedes Treffers, nie der gefundene Text. Jedes Repository wird genau einmal gezählt, in einer von drei Gruppen:
- geprüft;
- nicht prüfbar: Klonen fehlgeschlagen, Zeitüberschreitung, Scan fehlgeschlagen oder innerhalb des 10-Minuten-Limits nicht erreicht;
- übersprungen: archiviert, ein Spiegel, leer, zu groß (über 100 MB oder ein Checkout über 256 MB) oder über dem Limit von 200 Repositorys.
Ein Muster, das auf ein Repository beschränkt ist, wird nur auf diesem Repository getestet.
# 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 brauchen policy:write, weil das Ergebnis Dateien in jedem Repository der Organisation nennt. Pro Organisation läuft jeweils ein Testlauf, und alle 10 Minuten kann einer gestartet werden. Ein Lauf, den innerhalb von 2 Minuten kein Scanner übernimmt, endet als failed mit error: expired; starte ihn ein paar Minuten später erneut. Der Start wird im Audit-Log als code-security.secret_scan.custom_pattern.dry_run_started erfasst. Ein abgelehnter Start antwortet mit denselben Typen wie das Anlegen eines Musters oder mit dry-run-in-progress (409), dry-run-cooldown (429) oder dry-run-unavailable (503).
Limits. Eine Organisation kann bis zu 100 Muster haben, einschließlich der auf ein Repository beschränkten. Höchstens 20 davon dürfen keine keywords haben. Die Antwort der Liste nennt beide Werte als limit und limit_without_keywords.
Der Grund ist die Dauer eines Pushes. Ein Muster mit Schlüsselwörtern läuft nur auf Inhalten, die eines davon enthalten, und kostet daher fast nichts. Ein Muster ohne Schlüsselwörter läuft auf jedem Teil jedes Pushes. Gemessen mit unserem Push-Hook verlängerten 200 Muster ohne Schlüsselwörter einen Push mit 100 Commits von etwa 2 auf 17 Sekunden. Ein Schlüsselwort ist meist das feste Präfix des Secrets.
Das Speichern oder Löschen eines Musters stellt einen erneuten Scan der vollständigen Historie jedes Repositorys in die Warteschlange, sodass ein neues Muster auch findet, was bereits committet ist. Das dauert meist bis zu 15 Minuten. Änderungen werden im Audit-Log als code-security.secret_scan.custom_pattern.created, .updated und .deleted erfasst.
Wenn die Muster beim Push nicht geladen werden können, etwa während die API kurz nicht erreichbar ist, wird der Push trotzdem mit den eingebauten Regeln gescannt, und eine Audit-Zeile hält fest, dass die eigenen Muster nicht angewendet wurden. Das Durchsuchen der Historie findet dann, was übersehen wurde, sobald es den Standard-Branch erreicht.
Gültigkeitsprüfungen
Ein Secret, das noch funktioniert, ist dringender als eines, das bereits rotiert wurde. Eine Gültigkeitsprüfung fragt den Dienst, der ein gefundenes Secret ausgegeben hat, ob es noch aktiv ist, und kennzeichnet den Befund als Aktiv, Inaktiv oder Unbekannt. Aktive Befunde stehen zuerst, in der Konsole und in der API (validity, validity_checked_at).
Jeder Anbieter ist eine eigene Einstellung unter Sicherheit › Richtlinien › Push-Schutz › Gültigkeitsprüfungen oder PUT /_app/api/v1/orgs/{org}/code-security/secrets/validity-settings/{provider} mit {"enabled": true} (policy:write). Bevor du einen einschaltest, nennt die Seite den Host, den die Prüfung kontaktiert, und ob der Anbieter US-betrieben, EU-betrieben, UK-betrieben oder Kanada-betrieben ist. Die API meldet dasselbe als operator_jurisdiction (us, eu, uk, ca oder null für die interne Prüfung). Für das Vereinigte Königreich und Kanada gibt es jeweils einen Angemessenheitsbeschluss der EU; kein Anbieter, der aus einem anderen Land außerhalb der EU betrieben wird, wird unter GET .../validity-settings angeboten.
Diese Tabelle ist die Liste der Anbieter, auf die der Auftragsverarbeitungsvertrag (Anhang B §B.10) verweist. fremforge bietet eine Prüfung nur für einen Anbieter darin an, jeweils mit einer lesenden Anfrage an einen festen Host, oder, wo ein Anbieter seine Schlüssel über regionale Hosts bedient (SendGrid, Mailgun, Datadog, Intercom, Postman, Square, HCP Terraform, Typeform), an eine feste Liste von Hosts, die der Reihe nach gefragt werden; eine eigene URL kannst du nicht hinzufügen. Ein später hinzugefügter Anbieter startet ausgeschaltet.
| Anbieter | Geprüfte Regeln | Wohin die Prüfung geht | Die Anfrage | Betrieben | Standard |
|---|---|---|---|---|---|
fremsh | frem.sh-API-Tokens (ffp_) | Innerhalb von frem.sh. Kein externer Aufruf | Der Hash des Tokens gegen den Token-Speicher von frem.sh | — | An |
github | GitHub-Tokens | api.github.com | GET /rate_limit | US-betrieben | Aus |
gitlab | Personal Access Tokens von GitLab.com (ein Token eines selbst verwalteten Servers wird als Unbekannt gelesen) | gitlab.com | GET /api/v4/personal_access_tokens/self | US-betrieben | Aus |
aws | AWS-Access-Keys | sts.amazonaws.com | GetCallerIdentity, das keine Berechtigung braucht | US-betrieben | Aus |
slack | Slack-Tokens | slack.com | POST /api/auth.test, ohne Body | US-betrieben | Aus |
stripe | Stripe-API-Schlüssel | api.stripe.com | GET /v1/balance | US-betrieben | Aus |
hetzner | Hetzner-Cloud-API-Tokens | api.hetzner.cloud | GET /v1/locations | EU-betrieben | Aus |
scaleway | Scaleway-Secret-Keys | api.scaleway.com | GET /account/v3/projects?page_size=1 | EU-betrieben | Aus |
ionos | IONOS-Cloud-API-Tokens | api.ionos.com | GET /cloudapi/v6/ | EU-betrieben | Aus |
npm | npm-Access-Tokens | registry.npmjs.org | GET /-/whoami | US-betrieben | Aus |
openai | OpenAI-API-Schlüssel | api.openai.com | GET /v1/models | US-betrieben | Aus |
anthropic | Anthropic-API-Schlüssel (keine Admin-API-Schlüssel) | api.anthropic.com | GET /v1/models | US-betrieben | Aus |
huggingface | Hugging-Face-Access-Tokens (keine veralteten api_org_-Tokens) | huggingface.co | GET /api/whoami-v2 | US-betrieben | Aus |
digitalocean | DigitalOcean-Tokens | api.digitalocean.com | GET /v2/account | US-betrieben | Aus |
sendgrid | SendGrid-API-Schlüssel | api.sendgrid.com, dann api.eu.sendgrid.com, bis einer antwortet | GET /v3/scopes | US-betrieben | Aus |
mailgun | Private Mailgun-API-Schlüssel | api.mailgun.net, dann api.eu.mailgun.net, bis einer antwortet | GET /v3/domains | US-betrieben | Aus |
datadog | Datadog-API-Schlüssel | Die sechs Sites von Datadog in dieser Reihenfolge, bis eine antwortet: 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 | US-betrieben | Aus |
azuredevops | Personal Access Tokens von Azure DevOps. Nur Aktiv oder Unbekannt | app.vssps.visualstudio.com | GET /_apis/profile/profiles/me | US-betrieben | Aus |
ovh | OVHcloud-API-Zugangsdaten. Nur Aktiv oder Unbekannt | eu.api.ovh.com | GET /1.0/auth/currentCredential, signiert | EU-betrieben | Aus |
adafruit | Adafruit-IO-Schlüssel | io.adafruit.com | GET /api/v2/user | US-betrieben | Aus |
airtable | Personal Access Tokens von Airtable | api.airtable.com | GET /v0/meta/whoami | US-betrieben | Aus |
asana | Personal Access Tokens von Asana | app.asana.com | GET /api/1.0/users/me | US-betrieben | Aus |
doppler | Persönliche Doppler-Tokens | api.doppler.com | GET /v3/me | US-betrieben | Aus |
dropbox | Dropbox-Access-Tokens | api.dropboxapi.com | POST /2/users/get_current_account, das das Konto liest | US-betrieben | Aus |
easypost | EasyPost-API-Schlüssel | api.easypost.com | GET /v2/addresses?page_size=1 | US-betrieben | Aus |
fastly | Fastly-API-Tokens | api.fastly.com | GET /tokens/self | US-betrieben | Aus |
heroku | Heroku-API-Schlüssel | api.heroku.com | GET /account | US-betrieben | Aus |
intercom | Intercom-Access-Tokens | api.intercom.io, dann api.eu.intercom.io, dann api.au.intercom.io, bis einer antwortet | GET /me | US-betrieben | Aus |
lob | Lob-API-Schlüssel | api.lob.com | GET /v1/addresses?limit=1 | US-betrieben | Aus |
notion | Notion-Integrations-Tokens | api.notion.com | GET /v1/users/me | US-betrieben | Aus |
postman | Postman-API-Schlüssel | api.getpostman.com, dann api.eu.postman.com, bis einer antwortet | GET /me | US-betrieben | Aus |
readme | ReadMe-API-Schlüssel | dash.readme.com | GET /api/v1 | US-betrieben | Aus |
rubygems | RubyGems-API-Schlüssel | rubygems.org | GET /api/v1/gems.json | US-betrieben | Aus |
shippo | Shippo-API-Tokens | api.goshippo.com | GET /addresses/?results=1 | US-betrieben | Aus |
square | Square-Access-Tokens | connect.squareup.com, dann connect.squareupsandbox.com, bis einer antwortet | GET /v2/merchants/me | US-betrieben | Aus |
brevo | Brevo-API-Schlüssel (Sendinblue) | api.brevo.com | GET /v3/account | EU-betrieben | Aus |
gocardless | GoCardless-Access-Tokens (live) | api.gocardless.com | GET /creditors?limit=1 | UK-betrieben | Aus |
duffel | Duffel-Access-Tokens | api.duffel.com | GET /air/airlines?limit=1 | UK-betrieben | Aus |
cohere | Cohere-API-Schlüssel | api.cohere.com | POST /v1/check-api-key, die eigene Schlüsselprüfung von Cohere | Kanada-betrieben | Aus |
hcpterraform | HCP-Terraform-API-Tokens. Nur Aktiv oder Unbekannt | app.terraform.io, dann app.eu.terraform.io, bis einer antwortet | GET /api/v2/account/details | US-betrieben | Aus |
pulumi | Pulumi-Access-Tokens. Nur Aktiv oder Unbekannt | api.pulumi.com | GET /api/user | US-betrieben | Aus |
sentry | Sentry-User-Auth-Tokens. Nur Aktiv oder Unbekannt | sentry.io | GET /api/0/ | US-betrieben | Aus |
messagebird | MessageBird-Access-Keys. Nur Aktiv oder Unbekannt | rest.messagebird.com | GET /balance | EU-betrieben | Aus |
typeform | Personal Access Tokens von Typeform. Nur Aktiv oder Unbekannt | api.typeform.com, dann api.eu.typeform.com, bis einer antwortet | GET /me | EU-betrieben | Aus |
Personal Access Tokens von Azure DevOps, Datadog-API-Schlüssel und Personal Access Tokens von Asana werden von drei fremforge-Regeln gefunden, azure-devops-pat, datadog-api-key und asana-personal-access-token, die an allen drei oben genannten Stellen laufen.
Manche Prüfungen können zeigen, dass ein Secret aktiv ist, nie, dass es das nicht ist. Azure DevOps weist ein aktives Token zurück, das auf eine Organisation beschränkt ist oder dem der Profil-Scope fehlt, daher wird eine Ablehnung dort als Unbekannt gelesen, nie als Inaktiv. Eine Anfrage an OVHcloud muss mit Application Key, Application Secret und Consumer Key zusammen signiert werden. Die Prüfung läuft nur, wenn das Durchsuchen der Historie alle drei in derselben Datei findet, etwa einer ovh.conf, und jede Antwort außer Erfolg wird als Unbekannt gelesen. Tokens von HCP Terraform, Pulumi und Sentry können zu einem selbst gehosteten Terraform Enterprise, Pulumi oder Sentry gehören, das sie beim öffentlichen Dienst ablehnt. MessageBird-Schlüssel der neueren Bird-Plattform können von der ursprünglichen API von MessageBird abgelehnt werden. Ein Typeform-Token ohne den Konto-Scope wird möglicherweise genauso abgelehnt wie ein ungültiges. Diese fünf werden daher als Aktiv oder Unbekannt gelesen, nie als Inaktiv.
Ein Secret ist nur dann inaktiv, wenn jeder Host das sagt. Ein SendGrid-Schlüssel, der an die EU-Region gebunden ist, scheitert beim globalen Host, ein Datadog-Schlüssel gehört zu einer der Sites von Datadog und ein Schlüssel von Intercom, Postman oder Typeform zu einer ihrer Regionen; ein Sandbox-Token von Square scheitert in der Produktion. Die Prüfung fragt die Hosts daher in der angegebenen Reihenfolge und hört beim ersten auf, der antwortet, dass das Secret aktiv ist. Sie liest Inaktiv nur, wenn jeder Host es ablehnt.
Keine automatische Prüfung: Atlassian, Twilio, Flutterwave, Mailchimp, Telegram, selbst verwaltetes GitLab und T Cloud. Ihre Befunde haben nur Selbst prüfen. Ein Atlassian-Token braucht zusätzlich die Site und die E-Mail-Adresse des Kontos, und ein Twilio-API-Schlüssel braucht seine SID. Die Flutterwave-Regel findet nur Test-Schlüssel. Ein Mailchimp-Schlüssel nennt seinen eigenen Rechenzentrums-Host, daher gibt es keinen festen Host zum Prüfen. Telegram wird von außerhalb der EU, des Vereinigten Königreichs, Kanadas und der USA betrieben. Ein selbst verwalteter GitLab-Server oder jeder andere Host, den du wählst, ist ein Ziel, das fremforge nicht in deinem Namen kontaktiert. T Cloud ist die Cloud, auf der fremforge selbst läuft, und ihr Identitätsdienst ist für die Prüfungen von fremforge bewusst nicht erreichbar. Für einen T-Cloud-Schlüssel fragt der Befehl OBS nach einer Liste deiner Buckets: Ein Schlüssel, der nicht bekannt ist, antwortet mit InvalidAccessKeyId.
Ein Schlüssel, der aktiv ist, aber den Lesezugriff der Prüfung nicht durchführen darf, zählt als Aktiv: ein Stripe-Schlüssel, dem der Kontostand verweigert wird, ein Scaleway-Schlüssel, dem die Projekte verweigert werden, ein Anthropic-Schlüssel, der mit permission_error abgelehnt wird, ein OpenAI-Schlüssel, der wegen fehlender Berechtigungen abgelehnt wird, ein Dropbox-Token, das mit missing_scope abgelehnt wird, oder ein Schlüssel von Heroku, RubyGems, Square, Asana, GoCardless oder Duffel, der mit 403 abgelehnt wird. Bei den am 2026-10-24 hinzugefügten Anbietern wird nur die genaue Antwort, die jeder einem nicht existierenden Schlüssel gibt, als Inaktiv gelesen; jede andere Antwort wird als Unbekannt gelesen. invalid_api_key von OpenAI ist die einzige Antwort von OpenAI, die als Inaktiv gelesen wird. Admin-API-Schlüssel von Anthropic und veraltete Organisations-Tokens von Hugging Face werden nicht geprüft, weil die obigen Endpunkte nicht erkennen können, ob sie aktiv sind.
Wie eine Prüfung funktioniert und was gespeichert wird. Jede Prüfung ist rein lesend. Sie fragt, wem ein Token gehört oder ob es gültig ist, und ändert beim Anbieter nie etwas. Das Secret wird weiterhin nie gespeichert. Während das Durchsuchen der Historie ein Repository liest, verschlüsselt der Scanner jeden passenden Wert mit einem Schlüssel, den die API für genau diesen Scan erzeugt. Die API entschlüsselt ihn, führt die Prüfung aus, behält nur das Ergebnis und den Zeitpunkt der Prüfung und verwirft den Wert. Der Schlüssel wird gelöscht, wenn der Scan endet. Der Scanner selbst hat keinen Netzwerkzugang zu den Anbietern.
Ein frem.sh-Token wird gegen den eigenen Token-Speicher von frem.sh geprüft, und sein Zustand wird live gelesen: Wird das Token widerrufen, zeigt der Befund sofort Inaktiv. Bei den anderen Anbietern stammt der Zustand vom letzten Durchsuchen, das das Secret gelesen hat.
Unbekannt bedeutet, dass der Anbieter keine klare Antwort gegeben hat, zum Beispiel bei einer Zeitüberschreitung oder einem Rate Limit, bei einem AWS-Schlüssel, dessen geheime Hälfte nicht innerhalb von fünf Zeilen gefunden wurde, oder bei OVHcloud-Zugangsdaten, deren drei Teile nicht zusammen gefunden wurden. Unbekannt wird nie als Inaktiv angezeigt.
Das Einschalten eines Anbieters scannt die Historie jedes Repositorys erneut, weil der Wert eines gefundenen Secrets nur existiert, während das Durchsuchen ihn liest. Das Ausschalten löscht die Ergebnisse dieses Anbieters. Änderungen werden im Audit-Log als code-security.secret_scan.validity.updated erfasst.
Selbst prüfen und dann markieren
Du musst keine Gültigkeitsprüfung einschalten, um zu wissen, ob ein Secret noch funktioniert. Für ein Secret, das von einem der obigen Anbieter oder von Atlassian, Twilio, Flutterwave, Mailchimp, Telegram, einem selbst verwalteten GitLab-Server oder T Cloud ausgegeben wurde, hat der Befund Selbst prüfen: die eigene lesende Anfrage des Anbieters als Befehl, den du in deinem Terminal ausführst, zum Beispiel für ein GitHub-Token:
read -rs TOKEN && curl -s -o /dev/null -w '%{http_code}\n' -H "Authorization: Bearer $TOKEN" https://api.github.com/rate_limitDer Befehl enthält nie das Secret. Er wartet darauf, dass du es eintippst oder einfügst, zeigt es nicht an und hält es aus deiner Shell-Historie heraus. Es geht nur an den Anbieter, nie an fremforge. Der Befund sagt, was der Befehl ausgibt, wenn das Secret aktiv ist (hier 200), und wenn nicht (401).
Halte dann fest, was du herausgefunden hast, mit Als aktiv markieren oder Als rotiert markieren im Menü des Befunds, oder mit PUT /_app/api/v1/orgs/{org}/code-security/secrets/findings/{id}/mark und {"mark": "active"}, {"mark": "rotated"} oder {"mark": null} (findings:write). Ein als aktiv markierter Befund wird bei den aktiven aufgeführt. Jede Fundstelle wird einzeln markiert: Eine Markierung gehört zu einem Befund (eine Datei, Zeile und ein Commit) und bleibt über das Durchsuchen der Historie hinweg erhalten, wie eine Verwerfung. Wurde dasselbe Secret in zwei Commits gefunden, markiere beide. fremforge speichert nichts, was aus dem Secret abgeleitet ist und die Fundstellen miteinander verbinden könnte. Eine Markierung verwirft den Befund nicht. Jede Markierung wird im Audit-Log als code-security.secrets.marked erfasst. Die Befund-API liefert die Markierung als mark und den Befehl als self_check.
Standardmäßig rotieren, und was zuerst
Behandle jedes hier gefundene Secret als kompromittiert und rotiere es, es sei denn, sein Anbieter oder du selbst hast bestätigt, dass es nicht mehr funktioniert. Ein Secret aus dem Code zu entfernen, widerruft es nicht: Es bleibt in der Historie, wo jeder mit Lesezugriff es finden kann.
Damit du entscheiden kannst, was du zuerst rotierst, wenn kein Anbieter geantwortet hat, werden Befunde danach sortiert, wie wahrscheinlich das Secret noch aktiv ist. Die Schätzung nutzt nur, was fremforge bereits weiß, nie das Secret selbst, und kontaktiert niemanden:
| Signal | Wirkung |
|---|---|
| Die genaue Zeile steht noch im aktuellen Code des Standard-Branches | Wahrscheinlich aktiv |
| Der Push-Schutz hat in diesem Repository in den letzten 90 Tagen dieselbe Art von Secret blockiert | Wahrscheinlich aktiv |
| Die Zeile wurde später auf dem Standard-Branch geändert oder entfernt | Möglicherweise aktiv: Das Entfernen hat es nicht widerrufen |
| Der Commit liegt nur auf einem anderen Branch als dem Standard-Branch | Möglicherweise aktiv |
| In den letzten 90 Tagen committet | Erhöht die Schätzung |
| Vor mehr als zwei Jahren committet und nicht im aktuellen Code | Weniger wahrscheinlich aktiv |
Die eigene Antwort eines Anbieters (Aktiv oder Inaktiv) und deine eigene Markierung haben immer Vorrang. Die Schätzung wird mit bis zu zwei ihrer Gründe bei jedem Befund angezeigt, auf den kein Anbieter geantwortet hat und den du nicht markiert hast. Die Signale aus dem Code erfasst das wöchentliche Durchsuchen der Historie, daher zeigt ein Befund, der vor dem Durchlauf gefunden wurde, der sie eingeführt hat, bis zum nächsten nur das Signal aus dem Push-Schutz. Die Befund-API liefert sie als likely_live (level und reasons), zusammen mit committed_at. Der Wert ist null, wenn ein Anbieter geantwortet hat oder der Befund markiert ist. Es ist eine Schätzung, keine Prüfung: Auch „Weniger wahrscheinlich aktiv" heißt rotieren.
Was gespeichert wird
Gespeichert wird nur ein Fingerabdruck des Treffers, nie der gefundene Text. Regel, Pfad, Zeile und Commit reichen aus, um die Datei in diesem Commit zu öffnen. Eine Gültigkeitsprüfung ergänzt das Ergebnis und dessen Zeitpunkt. Bei einem frem.sh-Token speichert sie außerdem die ID des passenden Token-Datensatzes, nie das Token selbst.
Wenn ein Secret gefunden wird
Rotiere die Zugangsdaten zuerst an ihrer Quelle. Das Löschen der Zeile macht das Leck nicht rückgängig: Wer die Historie hat, hat den Wert weiterhin. Entferne sie dann aus dem Code: Korrektur-PR öffnen in der Spalte Fix des Befunds verschiebt Zugangsdaten in Go, Python, JavaScript oder TypeScript in eine Umgebungsvariable, ohne KI; siehe Regelbasierte Korrekturen. Der Pull Request entfernt den Wert nicht aus der Historie, daher kommt das Rotieren in jedem Fall zuerst.