Breachwire.riscent

Breach report

sk- in the browser: thousands of OpenAI keys shipped in client code and public repos

PWA & Startup BreachesHigh2025-01
The bottom lineCyble found OpenAI API keys exposed at scale — over 5,000 GitHub repositories and around 3,000 public websites shipping sk- keys in client-side JavaScript or committed source, where automated scanners abuse them within hours or minutes.
Category
PWA & Startup Breaches
Type
Vulnerability class (exposed API keys)
Date
2025-01
Severity
High
OWASP
LLM10 Unbounded Consumption
CWE / CVE
CWE-798

What happened

Cyble Research and Intelligence Labs documented OpenAI API keys leaking as a class, not a one-off. They found hardcoded keys in over 5,000 GitHub repositories and OpenAI keys embedded in client-side code on roughly 3,000 public-facing websites. On the web, keys were baked into JavaScript bundles, static assets, and front-end framework files — "accessible to any user inspecting the application." Cyble observed key formats including project-scoped (sk-proj-…) and service-account (sk-svcacct-…) keys, and warned that once a key is committed, automated scanners indexing new commits reduce the window from exposure to abuse to "hours or even minutes."

Root cause

An LLM API key is a privileged, billable credential (CWE-798). Calling the model provider directly from the browser means the secret must ship to the browser, where anyone can read it via view-source or devtools. The app works — it talks to the model — while the invariant "the key never leaves the server" is violated by design. Committing a key to a repo is the same failure through a different door: source history is effectively public. The result is unbounded consumption on the victim's account (LLM10): attackers rack up inference charges or resell access.

How it would have been caught

A build-time check that greps the shipped client bundle for sk- key patterns and fails on a hit catches the front-end leak. Secret scanning over the repo and its history catches the committed variant. Both are trivial reproductions: open the deployed site's JS in a browser and search for the key, or scan the git log.

How to prevent it

  • Never put a provider key in client code. Proxy all model calls through your backend, which holds the key and enforces per-user auth and rate limits.
  • Run secret scanning in CI on both the built client bundle and the repository history; gate deploys on a clean result.
  • Scope keys narrowly, set spend limits, and rotate immediately on any exposure — assume a leaked key is abused within minutes.

The Breachwire test (red → green)

Open the deployed app's client JavaScript (or scan the repo history) and confirm a working sk- key is present and successfully authenticates to the provider — the RED proof the secret is public. Move the model call behind a server-side proxy, remove and rotate the key, and confirm the client bundle contains no key and the old key no longer authenticates, while the feature still works through the backend.