Skip to content

fix(api): query pagination accepts non-integer limit/page → fractional OFFSET/LIMIT #785

Description

@ANSHSINGH050404

Describe the bug
POST /v1/query DynamicQueryRequest accepts non-integer limit/page without validation and produces fractional LIMIT/OFFSET that fail ClickHouse Int32 binding.

Affected code:

  • apps/api/src/schemas/query-schemas.ts:49-50 DynamicQueryRequestSchema: t.Optional(t.Number()) — no minimum/integer constraint.
  • apps/api/src/routes/query.ts:294-308 validatePaginationFields only checks <1 and >10000/<1 for page, not Number.isInteger/isFinite.
  • apps/api/src/routes/query.ts:1040-1041 limit: request.limit || 100, offset: request.page ? (request.page-1)*(request.limit||100) :0 → with limit=1.5, page=1.5 yields offset=0.75 (fractional).
  • packages/ai/src/query/simple-builder.ts:1148-1154 buildLimitClause/buildOffsetClause emit LIMIT 1.5 OFFSET 0.75 vs. builders LIMIT {limit:Int32} OFFSET {offset:Int32} (packages/ai/src/query/builders/pages.ts:182, sessions.ts:318, etc.).
  • Internal packages/ai/src/query/index.ts:61-62 QuerySchema: z.number().min(1).max(1000) also allows floats (missing .int()).

To Reproduce

  1. Authenticated request:
POST /v1/query?website_id=<id>&timezone=UTC
Content-Type: application/json
{
  "parameters": ["top_pages"],
  "startDate": "2026-01-01",
  "endDate": "2026-01-02",
  "limit": 1.5,
  "page": 1.5
}
  1. Also try {"limit": 0.5}, {"page": 1.5}, {"limit": 1.5, "page": 2.3}.

Expected behavior
400 VALIDATION_ERROR with details like:

{ "field":"limit", "message":"Limit must be an integer" }
{ "field":"page", "message":"Page must be an integer" }

limit/page should require finite integer, limit 1..10000, page 1...

Actual behavior
No validation error; request reaches ClickHouse with fractional values. Reproduced locally with copy of validatePaginationFields (D:\tmp\repro_pagination.js):

float limit {limit:1.5} => errors=[] limit=1.5 offset=0  BUG non-integer passed
float page {page:1.5} => errors=[] limit=100 offset=50  BUG
both float => offset 0.75 BUG not integer
Infinity/NaN similarly slip through (JSON NaN not encodable but Elysia coercion from query string can produce floats).

buildLimitClause(NaN)'' (unbounded), buildLimitClause(0)''.

Screenshots
N/A

Environment

  • OS: Windows, Bun 1.4.2 (package.json:30 bun@1.4.1 pinned)
  • Commit: 92c15273d (origin/staging)

Additional context

  • limit default fallback || 100 masks 0/NaN in apps/api/src/routes/query.ts:1040, simple-builder.ts:1149 fallback similarly.
  • limit max mismatch: API allows 10000 (validatePaginationFields:301), internal QuerySchema allows 1000 — not part of this bug but worth aligning.
  • No existing open issue covers this (checked gh issue list).
  • Minimal fix proposal: tighten DynamicQueryRequestSchema (TypeBox minimum), add Number.isInteger/isFinite in validatePaginationFields, add .int() to QuerySchema (packages/ai/src/query/index.ts:61-62), add regression test.

AI disclosure
Issue drafted with assistance from Muse Spark (opencode/muse-spark-1.2-contributor-free) and human-verified via local reproduction (node D:\tmp\repro_pagination.js).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions