Breach report
sk- in the browser: thousands of OpenAI keys shipped in client code and public repos
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.