Research 16 min read Sep 17, 2026 5

Negative Numbers, Unlimited Queries: Testing the Full Numeric Domain of API Limits

A numeric API limit can fail when negative values, sentinels, or conversions change its meaning downstream. We examine validation against the field's actual business boundary.

APISecurity GraphQLSecurity BusinessLogic InputValidation SecureCoding FuzzTesting
S
SecureHup Team
Security Researcher · SecureHup
Negative Numbers, Unlimited Queries: Testing the Full Numeric Domain of API Limits

An export API documents limit as an integer between 1 and 100. The handler checks only limit > 100. A caller sends -1; the database adapter interprets that sentinel as no limit and returns the full result set. Schema validation passed, the comparison passed, and the response violated the business boundary the field was meant to enforce.

Numeric parameters are compact policy languages. Page size controls volume, timeout controls authority duration, retry count controls external work, quantity controls money or inventory, and concurrency controls fan-out. Type checking confirms representation; it says nothing about units, closed intervals, sentinel values, overflow, conversion, or downstream meaning.

Testing should therefore assert the protected effect. A 200 for a negative number may be safe if the server normalizes it. A finding exists when the request exceeds the declared row, cost, time, money, or work boundary. Trace the value across resolver, service, adapter, queue, billing, and serialization before deciding what it did.

Key Takeaways

  • A numeric type is not a numeric policy; every security-relevant number needs an explicit accepted domain, units, and boundary behavior.
  • Checks that enforce only value <= max often admit negative values, while checks that enforce only value >= min may admit unbounded positive work.
  • Different layers can assign different semantics to the same value, turning a harmless-looking negative, zero, null, or overflowed number into a limit bypass.
  • Pagination and batch controls must be enforced at the data-access or work-admission boundary, not only in a client or GraphQL resolver.
  • Strong testing observes rows, objects, tasks, cost, or state changes rather than treating a successful response as proof of impact.
  • Durable fixes use shared bounded types or constructors so invalid numeric states cannot travel through the application.

Every Numeric Parameter Has a Contract

Developers often document a field with a type and a short description:

take: Int
Maximum number of records to return.

The actual contract is much larger:

accepted values: integers from 1 through 100
omitted value: server chooses 25
null: rejected or treated exactly like omission
zero: returns zero records, not unlimited records
negative: rejected before data access
too large: rejected or clamped according to documented policy
overflow: rejected during coercion
units: records
scope: per request, after authorization filters
enforcement: query builder and response serializer

If any element is undefined, a downstream component will define it implicitly. That implicit behavior is where bypasses appear.

Type Validity Is Only the First Gate

GraphQL's standard integer type has representation and coercion rules. A valid signed integer can still be invalid for a page size. JSON similarly distinguishes numbers at the syntax level but leaves application range and meaning to code. A language runtime may accept a value that the database driver, ORM, cache, or message broker interprets differently.

Security review should separate three layers:

  1. Representation domain: Can the transport and runtime represent the value?
  2. Business domain: Is the value meaningful and permitted for this field?
  3. Effect domain: What work or access does the downstream system perform with it?

Passing layer one says nothing about layers two and three.

How Negative Values Escape Maximum Checks

A common implementation looks reasonable:

if requested > configured_max:
    reject

query.limit(requested)

For a maximum of one hundred, -1 > 100 is false. The value proceeds. What happens next depends on the adapter:

  • It may interpret -1 as no limit.
  • It may omit the limiting clause.
  • It may count from the end.
  • It may cast to an unsigned value and become enormous.
  • It may throw an exception after expensive work begins.
  • It may normalize to zero.
  • It may use a framework-specific sentinel.

The vulnerability is not the negative number alone. It is the composition of an incomplete policy check with a downstream semantic that increases authority or work.

Maximum Is Not a Complete Predicate

For a bounded count, the invariant is normally:

minimum <= requested <= maximum

But even that may be incomplete. The field may also require:

  • Integer, not floating point.
  • Finite value.
  • Exact unit.
  • No implicit string coercion.
  • No scientific notation.
  • No negative zero ambiguity.
  • No overflow during conversion or arithmetic.
  • No multiplication that exceeds a downstream bound.
  • Consistent behavior when omitted or null.

The safest design validates once into a domain-specific type such as PageSize, RetryBudget, or PositiveQuantity, then prevents raw numbers from reaching deeper layers.

The Dangerous Values Are More Than Negative One

Testing only -1 creates a shallow regression. Numeric-domain testing should cover semantic classes.

Negative Values

