Incidents
AI-written code leaked an API key and ran up fraudulent charges — the real cause was an unpatched CVSS 10.0
A pattern indie developers really do hit: an app shipped with AI's help has its API key stolen and racks up fraudulent charges via unfamiliar bulk jobs. The detour of blaming the wrong culprit, the real cause — a months-old public CVSS 10.0 RCE — and how to stop it recurring, told defensively without attack steps.
A case that security-unfamiliar indie developers commonly hit, with every identifying detail removed and turned into a lesson. No attack reproduction here — the goal is defense, so the same mistake doesn't happen to you.
- Class
- API key leak / fraudulent billing
- Severity
- Critical (a published CVSS 10.0 abused)
- Root cause
- A pre-auth RCE in the web framework left unpatched for months
- Leak scope
- Every secret in the environment (the whole keyring)
- Execution scope
- Stayed inside an unprivileged container (host root unharmed)
- Permanent fix
- ①upgrade to patched version ②rotate all keys ③machine CVE monitoring
“Stopped the charges” ≠ “handled”
Stopping the bill is first aid. Closing the leak path is a separate operation. You're only done when you've done both.
What happened (timeline)
Day 0 — shipped & running
An app built with AI's help was shipped and running in production.One day — the bill spikes
The cloud-AI bill suddenly jumps. Bulk jobs on a model that would never be used, doing work that wasn't theirs.Investigation — third-party abuse
"Our app ran wild" doesn't explain it. A third party was running it with a stolen key.The chase — real investigation begins
"So where did the key leak from?" — that was the real investigation.
Why it wasn't caught at first (the detours)
In the order the mistakes were made — because the mistakes are the lesson.
Jumped to a culprit
→ Before deciding "my fault" or "their fault," look at the data first.
A clean grep nearly brought relief
Hiding the symptom felt like fixing it
A clean grep is not the all-clear
Even with no key in any file, a vulnerability can pull environment variables out of a running process. Leaks happen at runtime (RCE, HTTP headers), not just in files. See What is RCE.
The real cause: a neglected, published CVSS 10.0
A strange signature in old access logs matched threat intel instantly. The web framework version range in use had a published pre-auth RCE (CVSS 10.0) that was already being exploited in the wild — and it had run unpatched for months.
- This wasn't a passive bug — it was an attacker executing code on the server and exfiltrating the environment.
- The saving grace: execution stayed inside an unprivileged container (host root wasn't taken). The breach review found no persistent backdoor, miner, or C2 — the confirmed impact was secret theft.
- Because the internal DB was reachable, the response assumed the DB contents had leaked too.
Lesson: when something behaves strangely, suspect a known CVE first. Don't assume it's your own bug.
The count was wrong too — judge by the running version
Spreading out, "8 more vulnerable dependencies" was reported — also wrong. They'd been counted by the lower bound in package.json (the ^ caret range). The truly dangerous ones were only the 2 pinned dependencies left behind.
Check your own running versions
See the versions actually resolved, in the lockfile or the running container.
# npm: see what's actually installed
npm ls next react react-dom
# inside a running container
docker exec <container> npm ls <package>The numbers here are the truth — not the ^ in package.json.
What was done first (reproducible steps)
Revoke the abused key immediately
Upgrade the framework to the patched version
Rotate every secret
Breach review
Account-side defense
The real prevention: let machines watch
Boiled down, this incident was "a human overlooked a published CVSS 10.0." Manual patrols always miss things. Machines don't.
Dependency scanning you can start today
Free to start — one step in CI.
# OSV scanner (Google) checks your lockfile
osv-scanner --lockfile=pnpm-lock.yaml
# On GitHub, enable Dependabot (repo Settings → Security)ITD itself, per this lesson, monitors its own dependencies for CVEs — we practice what we recommend.
Lessons
Common mistakes
- Calling it done at "stopped the charges"
- Relaxing because grep is clean
- Masking the symptom at a proxy and feeling fixed
- Counting vulnerabilities by the
package.jsonfloor - Rotating only the one confirmed-abused key
Correct defense
- Treat first aid and "close the leak path" as two separate jobs, do both
- Suspect runtime leaks (RCE, headers) too
- Update the root (the framework)
- Judge by the version actually running
- Replace the whole env if it leaks
In one line: the runaway bill was the tip of the iceberg. The real cause was a neglected CVSS 10.0 RCE — and the truth came not from one lucky guess, but from being wrong, colliding, and grinding the errors away.
Read next
- Glossary: What is RCE · What is a CVE · What is CVSS
- Defense: Running Next.js safely (CVE hygiene)
- Incident: When .env was exposed to the whole world
FAQ
QIf an API key leaks, is it enough to revoke just the one key that was abused?
No. When the leak path is at runtime (an RCE or a header leak), assume every secret in the environment leaked at once, and rotate them all. The key you saw abused is just the tip of the iceberg.
QIf grep across my code and git finds no keys, am I safe?
Not necessarily. Even with no key in any file, a framework vulnerability can exfiltrate environment variables straight from the running process. Leaks happen at runtime too, not just in files.
QWhat stops this from happening again, most reliably?
Machine-monitoring your dependencies for CVEs. The root cause here was a human overlooking a published CVSS 10.0 for months; Dependabot or osv-scanner closes that gap structurally.