2 · The key-class catalog
The Secret Leak Field Manual · 7 min read
Question this chapter answers: Given a specific string, what does possessing it actually let someone do?
Severity is not about how "secret-looking" a string is. It is the combination of three things: scope (what one credential can reach), durability (how long it works), and visibility (where it can be read from). A publishable key is safe in every browser precisely because its scope is designed for that. A database connection string is critical anywhere because it is the database. This chapter is the reference catalog: six families, one table each, and a plain statement of what possession means for every credential type.
Every example below is synthetic, marked with embedded FAKE strings, and safe to read aloud. The full format reference is Appendix B.
Family 1 — Payments
| Credential | Anatomy | Scope model | What possession means |
|---|---|---|---|
Publishable pk_live_… | Prefix + random body; e.g. pk_live_FAKE000000000000000000000 | Token identification only | Nothing sensitive; it belongs in browsers |
Secret sk_live_… | Prefix + random body; e.g. sk_live_FAKE000000000000000000000 | Unrestricted account access | Charges, refunds, customer records, payout destinations — full control of the payment operation |
Restricted rk_live_… | Same shape as secret keys; e.g. rk_live_FAKE000000000000000000000 | Permissions you assign per key | Only the granted permissions — real but bounded damage |
Webhook signing secret whsec_… | e.g. whsec_FAKE000000000000000000000000 | Authenticates events to your endpoint | Ability to forge events your server accepts as genuine — fake fulfillment triggers, false subscription states |
Stripe documents that restricted keys exist to limit damage if obtained, recommends migrating new use cases away from unrestricted secret keys, and now adds organization-level keys to the same family (docs.stripe.com/keys). One attribution note: Clerk issues secret keys in exactly Stripe's shape. When you find an sk_live_-shaped string, confirm which provider it belongs to before acting — the rotation path differs even though the blast radius is similar.
Family 2 — Cloud
| Credential | Anatomy | Scope model | What possession means |
|---|---|---|---|
| AWS long-lived pair | Access key ID + secret, e.g. AKIAFAKEEXAMPLE000000 / FAKE0000000000000000000000000000000000 | Whatever IAM attaches to that user | Programmatic AWS access under that identity until deleted |
| AWS temporary (STS) | ASIA… IDs with session tokens | Time-boxed, role-scoped | Full role access while valid — expiry is not a response plan |
| Google service-account JSON | PEM private key inside JSON; e.g. -----BEGIN PRIVATE KEY-----\nFAKE000000...\n-----END PRIVATE KEY----- | Service-account roles across projects | Impersonation of that service account's grants |
AWS caps users at two active access keys per person specifically so rotation never requires downtime, and its own guidance prefers short-lived credentials over long-term keys (IAM user guide). Firebase web keys (AIza…) are the family's deliberate exception: they are public by design, protected by configuration — API restrictions and app checks — rather than secrecy (Firebase docs). Reporting an AIza key as a leaked secret is a false positive; reporting it without restrictions configured is a finding worth fixing.
Family 3 — Database and backend-as-a-service
| Credential | Anatomy | Scope model | What possession means |
|---|---|---|---|
| Postgres URL | postgresql://app:FAKEPASSWORD@db.example.internal:5432/prod | The database role named in the URL | Direct database access, bypassing every application-layer check |
| MongoDB URL | mongodb+srv://app:FAKEPASSWORD@cluster0.example.mongodb.net/prod | The database user in the string | Same — the connection string is the login |
Supabase service_role JWT | Three-part JWT; synthetic: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJvbGUiOiJzZXJ2aWNlX3JvbGUiLCJpYXQiOjE3NTQwMDAwMDB9.FAKE_SIGNATURE_EXAMPLE_ONLY | Bypasses Row Level Security entirely | Read, write, delete on all project data |
Supabase sb_secret_… | New-format platform key | Full data access, replaces legacy JWTs | Same elevated access, individually revocable |
Supabase management token (sbp_…) | Account-level token | Manages projects via the management API | Control over projects, not just data |
Supabase's current guidance: publishable keys (sb_publishable_…) are safe to expose; secret keys belong only in backend components; the legacy JWT-based anon and service_role keys are deprecated by the end of 2026 (API keys guide). Two recognition notes prevent expensive mistakes. First, local development mints the same service_role JWT on every machine, identified by its issuer; it grants nothing hosted and is not a leak. Second, the anon key is public by design — confusing it with service_role is the most common misclassification in this family.
Family 4 — Language models
| Credential | Anatomy | Scope model | What possession means |
|---|---|---|---|
| OpenAI project key | sk-proj-FAKE000000000000000000000000 | Scoped to a project | Billable API access under that project's limits |
| OpenAI admin key | sk-admin-… shape | Organization-wide management | Broadest LLM exposure class |
| OpenAI-compatible legacy shape | sk-FAKE0000000000000000000000000000 | Provider-specific | Billable access at DeepSeek, Groq, Together, Fireworks, and others sharing the shape |
| Anthropic | sk-ant-api03-FAKE0000000000000000000000000000000000000000000000 | Console/workspace-scoped | Billable API access |
Possession means spending your money and quota. There is no sandbox mode for production LLM keys: treat every one as live. OpenAI's help center states the recovery path plainly — a lost or exposed key cannot be retrieved, so create a new one and update the application (help.openai.com). Project-scoped keys with spend limits are the blast-radius tool here; Chapter 6 applies them.
Family 5 — Source control
| Credential | Anatomy | Scope model | What possession means |
|---|---|---|---|
| GitHub classic PAT | ghp_, gho_, ghu_, ghs_, ghr_ prefixes; e.g. ghp_FAKE0000000000000000000000000000000 | Scopes layered on top of everything the owning user can reach | Repo contents, code, org administration — bounded by scopes, unbounded by repo count |
| Fine-grained PAT | github_pat_FAKE000000000000000000_0000000000000000000000000000000 | One owner, selected repos, granular permissions | Only the granted permissions on the chosen repositories |
GitHub's documentation treats tokens like passwords, recommends fine-grained tokens over classic, and supports organization policies requiring approval and maximum lifetimes (managing personal access tokens). GitHub also operates secret scanning on public repositories and works with partners so certain detected tokens are revoked automatically (about secret scanning) — a safety net that covers GitHub's own token formats, not the other five families in this chapter.
Family 6 — Email, SMS, and messaging
| Credential | Anatomy | Scope model | What possession means |
|---|---|---|---|
| SendGrid | SG.FAKE0000000000000000000.0000000000000000000000000000000000000 | Full-access or scoped keys | Send email as your domain — the raw material of phishing campaigns wearing your brand |
| Resend | re_FAKE_000000000000000000000 | Account-scoped sending | Same sending capability |
| Slack bot/user/app tokens | xoxb-0000-FAKE000000000000000 and siblings | Bot: installed workspace scope; user: acting user; app-level: cross-workspace app actions | Message posting, channel reads, workspace actions per scope |
| Mapbox secret token | sk.eyJFAKE0000000000000000.FAKE0000000000000000 | Account operations incl. token management | Billing-relevant account changes; only pk. tokens belong in browsers (Mapbox tokens) |
Email credentials are underrated in severity rankings because the direct financial drain is invisible next to a payment key. The damage arrives later: your domain sends spam or phishing, deliverability collapses, and reputation repair takes weeks.
Reading the tables under pressure
When someone hands you an unknown string mid-incident:
- Match the prefix or anatomy against Appendix B → identify family.
- Take the "what possession means" row literally → assume capability, verify with logs (Chapter 5).
- If two providers share a shape (Stripe/Clerk, OpenAI/gateways) → attribute honestly before rotating, then rotate regardless.
Do this now
- [ ] Build your inventory against these six families; note which families you hold zero, one, or many of.
- [ ] Mark every credential in your inventory as full-scope or scoped; scoped-only inventories rotate faster and cheaper.
- [ ] Confirm no Mapbox secret token and no Supabase
service_role/sb_secret_value exists in any browser-facing code. - [ ] For each family you use, open the provider's cited documentation once now — reading docs during an incident costs minutes you will not have.