Try boundary-adjacent and extreme negatives: -1, minimum minus one, a large negative, and the minimum representable integer. Different code paths may use negation, absolute value, or unsigned casts that behave differently at the minimum integer.

Zero

Zero is often ambiguous. It can mean none, default, unlimited, disabled, or immediate. A batch size of zero might correctly process nothing, while a timeout of zero might disable timeout enforcement. Document field-specific behavior.

Maximum Plus One

This is the conventional upper-bound test and should remain. Also test the largest transport-representable value and a value that overflows after multiplication, such as count * item_size or page * page_size.

Fractions and Scientific Notation

The API layer may reject them, round them, truncate them, or coerce them. Alternative transports can differ. A REST endpoint receiving JSON and a GraphQL resolver calling the same service should produce the same domain decision.

Strings and Booleans

Loose coercion can turn "0", "-1", empty string, true, or whitespace into numbers. Some languages treat failed conversion as zero, which can activate a sentinel meaning.

Null and Omission

They are not automatically equivalent. Omission may apply a safe default while explicit null bypasses a constructor, or the reverse. Follow both paths to the effective query or operation.

Duplicate and Conflicting Values

HTTP query parameters can repeat. Gateways, frameworks, and application code may select first, last, or all values. A validator may inspect limit=10 while the data layer consumes limit=-1 from a different representation.

Numeric Limits Protect More Than Availability

The obvious impact of an unlimited query is resource consumption, but numeric-policy failures can cross several security properties.

Field Intended policy Unsafe alternate meaning Potential impact
Page size Bounded records Negative means unlimited Data overexposure, memory pressure
Offset Non-negative position Negative rewinds or errors late Unexpected records, expensive scans
Batch count Bounded operations Zero/negative disables cap Fan-out, queue saturation
Quantity Positive purchasable amount Negative credits inventory or balance Financial abuse
Price/discount Bounded rate Sign inversion Underpayment or payout
Retry count Small non-negative count Negative means infinite/default External-call amplification
Timeout Positive duration Zero disables timeout Resource retention
Concurrency Positive worker cap Negative/zero selects automatic maximum Cost and availability
Retention days Approved range Negative skips deletion or deletes immediately Compliance or data loss
Token lifetime Positive bounded duration Overflow or sign changes expiry Authentication persistence
Coordinate/range Valid ordered interval End before start underflows length Large allocation or policy bypass
Version number Existing positive version Negative selects latest/sentinel Authorization on unintended object

This is why business logic testing must observe the protected effect. A negative page size that safely returns an empty list is not equivalent to a negative quantity that increases account credit.

Cross-Layer Semantics Create the Bypass

Numeric data often changes shape several times:

JSON or GraphQL value
  -> framework scalar
  -> resolver argument
  -> service DTO
  -> language integer
  -> ORM option
  -> database dialect
  -> result serializer

Each conversion can reinterpret the value. A resolver may check the original integer, while a helper substitutes a default for values evaluated as false. An ORM may use a negative sentinel. A database driver may reject it. A serializer may apply a second cap after all rows were loaded, hiding the expensive query while protecting only response size.

Clamping Can Hide Inconsistency

Clamping values into a permitted range is sometimes appropriate, particularly for public read APIs. But silent clamping creates ambiguity and can differ across layers. If the resolver clamps to one hundred but a background export path calls the service directly, the service may remain unbounded.

When clamping is used:

  • Perform it in one authoritative domain function.
  • Record the effective value.
  • Apply the same rule across transports and workers.
  • Ensure cost is bounded before clamping requires expensive parsing.
  • Return documentation-consistent behavior.

For security-sensitive quantities, explicit rejection is often easier to reason about and audit.

Put Enforcement Near the Protected Effect

Client-side controls improve usability but do not enforce policy. Resolver checks are useful but can be bypassed by another adapter. The deepest reusable service or repository layer should require a validated bounded type.

An effective pattern is:

raw input
  -> strict transport coercion
  -> domain constructor(min, max, units)
  -> authorization and tenant filter
  -> bounded query/work admission
  -> bounded serialization

The data-access layer should never interpret arbitrary caller numbers as sentinels. Sentinel values can exist internally, but they should be represented by explicit types or options that untrusted input cannot construct.

Authorization Filters Must Come First

For list operations, apply tenant and object authorization before pagination. Fetching an unlimited global set and filtering in application memory is both a data-exposure risk and a resource risk. The database query should include authorization predicates and a validated limit in the same execution plan.

Code-Aware Autonomous Testing Workflow

Numeric-domain testing becomes useful when it is guided by code and effect.

1. Discover Numeric Inputs

