When Billing Overflows: How One Integer Can Turn Charges Into Credits
There is a bug pattern that keeps surfacing across LLM gateways, reseller platforms, and homegrown metering layers. It is not a jailbreak and not a prompt injection. It is much older and much more boring: an integer overflow in the billing path, triggered by a parameter the user fully controls. When it fires, the platform does not overcharge the user — it pays them. This is a field note on why it happens, how to recognize it, and how to shut it down.
The shape of the bug
Usage-based billing for AI APIs almost always ends in the same arithmetic: multiply a quantity by a rate, round, and store the result as a signed integer — commonly an int64 representing a fractional currency unit or an internal “quota” point. The quantity is usually a token count. But several request parameters that feed that quantity are supplied by the caller:
- Image count (
n) on an image-generation request — a multiplier on the per-image price. - Video duration or
secondson a video-generation request — a multiplier on the per-second price. - max_tokens /
max_output_tokens, which some pre-charge estimators fold straight into the pre-consumed amount.
Take any of these, make it absurdly large, and the intermediate product grows without bound. A signed 64-bit integer tops out at 9,223,372,036,854,775,807 — about 9.22 × 1018. Push past that and the value does not clamp or error; it wraps around to a negative number. In C, Go, Java, Rust release builds, and plenty of other environments, signed overflow either wraps silently or is explicitly defined to wrap. The moment your computed charge goes negative, the settlement step that was supposed to subtract from the user’s balance adds to it. A charge became a credit.
The attacker never needs to break your auth, your model, or your network. They just send a number your arithmetic cannot hold.
A concrete walk-through
Suppose an image endpoint prices each image at a small per-image quota, and the number of images n is applied as a multiplier. Simplified, the settlement looks like this:
// quota is an int64 of internal billing points
quota := perImageQuota * int64(n) // n comes straight from the request
user.Balance -= quota // "charge" the user
With n = 3 this is a normal charge. With n in the billions or larger, perImageQuota * n sails past the int64 ceiling and wraps negative. Now user.Balance -= (negative) increases the balance. The same story plays out when the multiplier is a floating-point value first — a huge float64 converted back to int64 is undefined-or-wrapping in most languages, and lands negative just as easily.
Decimal libraries do not save you by themselves. A big-decimal type will happily hold a number far larger than int64 — the overflow moves to the moment you call something like .IntPart() or cast the decimal back to a native integer for storage. If that final narrowing step has no bounds check, the wrap happens there instead.
Why it slips through review
Three reasons this bug is so common in AI billing specifically:
- The multipliers are new. Text billing was token-in, token-out — both server-measured. Image
n, videoseconds, and aggressive pre-charge estimators introduced caller-controlled multipliers into a code path that historically only handled server-measured quantities. The validation never caught up. - Nobody tests the absurd input. Test suites cover
n = 1,n = 4, mayben = 10. They do not covern = 9999999999, because no legitimate user sends that. The overflow lives in a region of the input space that only an attacker visits. - The failure is invisible in the happy path. Overflow produces no exception, no 500, no log line. Balances silently drift up instead of down. Unless you are alerting on negative computed charges, the first signal is an accounting discrepancy weeks later.
How to know if you are exposed
You can answer this in an afternoon:
- Enumerate caller-controlled billing inputs. Every request field that ends up multiplied into a charge —
n,seconds/duration,max_tokens, batch sizes, anything similar. - Trace each to the final integer. Follow the value through pre-charge estimation and post-request settlement. Note every point where a large intermediate is narrowed to a native int (a cast, an
IntPart(), a truncation). - Ask one question at each narrowing point: if this value were 1019, what comes out? If the answer is “a negative number” or “undefined,” you have the bug.
- Audit your ledger for negative charges. Query historical billing rows for any charge that came out at or below zero on a paid model. A clean history is reassuring; a single hit is a live incident.
How to defend
The durable fix is two independent layers. Either one alone helps; together they close the class of bug, not just the one instance you found.
1. Clamp at every quota exit (defense in depth)
Wherever a computed charge is narrowed to the integer you store, pass it through a single clamp that (a) compares in a type that cannot overflow — a big-decimal or a saturating check — before the narrowing, and (b) bounds the result to [0, MAX_SAFE]. A charge is never negative; a single request is never astronomically large. Pick a ceiling comfortably below int64 max (say 1015 billing points) so later additions cannot re-overflow, and pin the floor at zero so no arithmetic path can ever credit a user through the charge route.
// One helper, used at every settlement/pre-charge exit.
func clampQuota(d Decimal) int64 {
if d.LessThanOrEqual(Zero) { return 0 } // never credit via a charge
if d.GreaterThan(MaxSafe) { return MaxSafe } // compare before narrowing
return d.RoundToInt64()
}
The key detail: the comparison must happen in the wide type, before you cast down. Clamping after the cast is too late — the wrap has already happened.
2. Validate the inputs at the boundary
Reject the absurd parameter before it ever reaches arithmetic. Cap n, seconds, and max_tokens at sane maxima and return a 400 when a request exceeds them. Make the limits configurable so operations can tune them without a deploy. This layer also spares you the second, quieter problem: a merely large-but-not-overflowing parameter that produces a real, gigantic, legitimate charge you then have to refund and apologize for.
| Layer | Catches | Miss mode if absent |
|---|---|---|
| Input validation (400 on absurd param) | The attack before arithmetic; also legitimate-but-huge requests | Overflow reaches the math; or a real astronomical charge |
| Quota clamp at every exit | Overflow anywhere in the pipeline, including paths you forgot | A new code path re-introduces the wrap |
The wider lesson
This is a reminder that AI billing is a security surface, not just an accounting detail. The interesting attacks on inference platforms are not always exotic. Sometimes the exploit is a forty-year-old integer overflow wearing a new hat, reachable because a caller-controlled multiplier was added to a billing path that used to only see server-measured numbers. Treat every request parameter that touches a charge as hostile input, bound it at the boundary, and clamp it again at the exit. Two cheap checks turn a “charges became credits” incident into a rejected request and a log line.
Q1: Is this a jailbreak or prompt-injection issue?
No. It is a classic integer overflow in the billing arithmetic, triggered by an oversized but perfectly ordinary API parameter. It has nothing to do with the model’s behavior.
Q2: Doesn’t using a big-decimal library prevent it?
Only partly. A big-decimal can hold the oversized intermediate, but the overflow reappears the moment you narrow it back to a native int64 for storage — unless that narrowing step has a bounds check. Clamp before you cast down.
Q3: How do I check whether I have already been hit?
Query your billing ledger for any charge that settled at or below zero on a paid model, and look for user balances that rose without a corresponding payment. A negative computed charge is the signature.
Billing you can trust
BUZZ AI Gateway is pure pay-per-token with bounded, validated billing across text, image, and video endpoints. One key, transparent pricing, no surprises in either direction.
Create an accountLast reviewed: 2026-07-08