You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Register the API-key marker with secret scanners and expose self-revoke #561
Register it with secret-scanning partners (GitHub, and others as relevant) and implement their verification endpoint.
Expose a public self-revoke endpoint so a stranger who finds a leaked key can kill it without holding rights on the account.
Why now
Partner registration is a multi-week external process. The marker must be fixed before #548's schema. The legacy prefix was a bare SC (src/lib/actions/crypto.ts), far too low-entropy to register.
What each scanner needs to recognise a Source Cooperative API key, and the steps to register with it. Keep this comment current: when the key format or an endpoint changes, update it and every registration below. (Written 2026-09-29.)
The key format, as registered
A key is sck_, 30 random base62 characters, and six more that are their checksum: 40 characters in all. The checksum is the CRC-32 of the 30 random characters (IEEE, as zlib computes it), written in base62 with the digits 0-9A-Za-z, most significant first, left-padded with 0 to six. This is GitHub's own token layout. It is defined by ADR-013 (source-cooperative/data.source.coop#242) and in code by API_KEY_PATTERN and isApiKey in src/types/service-account-key.ts (#596).
Regular expression: sck_[0-9A-Za-z]{36}, anchored in our code as ^sck_[0-9A-Za-z]{36}$. It should not match inside a longer run of letters and digits, so its boundaries are the start or end of the text or any character outside [0-9A-Za-z].
Reference check, for any scanner that can run one:
Test vectors (random part → checksum): 30 × a → 1yLcDB; 0123456789ABCDEFGHIJabcdefghij → 4Us3aw (a CRC above 2^31); 29 × 0 then 1 → 010Ohw (keeps its leading zero). A key is sck_ + random part + checksum; none is spelled out here, so this issue doesn't trip the scanners it describes.
Our own repositories assemble their test keys at run time or from pieces, so they contain no whole key for a scanner to report.
Source Cooperative API Key (type identifier source_coop_api_key)
Regular expression
sck_[0-9A-Za-z]{36}, with the checksum above for GitHub to apply if it can
Test account
A Source Cooperative login that manages a service account, so GitHub can issue its own keys (see the steps)
Endpoint URL
https://source.coop/api/v1/secret-scanning/github
What the endpoint does (#581): it takes GitHub's POST, checks Github-Public-Key-Signature (base64 DER ECDSA, P-256 with SHA-256, over the raw body) against the key Github-Public-Key-Identifier names at https://api.github.com/meta/public_keys/secret_scanning, and answers 401 to anything it can't verify. It revokes every reported token that passes isApiKey and answers 200 with [{token_raw, token_type, label}], true_positive for a key that exists, whether it was revoked now or before, and false_positive for one that doesn't. Tokens failing the pattern or checksum are left out without a lookup. Each revocation is logged at warn with via: "github" and the url and source where GitHub found it.
Create the test account: an individual account with a password login on a shared mailbox, owning a service account that holds no memberships, so a key GitHub issues reaches nothing. Send its credentials to GitHub privately, never in this issue.
whether it redelivers a report our endpoint refuses with 401. An instance fetches GitHub's public keys at most once every ten minutes, so a report signed with a newly published key can be refused until that window passes;
to read our feedback labels (token_raw + label);
whether it can apply our checksum to its matches, which would cut false positives before they reach us;
whether the pattern will be push-protected.
Once enrolled, issue a key from the test account, commit it to a public test repository, and confirm the endpoint logs via: "github" and the key's row shows it revoked. Note the date here.
If the key format ever changes, update the registration before issuing keys in the new format.
GitLab notifies partners of credentials leaked in public projects on GitLab.com; interest goes through GitLab epic 4944. It posts [{type, token, url}] signed with Gitlab-Public-Key-Signature (ECDSA with SHA-256) against GitLab's published keys, and retries on any status of 400 or above, so the endpoint must answer 2xx once it has processed a batch. The partner's API must be idempotent (revocation already is) and rate-limited (the Vercel WAF follow-up on #581).
An open-source scanner run locally and in CI. A rule is a pull request to gitleaks/gitleaks.
Add cmd/generate/config/rules/sourcecoop.go using GenerateUniqueTokenRegex (the sck_ prefix is distinctive) with keyword sck_, true positives from generateSampleSecret, and false positives such as the checksum-less 47-character format. Add it to configRules in cmd/generate/config/main.go, run make config/gitleaks.toml, and open the PR.
A detector is a pull request to trufflesecurity/trufflehog, and it must verify candidates against a live, non-destructive API call.
Add a DetectorType to proto/detector_type.proto, run make protos and go run hack/generate/generate.go detector <DetectorType>, register the detector in pkg/engine/defaults/defaults.go, and implement the regex, the checksum and at least five tests.
Verification: a form-body POST to https://data.source.coop/.sts with Action=AssumeRoleWithWebIdentity, RoleArn=_default and the key as WebIdentityToken. A 200 means the key is live; InvalidIdentityToken "API key was not accepted" means it's revoked, expired or unknown; "API key is malformed" means it isn't a key. Never verify through the self-revoke route, which revokes the key. A verified exchange updates the key's "last used" in the UI. This needs feat(sts): exchange opaque API keys at /.sts by hash lookup data.source.coop#235 deployed.
Activity
docs(adr): revise ADR-013 — API keys are opaque secrets resolved by t…
feat(accounts): issue, revoke and expire opaque API keys for service …
alukach commentedon Sep 29, 2026
Registration log: secret scanners
What each scanner needs to recognise a Source Cooperative API key, and the steps to register with it. Keep this comment current: when the key format or an endpoint changes, update it and every registration below. (Written 2026-09-29.)
The key format, as registered
A key is
sck_, 30 random base62 characters, and six more that are their checksum: 40 characters in all. The checksum is the CRC-32 of the 30 random characters (IEEE, as zlib computes it), written in base62 with the digits0-9A-Za-z, most significant first, left-padded with0to six. This is GitHub's own token layout. It is defined by ADR-013 (source-cooperative/data.source.coop#242) and in code byAPI_KEY_PATTERNandisApiKeyinsrc/types/service-account-key.ts(#596).sck_[0-9A-Za-z]{36}, anchored in our code as^sck_[0-9A-Za-z]{36}$. It should not match inside a longer run of letters and digits, so its boundaries are the start or end of the text or any character outside[0-9A-Za-z].a→1yLcDB;0123456789ABCDEFGHIJabcdefghij→4Us3aw(a CRC above 2^31); 29 ×0then1→010Ohw(keeps its leading zero). A key issck_+ random part + checksum; none is spelled out here, so this issue doesn't trip the scanners it describes.GitHub secret scanning partner program
Docs: https://docs.github.com/en/code-security/tutorials/secret-scanning-partner-program
Send to secret-scanning@github.com:
source_coop_api_key)sck_[0-9A-Za-z]{36}, with the checksum above for GitHub to apply if it canhttps://source.coop/api/v1/secret-scanning/githubWhat the endpoint does (#581): it takes GitHub's
POST, checksGithub-Public-Key-Signature(base64 DER ECDSA, P-256 with SHA-256, over the raw body) against the keyGithub-Public-Key-Identifiernames at https://api.github.com/meta/public_keys/secret_scanning, and answers 401 to anything it can't verify. It revokes every reported token that passesisApiKeyand answers 200 with[{token_raw, token_type, label}],true_positivefor a key that exists, whether it was revoked now or before, andfalse_positivefor one that doesn't. Tokens failing the pattern or checksum are left out without a lookup. Each revocation is logged at warn withvia: "github"and theurlandsourcewhere GitHub found it.Steps
token_raw+label);via: "github"and the key's row shows it revoked. Note the date here.GitLab leaked-credential partner program
Docs: https://docs.gitlab.com/user/application_security/secret_detection/automatic_response/
GitLab notifies partners of credentials leaked in public projects on GitLab.com; interest goes through GitLab epic 4944. It posts
[{type, token, url}]signed withGitlab-Public-Key-Signature(ECDSA with SHA-256) against GitLab's published keys, and retries on any status of 400 or above, so the endpoint must answer 2xx once it has processed a batch. The partner's API must be idempotent (revocation already is) and rate-limited (the Vercel WAF follow-up on #581).POST /api/v1/secret-scanning/gitlabbeside the GitHub route, as feat(accounts): revoke leaked API keys via self-revoke and GitHub secret scanning #581's route layout anticipates, then register through epic 4944.gitleaks
Docs: https://github.com/gitleaks/gitleaks/blob/master/CONTRIBUTING.md
An open-source scanner run locally and in CI. A rule is a pull request to gitleaks/gitleaks.
cmd/generate/config/rules/sourcecoop.gousingGenerateUniqueTokenRegex(thesck_prefix is distinctive) with keywordsck_, true positives fromgenerateSampleSecret, and false positives such as the checksum-less 47-character format. Add it toconfigRulesincmd/generate/config/main.go, runmake config/gitleaks.toml, and open the PR.TruffleHog
Docs: https://github.com/trufflesecurity/trufflehog/blob/main/hack/docs/Adding_Detectors_external.md
A detector is a pull request to trufflesecurity/trufflehog, and it must verify candidates against a live, non-destructive API call.
DetectorTypetoproto/detector_type.proto, runmake protosandgo run hack/generate/generate.go detector <DetectorType>, register the detector inpkg/engine/defaults/defaults.go, and implement the regex, the checksum and at least five tests.POSTtohttps://data.source.coop/.stswithAction=AssumeRoleWithWebIdentity,RoleArn=_defaultand the key asWebIdentityToken. A 200 means the key is live;InvalidIdentityToken"API key was not accepted" means it's revoked, expired or unknown; "API key is malformed" means it isn't a key. Never verify through the self-revoke route, which revokes the key. A verified exchange updates the key's "last used" in the UI. This needs feat(sts): exchange opaque API keys at /.sts by hash lookup data.source.coop#235 deployed.Not yet looked into
GitGuardian and Yelp's detect-secrets.
feat(accounts): end API keys in a CRC-32 checksum (#596)
feat(accounts): revoke leaked API keys via self-revoke and GitHub sec…
docs(adr): ADR-013 keys end in a CRC-32 checksum (#242)