Skip to content

feat(secret): add built-in rules for OpenAI credential detection #10794

Description

@nikpivkin

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).

  1. True positive — a token matching each format is detected.
  2. True negative — wrong length / missing watermark / cross-vendor sk-ant-, sk-or-v1- keys are not detected.
  3. No regression — full secret-scanning suite passes (no overlap with stripe-secret-token).

Metadata

Metadata

Assignees

Labels

kind/featureCategorizes issue or PR as related to a new feature.scan/secretIssues relating to secret scanning

Fields

No fields configured for Feature.

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions