Summary
Add built-in secret detection rules for OpenAI API credentials. Trivy currently has no coverage for OpenAI tokens, so sk-... keys baked into application files or container images are not flagged during a scan.
All sk-* OpenAI keys embed the watermark T3BlbkFJ (base64 of "OpenAI") within the token, symmetric (the random part before and after is the same length). This watermark is the primary discriminator and is used as the pre-filter Keyword for every sk-* rule — it is highly specific and keeps false positives near zero, including against other vendors that reuse the sk- prefix (Anthropic sk-ant-, OpenRouter sk-or-v1-).
Note on stripe-secret-token. Stripe uses sk_(test|live)_ (underscore); OpenAI uses sk- (hyphen). The regexes do not overlap.
Severity criteria
- CRITICAL — grants full programmatic access to the OpenAI API / organisation (project, service-account, or admin scope); actively exploited for billing abuse.
- HIGH — legacy formats no longer issued but still honoured by the API with the same access level as active keys.
- MEDIUM — short-lived tokens (TTL ≤ 2 h) that may still be valid at scan time.
Proposed rules
CRITICAL
Note on the {20,100} range (rules 1–3). Derived from GitHub code-search analysis. The random part between the prefix and the watermark is not a single fixed length: for sk-proj- a fixed {74} misses ~14% of real tokens (offset 58 and others are common); for sk-svcacct- longer ones exist too (a real token at offset 80 was observed). For sk-admin- the fixed {58} would suffice, but the same range is applied for consistency. In all three the T3BlbkFJ watermark is the real discriminator, so the wider range does not raise false-positive risk.
Open question (for review). Rules 1–3 could be collapsed into a single rule sk-(?:proj|svcacct|admin)-[A-Za-z0-9_-]{20,100}T3BlbkFJ[A-Za-z0-9_-]{20,100} (cf. aws-access-key-id, which merges all prefixes under one title). They are kept separate so each finding carries a distinct title — sk-admin- (org-wide compromise), sk-svcacct- (long-lived non-human identity) and sk-proj- (project scope) differ in blast radius even though all are CRITICAL. Open to merging if reviewers prefer simplicity.
1. OpenAI Project API Key
Project-scoped API key (sk-proj-…T3BlbkFJ…) — the current default key type.
- Keyword:
T3BlbkFJ
- Regex:
sk-proj-[A-Za-z0-9_-]{20,100}T3BlbkFJ[A-Za-z0-9_-]{20,100}
- Why: Grants full API access within the project; actively exploited in the wild.
2. OpenAI Service Account Key
Service-account API key (sk-svcacct-…T3BlbkFJ…) — non-human identity for automated workloads.
- Keyword:
T3BlbkFJ
- Regex:
sk-svcacct-[A-Za-z0-9_-]{20,100}T3BlbkFJ[A-Za-z0-9_-]{20,100}
- Why: Long-lived, not tied to a human user, so leaks are harder to detect and revoke.
3. OpenAI Admin API Key
Organisation-wide admin API key (sk-admin-…T3BlbkFJ…).
- Keyword:
T3BlbkFJ
- Regex:
sk-admin-[A-Za-z0-9_-]{20,100}T3BlbkFJ[A-Za-z0-9_-]{20,100}
- Why: Access to admin APIs (user management, billing, audit logs); a leak is equivalent to full organisation compromise.
HIGH
4. OpenAI Legacy User API Key
Legacy personal key (sk-…T3BlbkFJ…, offset 20) — no longer issued, existing keys remain active.
- Keyword:
T3BlbkFJ
- Regex:
sk-(?:None-)?[A-Za-z0-9]{20,42}T3BlbkFJ[A-Za-z0-9]{20,42}
- Why: ~2 500 results on Sourcegraph; still honoured by the API with the same access as project keys. The optional
None- segment also covers the sk-None-… variant (same structure, literal None- prefix). The {20,42} range covers both the common offset 20 format and the rarer offset 42 generation; as with the active keys, the T3BlbkFJ watermark is the discriminator, so the wider range does not raise false-positive risk.
- Note: The charset is restricted to
[A-Za-z0-9] (no -/_) on purpose. This bare sk- rule would otherwise let its {20,42} run bridge the hyphen in sk-proj-, sk-svcacct-, sk-admin- and sk-service- and flag an active key a second time under the legacy ID. Excluding -/_ stops the run at the prefix hyphen, so each typed key matches only its own rule. The trade-off is that rare offset-42 keys containing -/_ in the random part are not covered.
- Note: The watermark anchor prevents cross-vendor false positives on the bare
sk- prefix (Anthropic sk-ant-, OpenRouter sk-or-v1-).
5. OpenAI Service API Key
Legacy service key with arbitrary name segment (sk-service-<name>-…T3BlbkFJ…) — likely predecessor to sk-svcacct-; not in the current UI.
- Keyword:
T3BlbkFJ
- Regex:
sk-service-[A-Za-z0-9-]+-[A-Za-z0-9]{20}T3BlbkFJ[A-Za-z0-9]{20}
- Why: ~195 results on GitHub; may still be active; same access level as project keys. Anchored by the
T3BlbkFJ watermark, so it is covered with the same low false-positive risk as the other sk-* rules.
MEDIUM
6. OpenAI Realtime Client Secret
Short-lived realtime session token (ek_…, 32 hex chars) — no watermark; TTL ≤ 2 h.
- Keyword:
ek_
- Regex:
ek_[a-f0-9]{32}
- Why: Grants access to the Realtime API; a token baked into an image during development may still be valid at scan time.
Testing
Test cases added to pkg/fanal/secret/scanner_test.go, with a testdata file per rule under pkg/fanal/secret/testdata/ (placeholder values, no live secrets).
- True positive — a token matching each format is detected.
- True negative — wrong length / missing watermark / cross-vendor
sk-ant-, sk-or-v1- keys are not detected.
- No regression — full secret-scanning suite passes (no overlap with
stripe-secret-token).
Summary
Add built-in secret detection rules for OpenAI API credentials. Trivy currently has no coverage for OpenAI tokens, so
sk-...keys baked into application files or container images are not flagged during a scan.All
sk-*OpenAI keys embed the watermarkT3BlbkFJ(base64 of"OpenAI") within the token, symmetric (the random part before and after is the same length). This watermark is the primary discriminator and is used as the pre-filterKeywordfor everysk-*rule — it is highly specific and keeps false positives near zero, including against other vendors that reuse thesk-prefix (Anthropicsk-ant-, OpenRoutersk-or-v1-).Severity criteria
Proposed rules
CRITICAL
1. OpenAI Project API Key
Project-scoped API key (
sk-proj-…T3BlbkFJ…) — the current default key type.T3BlbkFJsk-proj-[A-Za-z0-9_-]{20,100}T3BlbkFJ[A-Za-z0-9_-]{20,100}2. OpenAI Service Account Key
Service-account API key (
sk-svcacct-…T3BlbkFJ…) — non-human identity for automated workloads.T3BlbkFJsk-svcacct-[A-Za-z0-9_-]{20,100}T3BlbkFJ[A-Za-z0-9_-]{20,100}3. OpenAI Admin API Key
Organisation-wide admin API key (
sk-admin-…T3BlbkFJ…).T3BlbkFJsk-admin-[A-Za-z0-9_-]{20,100}T3BlbkFJ[A-Za-z0-9_-]{20,100}HIGH
4. OpenAI Legacy User API Key
Legacy personal key (
sk-…T3BlbkFJ…, offset 20) — no longer issued, existing keys remain active.T3BlbkFJsk-(?:None-)?[A-Za-z0-9]{20,42}T3BlbkFJ[A-Za-z0-9]{20,42}None-segment also covers thesk-None-…variant (same structure, literalNone-prefix). The{20,42}range covers both the common offset 20 format and the rarer offset 42 generation; as with the active keys, theT3BlbkFJwatermark is the discriminator, so the wider range does not raise false-positive risk.[A-Za-z0-9](no-/_) on purpose. This baresk-rule would otherwise let its{20,42}run bridge the hyphen insk-proj-,sk-svcacct-,sk-admin-andsk-service-and flag an active key a second time under the legacy ID. Excluding-/_stops the run at the prefix hyphen, so each typed key matches only its own rule. The trade-off is that rare offset-42 keys containing-/_in the random part are not covered.sk-prefix (Anthropicsk-ant-, OpenRoutersk-or-v1-).5. OpenAI Service API Key
Legacy service key with arbitrary name segment (
sk-service-<name>-…T3BlbkFJ…) — likely predecessor tosk-svcacct-; not in the current UI.T3BlbkFJsk-service-[A-Za-z0-9-]+-[A-Za-z0-9]{20}T3BlbkFJ[A-Za-z0-9]{20}T3BlbkFJwatermark, so it is covered with the same low false-positive risk as the othersk-*rules.MEDIUM
6. OpenAI Realtime Client Secret
Short-lived realtime session token (
ek_…, 32 hex chars) — no watermark; TTL ≤ 2 h.ek_ek_[a-f0-9]{32}Testing
Test cases added to
pkg/fanal/secret/scanner_test.go, with a testdata file per rule underpkg/fanal/secret/testdata/(placeholder values, no live secrets).sk-ant-,sk-or-v1-keys are not detected.stripe-secret-token).