Refuse a malformed list limit instead of silently coercing it - #500
Merged
davidmckayv merged 2 commits intoSep 12, 2026
Merged
Conversation
Ayush7614
requested review from
MikeRyanDev,
davidmckayv,
guidovizoso,
mxmzb and
tylerslaton
as code owners
September 12, 2026 12:16
# Conflicts: # CHANGELOG.md
davidmckayv
approved these changes
Sep 12, 2026
davidmckayv
left a comment
Contributor
There was a problem hiding this comment.
Deep-reviewed clean (validation, no secret leak, fail-closed, agrees with existing layers). CI green.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this changes
Fixes #496. One shared parser for every paged list, factoring out the audit list's rule:
GET /channelsandGET /api/admin/peopleread?limit=withNumber.parseInt, which coerces:?limit=12abcarrived as 12,?limit=3.9as 3,?limit=0x10as 0, and each answered 200 with a silently wrong page. There was no 400 path at all.server/src/paging.ts parsePageLimit(same trim +/^\\d+\$/rule asauditQueryFromUrl): absent/blank leaves the store default alone, a run of digits is clamped into1..maxagainst the same ceiling the store enforces (MAX_CHANNEL_PAGE/ peopleMAX_PAGE, now exported so edge and store cannot drift apart), and anything else is a 400 naming the parameter before the database is reached. The stores' own clamps stay as the second line of defence.Where it runs
Stateless query parsing in the server process. Same 400/clamp on every replica.
Postgres is already there and is the default answer to all of the above: nothing here needs it.
Boundary and audit
Changelog
CHANGELOG.mdunderUnreleased: list limit validation entry.Proof
bun test server/tests/paging.test.ts: 14 pass (absent/blank, well-formed, clamped 0/huge, 7 coerced shapes refused).bun test server/tests/channel-routes.test.tsnewchannel list limitblock: 9 pass (5 coerced refused with 400 + store never reached, 3 well-formed incl. clamp, absent default). File total 58 pass vs 49 on clean tree; the 29 remaining failures are pre-existing DB integration tests needing postgres (identical 29 fail on clean upstream main without my change).bun test server/tests/paging.test.ts server/tests/audit.test.ts server/tests/audit-cursor.test.ts: 25 pass, 0 fail.bunx biome format+bunx biome lint --error-on-warningson touched files: clean.bunx tsc --noEmit -p server/tsconfig.json: only the 3 pre-existingcopilot.tsmissing-module errors, identical on clean upstream main; nothing in touched files.