Skip to main content
Stackbastion

Draft, not yet published

Postmortem: an exposed API key and a surprise bill

7 July 2026· 6 min read · by Stackbastion

The email arrived at 6 AM. Subject line: “Your usage has exceeded your plan limit.” The founder assumed it was spam and went back to sleep. By the time they actually looked, the bill had climbed past anything the app could possibly have earned. The app had a few dozen users. The invoice looked like it belonged to a company with thousands.

Somebody had found an API key that should never have been public, and they’d been using it all night.

One honest note first. This is a composite of patterns we see across AI-built apps, not one specific client incident. The numbers and details are illustrative. The failure mode is real and about as common as they come.

What happened

The app called a third-party service. Could be an AI model provider, a maps API, an SMS gateway, an image generator. It doesn’t matter which. What matters is that the call needed an API key, and that key sat in the frontend code.

Here’s the trap. The app worked perfectly. The feature did exactly what it was supposed to. Nobody had a reason to think anything was wrong, because from the user’s side, nothing was.

But a frontend is public by definition. Anything your browser can read, anyone’s browser can read. The key was sitting in a JavaScript file that shipped to every visitor. All someone had to do was open the browser dev tools, or scan the page source, and there it was: a working key to a paid service, with no limit on how it could be used.

Someone found it. Automated scanners crawl public sites looking for exactly this, all day, every day. Once they had the key, they pointed it at the most expensive operation the service offered and ran it in a loop. The meter ran all night while the founder slept.

Why this keeps happening to AI-built apps

When you build an app by describing what you want, the AI wires up the API call the shortest way that works. The shortest way is often to put the key right where the call is made. If the call happens in the frontend, the key ends up in the frontend.

The AI isn’t wrong that the code works. It’s just optimizing for “does the feature run,” not “can a stranger read this secret.” Those are different questions, and only one of them shows up when you test the app yourself.

Two things make it worse for vibe-coded apps specifically:

  • The key often has no spending limit set, because setting one is an extra step nobody prompted for.
  • There’s usually no alert wired up, so the first sign of trouble is the bill, not a warning.

So you get a public secret, no ceiling, and no alarm. That’s the whole recipe.

The fix

The immediate fix when it happens is fast and brutal: revoke the key. The moment you rotate a leaked key, every request using the old one dies. The attacker’s loop breaks instantly. The bleeding stops the second the old key is dead.

Then you fix the actual problem, which is where the key lived.

Secret keys belong on a server, never in the browser. The pattern is simple. Your frontend calls your own backend. Your backend holds the key and makes the call to the third-party service. The key never leaves the server, so it never ships to a visitor’s browser, so there’s nothing to scan for. This is a small change in shape and a large change in exposure.

While you’re in there, set two guardrails:

  • A spending cap on every paid service that supports one. If the meter can’t run past a limit you set, a leaked key can only do so much damage before it caps out.
  • A usage alert that emails you when spend crosses a threshold. This is the difference between finding out at 6 AM from an alert and finding out from the invoice.

How to catch it before the bill does

You can find an exposed key in about five minutes, and you don’t need to be a security expert to do it.

Open your live app in a browser. Open the developer tools. Look at the page source and the network requests. If you can find something that looks like a key, a long string of letters and numbers, sitting in the frontend, so can everyone else. We wrote a step-by-step version of this in the five-minute exposed keys check, and it’s worth doing today rather than after an incident.

While you’re at it, log into every paid service the app uses and check two things: is there a spending limit set, and is there an alert configured. If either answer is no, fix it now. These take minutes and they turn a catastrophe into an annoyance.

The lesson

The painful part of this one isn’t the technical fix. Moving a key to the backend is routine work. The painful part is that the app looked completely healthy right up until the bill. There was no crash, no error, no broken feature to warn anyone. The only symptom was money leaving the account, and by the time that’s visible, the damage is done.

That’s the pattern with exposed secrets. They don’t announce themselves. The app that “just works” and the app with a key bleeding money look identical from the outside. The only way to know which one you have is to look.

If you’d rather have someone check for exposed keys, missing spending caps, and absent alerts before an attacker does, that’s what our free audit covers. We open your live app the same way a scanner would and tell you what’s sitting in the open.

FAQ

How does an API key end up exposed?

Usually because it’s stored in frontend code. Anything the browser downloads is readable by anyone who opens the dev tools or views the source. A key placed there is effectively public, even if the app itself looks private.

How do attackers find exposed keys so fast?

Automated scanners crawl public sites continuously looking for strings that match key formats. A newly exposed key can be found and abused within hours, sometimes minutes. It’s not targeted. It’s a machine sweeping the whole web.

What’s the first thing to do if I find one?

Revoke and rotate the key immediately. That kills every request using the old key, including the attacker’s. Then move the key to your backend so it can’t happen again.

Would a spending cap alone have prevented this?

A cap wouldn’t stop the key from leaking, but it would limit the damage. Combined with a usage alert, it turns an overnight disaster into a small, contained blip you catch quickly. The real fix is keeping the key off the frontend in the first place.

Do I need a backend just to hide one key?

Yes, and it’s less work than it sounds. A small server endpoint that holds your keys and forwards calls is enough. It’s the standard way to use any paid API safely, and it removes the entire problem of public secrets.