Negative limit query param reaches Postgres as negative LIMIT (500) #284
Labels
No labels
agent:fix_bugfix
agent:fix_conflicts
agent:fix_security
agent:gap_analysis
agent:implement
agent:implement
agent:implement
agent:open_issues
agent:ready
agent:research
agent:security_audit
agent:verify
architecture
backend
blocker:hybrid
blocker:launch
blocker:limit-orders
blocker:v2
block:log_only
block:security
bug
ci
contracts
correctness
deploy
dev
devops
docs
documentation
duplicate
e2e
enhancement
epic
feature
frontend
functional-completion
gas
good first issue
governance
help wanted
high-risk
hooks
hybrid
indexer
infra
infrastructure
integrators
invalid
launch-blocker
limit-orders
localnet
localterra
low priority
missing-implementation
needs-design
ops
performance
priority
high
priority
medium
product
qa
QA
question
ready
ready
research
scripts
security
security-hardening
smartcontracts
tech-debt
testing
ux
UX
v2
verification
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
code/cl8y-dex-terraclassic#284
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Severity: Informational
Reachability: Unauthenticated HTTP — any list endpoint that clamps
limitwith.min()only.Affected: the
.min()-only limit clamps (indexer/src/api/pairs.rs:351,501;traders.rs:220/277/342/413/471;cg.rs:300).Root cause:
limitis upper-capped but not lower-clamped, so a negative value passes through to Postgres as a negativeLIMIT.Summary
Several handlers do
q.limit.unwrap_or(default).min(max)with no lower bound. Iflimitdeserializes as signed,?limit=-1survives the.min()and reaches Postgres, which rejects a negativeLIMIT— the request 500s instead of returning a clean 400.No DoS, no data exposure, just an ungraceful error on bad input. The endpoints that use
.clamp(1, max)(e.g.cg.rs:52,pairs.rs:157/987/1066) are already fine.Recommended direction
.min(max)limit clamps with.clamp(1, max)everywhere (and reject/normalize negative offsets the same way).Acceptance criteria
?limit=-1(and0) returns a clean result or 400, never a 500.limitto[1, max].Approved
Fixed — swapped the
limitclamps from.min(MAX)to.clamp(1, MAX)so?limit=-1and?limit=0degrade to a 1-row response instead of a negativeLIMIT $N-> Postgres 500.limitisOption<i64>so negatives reach the query; offsets were already lower-guarded (.max(0)+ max-offset 400), so only the limit clamps needed it.Sites: traders.rs (5), pairs.rs (7), cg.rs (1) — plus oracle.rs:108 (
/oracle/history), which the original report didn't list but has the exact sameq.limit.unwrap_or(200).min(1000)->LIMIT $3bug. 14 total. The.clamp(1, MAX)pattern already existed in-tree (pairs.rs:157, cg.rs:52, tokens.rs:71), so this just makes it consistent.cargo check clean. Branch
qa/284-clamp-negative-limit, MR fork→main (no closing keyword). @PlasticDigitsmentioned in merge request !740
mentioned in commit
385e243901mentioned in merge request !749
Heads up — this one isn't fully fixed. The
.min()->.clamp(1, MAX)sweep missed one site and it's a live 500./api/v1/hooks(get_hook_events, hooks.rs:59) still hadparams.limit.unwrap_or(50).min(200)withlimit: Option<i64>feedingLIMIT $N, so a negative limit reaches Postgres as a negative LIMIT. Live:GET /api/v1/hooks?limit=-1-> 500 (controls?limit=5and?limit=0-> 200). The merged fix (c5e5323) touched cg/oracle/pairs/traders but never hooks.rs, and it's the lone surviving.min()limit clamp inapi/.Also: the fix shipped with no regression test for the actual bug — every
*_limit_cappedtest only asserts the upper bound (limit=9999), nothing covers negative/zero, so the suite wouldn't catch this or a re-break.Pushed the fix as MR !749 — hooks.rs:59 ->
.clamp(1, 200)plus a regression test (hooks_negative_and_zero_limit_clamp_to_one_not_500, asserts?limit=-1and=0return 200 and clamp to <=1 row). api_hooks 4/4. Needs to merge before this closes. @PlasticDigitsmentioned in commit
9edb3b6430mentioned in commit
6f620546a6mentioned in merge request !766
mentioned in issue #317
mentioned in commit
f331d38aa5Done on main. The original .min(MAX) -> .clamp(1, MAX) sweep plus the hooks.rs site I'd missed are both merged (MR !749, merge
9edb3b6), and the docs note for the clamp caps landed too (f331d38).Verified:
Scope note: this issue was the negative-limit/500 root cause on the list endpoints, which is fixed. The broader "add the same -1/0 regression coverage to the other clamp sites beyond /hooks" is the follow-up you split out as #317 — not a gap in this fix.
Good to close from my side. @PlasticDigits
mentioned in merge request !773
Verification complete — issue #284
Issue: https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/work_items/284
All acceptance criteria PASS. No repo changes required.
Acceptance criteria
?limit=-1and?limit=0return a clean result or 400, never 500limit=-1andlimit=0.limitto[1, max]indexer/src/api/**— no remainingunwrap_or(…).min(…)upper-only clamps. All handlers use.clamp(1, MAX).Commands run
Code review highlights
hooks.rs:61—params.limit.unwrap_or(50).clamp(1, 200)(the site missed in the first sweep, fixed in MR !749)pairs.rs,traders.rs,cg.rs,oracle.rs,tokens.rs— all use.clamp(1, MAX)limit_clamp_guardrail.rs(static),api_limit_lower_bound.rs(runtime), documented indocs/indexer-invariants.mdScope note
Broader negative/zero regression coverage for non-
/hooksendpoints is tracked separately in #317 (follow-up, not a gap in this fix).mentioned in merge request !959
mentioned in issue #431