Extract arguments from schemas, route definitions, request types, forms, configuration, queue messages, and internal APIs. Prioritize names such as limit, take, first, last, offset, count, quantity, amount, price, timeout, ttl, retry, workers, depth, version, and days.

2. Infer the Intended Domain

Use documentation, UI constraints, constants, database schemas, tests, and business rules. Do not assume every count starts at one; zero can be legitimate. Record uncertainty instead of inventing a policy.

3. Trace the Value to Its Effect

Follow validation, conversion, defaulting, arithmetic, query-building, scheduling, and serialization. Identify every alternate entry point that calls the same service without the primary resolver.

4. Create Synthetic Oracles

Use controlled data with known counts, a fake downstream provider, a bounded queue, or a disposable ledger. The oracle should answer how many records, jobs, retries, dollars, or state transitions actually occurred.

5. Exercise Equivalence Classes

Test valid boundaries and invalid semantic classes, not every integer. Include direct variables, inline literals, alternate transports, duplicate parameters, stored values, and worker messages where applicable.

6. Replay After Remediation

Confirm early rejection and confirm valid behavior at the minimum and maximum. A fix that rejects every value or silently changes default pagination is not complete.

Practical Numeric-Domain Test Matrix

Input class Example Expected policy Effect to verify
Omitted No field Documented default Default bounded work
Null Explicit null Reject or same as omission No alternate unbounded path
Minimum 1 for positive count Allow Exactly one unit
Zero 0 Field-specific behavior None/default is explicit
Negative adjacent -1 Reject No query sentinel reached
Large negative Far below minimum Reject No unsigned cast or overflow
Maximum Configured cap Allow Work equals cap
Maximum plus one Cap + 1 Reject or documented clamp Work never exceeds cap
Largest integer Transport maximum Reject No overflow in adapter
Minimum integer Transport minimum Reject No negation overflow
Fraction 1.5 Reject for integer field No truncation
Scientific notation Equivalent large value Consistent coercion No parser discrepancy
Numeric string "10" Strict documented behavior No loose conversion
Empty string "" Reject No conversion to zero sentinel
Boolean true Reject No conversion to one
Duplicate values Safe then unsafe Reject ambiguity Validator/consumer use same value
Product overflow page * size exceeds range Reject No wrapped offset
Alternate API Same service via REST/worker Same domain policy Adapter parity
Concurrent requests Valid near-cap values Aggregate quota Tenant remains bounded

The matrix should be specialized per field. A timeout and a quantity do not share the same correct zero behavior.

Evidence of Downstream Resource Impact

A report should show:

  • Field, endpoint, operation, and authenticated role.
  • Intended minimum, maximum, default, and units.
  • Where that policy is declared.
  • Exact input class and effective normalized value.
  • Resolver and downstream adapter behavior.
  • Query, task, ledger, inventory, or state effect.
  • Number of returned and internally fetched records.
  • Tenant and authorization filters applied.
  • Resource, financial, confidentiality, or integrity impact.
  • Required data volume and deployment settings.
  • Valid boundary control and fixed-build replay.

Avoid claiming "unlimited" from a 200 response alone. Count the result and inspect downstream work. A backend may safely clamp while the response omits that detail. Conversely, a response may return one hundred records after the service fetched a million and sliced them in memory. Both output and internal cost matter.

Severity is contextual. Exposing all records within the caller's tenant may be availability or excessive-data risk. Bypassing authorization filtering can become cross-tenant disclosure. Negative quantity can create financial integrity impact. Infinite retries can affect a paid provider or shared queue. Describe the proven effect rather than assigning one consequence to every numeric bug.

Remediation Patterns

  1. Define minimum, maximum, default, units, and zero/null behavior for every security-relevant number.
  2. Parse with checked conversion and reject overflow or unsupported coercion.
  3. Construct a domain-specific bounded type once.
  4. Remove public access to internal sentinel values.
  5. Enforce the bound in the shared service or data-access layer.
  6. Apply authorization predicates before limit and offset.
  7. Use checked arithmetic for offsets, ranges, prices, and durations.
  8. Bound response serialization independently from query execution.
  9. Add per-principal and per-tenant aggregate quotas where repeated near-cap requests matter.
  10. Return deterministic errors without leaking hidden maximums when that information is sensitive.
  11. Retain cross-adapter numeric-domain regression tests.

Schema directives can express some constraints, but do not rely on them as the only control. Internal callers, background workers, migrations, and alternate APIs may bypass transport validation. Domain invariants belong in reusable application code.

CI/CD and Observability

