A counter moved the wrong way. The request path will tell us whether that matters.
At 09:20, a tenant with no export units left submitted a request for -1 rows. The
endpoint returned an error from the export worker, but the quota ledger recorded a one-unit
credit. A support screenshot alone cannot tell us whether the counter is authoritative, whether
a later export used the credit, or whether the display is stale.
First compare the request ID, raw validated payload, quota ledger entry, counter snapshot, and worker result. Check whether a retry reused the same request ID. Reproduce only with a disposable tenant and a fake worker; do not send crafted quantities to a customer’s live account.
- Asset
- Tenant export allowance and the data returned by the export worker.
- Caller controls
- The requested row count. Authentication identifies the tenant; it does not make the count trustworthy.
- State
- A remaining-unit counter and an export job record.
- Invariant
- Each accepted request reserves an integer quantity from 1 through 5,000 and cannot make remaining quota increase.
Caller chooses the submitted value.
Parsing may reject, convert, or round.
Does it define the full allowed range?
Confirm what persisted and what ran.
A numeric value has at least two contracts here: a representation contract (what values the language and parser can represent) and a business contract (what quantities this API permits). A check that handles only the upper bound can satisfy neither. The important question is not “is this a number?” but “does this exact value have the intended meaning at the state-changing boundary?”
Follow the value across parsing, checking, storage, and work.
A dashboard may show remaining = 0, yet the handler, a queue message, and a
database column can each hold a different representation. Trace the request ID through
those boundaries. Record the exact value after parsing, the predicate result, the
persisted delta, and the count given to the export worker. Avoid logging sensitive row
contents; the quantity and identifiers are usually enough for this check.
Separate the observations from the hypothesesOpen after naming two plausible causes
- Observation
- The ledger contains a positive one-unit delta after the request for −1.
- Possible causes
- The handler negated an unchecked negative quantity; a retry credited twice; or the dashboard is showing a stale projection.
- Discriminating check
- For this request ID, compare raw parsed units, the exact ledger operation, committed counter value, and projection offset. Replay the request against a disposable fixture once.
- Conclusion in this fixture
- The handler accepts −1 because it checks only
units > remaining; storage subtracts units, so the counter rises by one. This proves a quota accounting flaw in this path, not that an export occurred.
The negative value is a useful minimal reproduction because it separates the predicate
from the storage operation. If a request for 1 is accepted at zero quota, investigate
stale state, tenant selection, and transaction timing too. If only large values fail, inspect
parsing, conversions, and arithmetic range. Do not assume every numeric anomaly is an overflow.
The upper bound is present. The lower bound is missing.
The intentionally flawed functions below isolate the numeric predicate. They are examples
for reading, not live endpoints. Their store interfaces leave persistence behind a
boundary; the lesson’s demonstrated effect is what happens when a store interprets consume(key, units) as subtracting that quantity from the remaining counter.
The same scenario appears in TypeScript and Go; their number representations differ.
// Intentionally flawed: negative units pass the upper-bound check and add quota back.
export async function requestExportVulnerable(
store: QuotaStore,
keyID: string,
units: number
): Promise<void> {
const remaining = await store.remaining(keyID);
if (units > remaining) throw new QuotaExceeded();
await store.consume(keyID, units);
} // Intentionally flawed: a negative request passes and subtraction increases quota.
func RequestExportVulnerable(store QuotaStore, keyID string, units int64) error {
remaining, err := store.Remaining(keyID)
if err != nil {
return err
}
if units > remaining {
return ErrQuotaExceeded
}
return store.Consume(keyID, units)
} Validate the business range before reserving shared state.
The first repair is a complete range: a positive integer no greater than the product
limit. TypeScript’s ordinary number is binary floating point, and integer
values are only exactly distinguishable through ±(253−1). Number.isSafeInteger rejects fractions, infinities, and integers outside that exact
range. If a JSON integer was already rounded during parsing, validate the original decimal string
or use a parser that preserves it; checking the rounded number cannot reconstruct lost digits.
Go’s int64 exactly represents signed integer values in its fixed range, but
arithmetic can overflow with deterministic wraparound semantics. A type does not establish
the product’s maximum either. Here the explicit bound of 5,000 keeps this particular
subtraction far from int64 limits; in other code, validate before adding or multiplying, or use checked
operations when the business range can approach the representation boundary.
Both variants reject values outside the same API contract before touching quota state.
const MAX_EXPORT_UNITS = 5_000;
export async function requestExportSafely(
store: QuotaStore,
keyID: string,
units: number
): Promise<void> {
if (!Number.isSafeInteger(units) || units < 1 || units > MAX_EXPORT_UNITS) {
throw new InvalidUnits();
}
if (!(await store.reserve(keyID, units))) throw new QuotaExceeded();
} const maxExportUnits int64 = 5_000
func RequestExportSafely(store QuotaStore, keyID string, units int64) error {
if units < 1 || units > maxExportUnits {
return ErrInvalidUnits
}
reserved, err := store.Reserve(keyID, units)
if err != nil {
return err
}
if !reserved {
return ErrQuotaExceeded
}
return nil
} For money, do not compare binary floating-point amounts with equality at an authorization threshold. Choose a defined currency precision, parse and validate it deliberately, and use integer minor units or a decimal type with explicit rounding rules. The rule must include edge cases such as refunds, negative adjustments, conversion, and rounding direction.
Test the boundary values and the state they can change.
A useful regression matrix includes -1, 0, 1, the
exact product maximum, one above that maximum, a fraction, NaN or infinity
where the boundary can receive them, and values at the language’s representation edge.
Confirm that invalid inputs create no reservation and no job. For valid inputs, assert the
committed remaining quota and the job quantity. In TypeScript, also test JSON text around Number.MAX_SAFE_INTEGER; in Go, test values around math.MaxInt64 if
the API or arithmetic can reach them.
Then exercise the actual store implementation with a concurrent boundary case and a failed job insert. Those checks establish whether reservation and job creation are atomic for the deployed database. A unit test of the validator cannot establish persistence behavior.
Invalid sign, empty work, fractional unit.
Valid integer quantity inside policy.
First value outside the business maximum.
Check persisted effects, not just errors.
Read the isolated example filesFull context and boundaries
export class QuotaExceeded extends Error {}
export class InvalidUnits extends Error {}
export interface QuotaStore {
remaining(keyID: string): Promise<number>;
consume(keyID: string, units: number): Promise<void>;
reserve(keyID: string, units: number): Promise<boolean>;
}
// Intentionally flawed: negative units pass the upper-bound check and add quota back.
export async function requestExportVulnerable(
store: QuotaStore,
keyID: string,
units: number
): Promise<void> {
const remaining = await store.remaining(keyID);
if (units > remaining) throw new QuotaExceeded();
await store.consume(keyID, units);
}
const MAX_EXPORT_UNITS = 5_000;
export async function requestExportSafely(
store: QuotaStore,
keyID: string,
units: number
): Promise<void> {
if (!Number.isSafeInteger(units) || units < 1 || units > MAX_EXPORT_UNITS) {
throw new InvalidUnits();
}
if (!(await store.reserve(keyID, units))) throw new QuotaExceeded();
}
package numericlimits
import "errors"
var ErrQuotaExceeded = errors.New("export quota exceeded")
var ErrInvalidUnits = errors.New("invalid export units")
type QuotaStore interface {
Remaining(keyID string) (int64, error)
Consume(keyID string, units int64) error
Reserve(keyID string, units int64) (bool, error)
}
// Intentionally flawed: a negative request passes and subtraction increases quota.
func RequestExportVulnerable(store QuotaStore, keyID string, units int64) error {
remaining, err := store.Remaining(keyID)
if err != nil {
return err
}
if units > remaining {
return ErrQuotaExceeded
}
return store.Consume(keyID, units)
}
const maxExportUnits int64 = 5_000
func RequestExportSafely(store QuotaStore, keyID string, units int64) error {
if units < 1 || units > maxExportUnits {
return ErrInvalidUnits
}
reserved, err := store.Reserve(keyID, units)
if err != nil {
return err
}
if !reserved {
return ErrQuotaExceeded
}
return nil
}
Choose controls that match both the numeric type and the business rule.
A tenant with zero export units submits −1, then a very large integer. What do you change first?
The ledger shows a credit, while the worker reports no rows exported. You have the request ID and can reproduce against a disposable tenant. Which investigation and repair best preserves the contract?
The confirmed example bug is narrow: an unchecked negative quantity passes an upper-bound-only predicate and can increase the counter when the store subtracts it. Further over-quota export depends on whether that counter is authoritative, whether credited quota is spendable, whether the job can be retried, and what the worker returns. Verify those links before estimating exposure.
For language behavior, consult MDN’s documentation for Number.isSafeInteger and Number.MAX_SAFE_INTEGER, and the Go language specification’s integer overflow rules. OWASP’s Input Validation Cheat Sheet discusses range and business validation. Checked 2026-10-01; these references describe platform and general validation behavior, not this fictional service’s actual exposure.
Connections to follow nextRelated lessons
The Math in Practice lessons Signed numbers, overflow, and fixed-width values and Floating point and rounding build the representation intuition used here. Race conditions and limit overruns covers the concurrent check-and-update problem that remains after a quantity is validated.