Skip to content

fix(web): validate page and limit query params on caps and space dashboard (#3567) - #3574

Open
ghazy2001 wants to merge 1 commit into
CapSoftware:mainfrom
ghazy2001:fix/dashboard-pagination-params-validation-3567
Open

ghazy2001 wants to merge 1 commit into
CapSoftware:mainfrom
ghazy2001:fix/dashboard-pagination-params-validation-3567

Conversation

@ghazy2001

@ghazy2001 ghazy2001 commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Fixes #3567
Linear: CAP-957

Opening /dashboard/caps?page=-1, /dashboard/caps?page=1.5, or /dashboard/spaces/[spaceId]?limit=2.5 resulted in a client-side exception crash due to invalid MySQL LIMIT and OFFSET syntax.

Root Cause

Both apps/web/app/(org)/dashboard/caps/page.tsx and apps/web/app/(org)/dashboard/spaces/[spaceId]/page.tsx parsed page and limit using Number(searchParams.page) || 1 and Number(searchParams.limit) || 15.
Negative (-1) and fractional (1.5, 2.5) numbers are truthy, passing through into the query's OFFSET (page - 1) * limit and LIMIT limit without being validated as positive integers.

Changes

  • Created apps/web/lib/pagination.ts providing parsePageParam and parseLimitParam to validate that page and limit inputs are integers ≥ 1, with bounds and default values.
  • Refactored parsePublicCollectionPage in apps/web/lib/public-collections-policy.ts to delegate to parsePageParam.
  • Replaced unvalidated Number(...) parsing in CapsPage and SharedCapsPage with parsePageParam and parseLimitParam.
  • Added unit tests in apps/web/__tests__/unit/pagination-params.test.ts verifying missing, empty, zero, negative, fractional, array, and oversized inputs.

Test plan

  • Ran unit tests with Bun:
    • bun test apps/web/__tests__/unit/pagination-params.test.ts (6 pass, 23 assertions)
    • bun test apps/web/__tests__/unit/activity-time-ago.test.ts (3 pass, 13 assertions)
    • bun test apps/web/__tests__/unit/video-thumbnail-duration.test.ts (4 pass, 17 assertions)
  • Verified page=-1 or page=1.5 cleanly falls back to page 1 instead of throwing syntax errors in SQL queries.

RetriggerView in GreptileConfidence Score: 4/5

Bound page values before merging so oversized inputs cannot still break dashboard queries.

Findings

  1. P1 Large pages still fail ▶
  2. P2 Next repeats the first page ▶
Fix with agent prompt
### Issue 1
apps/web/lib/pagination.ts:8
`parsePageParam` accepts enormous pages such as `1e308`. With the default limit of 15, `(page - 1) * limit` becomes `Infinity`, which both dashboards pass to `.offset()`. That cannot be a valid MySQL offset, so a crafted URL can still make the dashboard query fail. Bound the page so the calculated offset stays within a safe integer range, and add a test for oversized pages.

### Issue 2
apps/web/app/(org)/dashboard/caps/page.tsx:137
The server now falls back to page 1 for fractional values, but `Caps` and `SharedCaps` still read the raw page with `Number(...) || 1`. Opening `?page=1.5` shows page 1's videos while marking page 1.5 as current. The next link points to page 2.5, which again loads page 1. Use the same parser in both clients, or pass the parsed page from the server.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

Adds shared helpers to check dashboard page and limit values.

  • Dashboard pages use checked URL values for pagination.

Reviews (1) · Last reviewed commit: "fix(web): validate page and limit query ..." · Reviewed by Greptile

…board (CapSoftware#3567)

caps/page.tsx and spaces/[spaceId]/page.tsx parsed page and limit via Number(value) || default, allowing negative (-1) and fractional (1.5, 2.5) numbers to reach MySQL OFFSET and LIMIT clauses, causing syntax errors and client-side page crashes.

Introduced parsePageParam and parseLimitParam in apps/web/lib/pagination.ts to validate positive integers with safe fallbacks and bounds, reused by parsePublicCollectionPage. Added comprehensive unit tests.

Closes CapSoftware#3567
@vercel

vercel Bot commented Oct 29, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
cap-status Skipped Skipped Oct 29, 2026 10:14pm UTC

const value = Array.isArray(page) ? page[0] : page;
const parsed = Number(value);

if (!Number.isInteger(parsed) || parsed < 1) return defaultValue;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Large pages still fail

parsePageParam accepts enormous pages such as 1e308. With the default limit of 15, (page - 1) * limit becomes Infinity, which both dashboards pass to .offset(). That cannot be a valid MySQL offset, so a crafted URL can still make the dashboard query fail. Bound the page so the calculated offset stays within a safe integer range, and add a test for oversized pages.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/web/lib/pagination.ts
Line: 8

Comment:
**Large pages still fail**

`parsePageParam` accepts enormous pages such as `1e308`. With the default limit of 15, `(page - 1) * limit` becomes `Infinity`, which both dashboards pass to `.offset()`. That cannot be a valid MySQL offset, so a crafted URL can still make the dashboard query fail. Bound the page so the calculated offset stays within a safe integer range, and add a test for oversized pages.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.


const page = Number(searchParams.page) || 1;
const limit = Number(searchParams.limit) || 15;
const page = parsePageParam(searchParams.page, 1);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Next repeats the first page

The server now falls back to page 1 for fractional values, but Caps and SharedCaps still read the raw page with Number(...) || 1. Opening ?page=1.5 shows page 1's videos while marking page 1.5 as current. The next link points to page 2.5, which again loads page 1. Use the same parser in both clients, or pass the parsed page from the server.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/web/app/(org)/dashboard/caps/page.tsx
Line: 137

Comment:
**Next repeats the first page**

The server now falls back to page 1 for fractional values, but `Caps` and `SharedCaps` still read the raw page with `Number(...) || 1`. Opening `?page=1.5` shows page 1's videos while marking page 1.5 as current. The next link points to page 2.5, which again loads page 1. Use the same parser in both clients, or pass the parsed page from the server.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

This branch was previously deployed

1 inactive deployment
Preview – cap-status — 0dbf3490 Deployed Oct 29, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

My Caps and space pages crash on a negative or fractional page number

1 participant