Trigger focused tests when schema arguments, DTOs, coercion helpers, ORM versions, query builders, pagination libraries, billing arithmetic, queue settings, or service adapters change. A diff-aware test can identify affected numeric fields and run their equivalence classes without a full application scan.

Useful telemetry includes:

  • Rejected values by semantic class, without logging secrets.
  • Effective page or batch size after normalization.
  • Rows scanned versus rows returned.
  • Queue tasks admitted per request and tenant.
  • Retry and timeout values at execution.
  • Arithmetic overflow and conversion errors.
  • Requests at the configured maximum.
  • Negative and zero values by endpoint.
  • Adapter disagreement between API and worker paths.
  • Resource cost per returned item.

Do not automatically treat every negative input as an attack. Some domains legitimately use signed numbers. Alert when a value violates the field's declared domain or produces an effect beyond policy.

Release Acceptance Criteria

A numeric-policy fix is ready to ship only when the allowed and denied domains are both observable at the effect boundary. The release evidence should name the field, policy version, minimum, maximum, units, default, and behavior for zero, null, and omission. It should then show that the minimum and maximum still produce the intended work while every adjacent invalid class is rejected before query construction, job admission, billing, or state mutation.

Run the same cases through each reachable adapter. A resolver-level fix is incomplete when a REST route, scheduled worker, import job, or internal RPC can still pass a raw number to the shared service. Compare normalized values and effect counts across those paths. If clamping is intentional, record both the requested and effective value and prove that no expensive operation happened before normalization.

Risk acceptance should not say only that "the endpoint is authenticated." Record which roles can reach it, the maximum effect one request can cause, aggregate tenant controls, monitoring, owner, and expiry. An authenticated low-privilege user may still exploit a query or cost amplification path repeatedly. The final gate should fail when a test obtains more records, tasks, retries, time, money, or authority than the domain contract allows, even if the response status and schema remain valid.

Numeric Domain Review Questions

  • Is the numeric field's domain documented beyond its type?
  • Are minimum, maximum, default, units, zero, null, and omission defined?
  • Can negative values pass a one-sided maximum check?
  • Can zero disable a limit, timeout, retry, or safety feature?
  • Can values overflow during conversion, multiplication, addition, or negation?
  • Do duplicate parameters produce ambiguous selection?
  • Do GraphQL, REST, workers, and internal calls share enforcement?
  • Does any downstream adapter assign sentinel meaning to raw input?
  • Are authorization filters applied before pagination?
  • Are rows fetched, tasks created, and costs observed, not only response length?
  • Are aggregate tenant limits present for repeated near-cap calls?
  • Does CI test valid boundaries as well as invalid classes?

Frequently Asked Questions

Does a GraphQL Int automatically reject negative values?

No. A signed integer can be valid at the GraphQL type level. Whether a negative value is permitted depends on the argument's application-specific domain. A page size usually needs an explicit non-negative or positive constraint.

Is clamping a value to the maximum safer than rejecting it?

It can be, if the behavior is deliberate, centralized, documented, and consistent across every caller. For financial or security-sensitive fields, rejection is often clearer. Silent clamping must not hide expensive work performed before the clamp.

Why test zero separately from negative values?

Zero frequently acts as a sentinel for default, unlimited, disabled, immediate, or empty behavior. Its correct meaning differs by field, so it deserves an explicit invariant and regression case.

Can a response cap protect an unbounded database query?

Not necessarily. The service may fetch or scan a large result and then serialize only a small subset. Enforce the limit in the query or work-admission layer and observe internal rows or cost.

What should an autonomous test use as its oracle?

Use the protected effect: returned records, rows scanned, jobs created, provider calls, account balance, inventory, token lifetime, or state transitions. Status codes alone cannot distinguish a safe normalization from a real bypass.

The Business Limit Lives in the Observable Effect

Generic integers become easier to govern when the application gives them domain meaning: a unit, minimum, maximum, default, and clearly defined exceptional values. Validation before conversion catches one class of error, while a second boundary near row selection, retry creation, duration, quantity, or fan-out protects the effect from downstream sentinel and overflow behavior.

We prefer tests organized around semantic classes and observed outcomes rather than a catalogue of unusual numbers. The lasting assertion is not that -1 always returns one status code. It is that no representable input can make the operation exceed the business or resource limit the field is supposed to express.

#APISecurity #GraphQLSecurity #BusinessLogic #InputValidation #SecureCoding #FuzzTesting

Was this helpful?
Share :

Find issues like this in your own codebase

SecureHup runs this exact exploit chain — discovery, enumeration and cross-tenant validation — on every deployment, automatically.