IOnclad  /  Features  /  Denial of Wallet
Risk Breakdown · Cost / DoW High

Some bugs don't breach you. They bankrupt you.

Denial of Wallet is the class of bug that costs you money instead of data. A render loop, a missing idempotency key, a retry storm, an unbounded fan-out, or a serverless function that re-triggers itself. None of them leak a single record. All of them can turn a quiet Tuesday night into a five-figure cloud bill before you've finished your coffee. IOnclad's CostGuard scanner hunts for these patterns directly in your code.

What CostGuard looks for

Most security tools only care about who gets in. CostGuard cares about what your own code does to your bill when nobody is watching. It checks JavaScript and TypeScript for seven distinct money-drain shapes, ranked by blast radius:

CW01 render loop CW02 missing idempotency CW03 webhook re-processing CW04 retry storm CW05 unbounded fan-out CW06 serverless recursion CW07 anonymous metered API

Why it's dangerous

A leaked key gets rotated and the bleeding stops. A wallet-drain bug keeps charging your card every second until you find it. Here are the patterns, from worst blast radius down.

1. The render loop that fetches forever (CW01)

A React useEffect with a network call and no dependency array runs on every render. If it also sets state, each render fires another fetch, which sets state, which renders again. An infinite loop hammering your API within seconds of the page loading. This is the single most common AI-generated cost bomb, which is why CostGuard ships it at High. An empty [] deps array (mount once) is correct and never fires.

2. The retry storm with no brakes (CW04)

A while(true) or for(;;) loop that retries a network or paid-API call inside a catch with no backoff, no attempt cap, and no break. One persistent 500 from the upstream service spins this at full speed: thousands of billable calls per second, plus a thundering-herd hit that can keep the dependency down. CostGuard only flags genuinely uncapped loops. A real attempt counter (attempt < 5), a break, or a retry library like p-retry clears it.

3. The serverless function that eats itself (CW06)

A Firestore trigger fires on a collection, then writes back to the same collection. The write re-fires the trigger, which writes again. Infinite recursion. This exact pattern is the well-known cause of overnight five-figure Firebase and GCP bills. CostGuard catches both the named-collection self-write and the v2 ref-style form (event.data.after.ref.update) that targets the same doc without naming the collection.

4. The double charge and the fan-out (CW02, CW03, CW05)

A Stripe charge, payment, or subscription created with no idempotency key double-bills the customer on any retry: a network blip, a double-click, an at-least-once queue. A webhook handler that writes without de-duping on the event id re-processes the same event, because Stripe, GitHub, and Svix all deliver at-least-once. And a Promise.all(items.map(...)) with no concurrency cap launches one paid call per element, so a big array means hundreds of simultaneous calls, instant rate-limit bans, and a metered spike.

The nastiest trap

Denial of Wallet does not need an attacker. The render loop, the recursive trigger, and the uncapped retry all fire from your own users on a normal night. But CW07 is the one that invites an attacker in: a metered API (LLM, SMS, email, maps, search) reachable with no auth means anyone on the internet can script your endpoint and run your bill up on purpose. That's economic denial-of-service, and it's free for them.

The shape of a real incident

Two lines of code, one missing argument, a bill nobody approved:

// The component mounts. The effect has no deps array.
useEffect(() => {
  fetch('/api/recommend').then(r => setItems(r));  // setItems re-renders...
});                                                 // ...which re-runs this.

// Every render fires another paid call. The loop never stops.
// By morning: 4.1M requests to the recommendations API.

useEffect(() => {
  fetch('/api/recommend').then(r => setItems(r));
}, []);                                             // runs once. fixed.
How IOnclad catches it

CostGuard is a dedicated cost-and-resilience scanner that runs 100% offline. It reads your JavaScript and TypeScript, never sends it anywhere, and is built to stay quiet on safe code:

CostGuard finds known patterns and common misconfigurations. It is not a guarantee or a load test, and LaunchGuard guidance is advisory, not legal or financial advice.

How to fix it

Find every secret before someone else does.

Scan your code and full git history in under a minute. Free verdict, no signup, nothing leaves your machine.