Skip to content

fix(follows): cap huge public offsets#357

Closed
Zekbot001 wants to merge 1 commit into
profullstack:masterfrom
Zekbot001:money/ugig-follow-offsets
Closed

fix(follows): cap huge public offsets#357
Zekbot001 wants to merge 1 commit into
profullstack:masterfrom
Zekbot001:money/ugig-follow-offsets

Conversation

@Zekbot001
Copy link
Copy Markdown
Contributor

Summary

  • cap oversized offsets in the public followers and following endpoints before building Supabase ranges
  • keep valid offsets unchanged while bounding extreme requests at 100_000
  • add regression coverage for both mirrored routes and returned pagination metadata

Fixes #356.

Paid task context: https://ugig.net/gigs/abd6b2a0-e728-48cf-a46f-f99e419ed94e

Verification

  • .\node_modules\.bin\vitest.CMD run src/app/api/users/[username]/followers/route.test.ts src/app/api/users/[username]/following/route.test.ts
  • .\node_modules\.bin\eslint.CMD src/app/api/users/[username]/followers/route.ts src/app/api/users/[username]/followers/route.test.ts src/app/api/users/[username]/following/route.ts src/app/api/users/[username]/following/route.test.ts
  • git diff --check

Existing upstream check blockers

  • .\node_modules\.bin\tsc.CMD --noEmit reaches unrelated existing errors in src/app/api/affiliates/offers/route.test.ts:53 and src/app/api/applications/[id]/route.test.ts:21.
  • corepack pnpm run lint reaches unrelated existing react-hooks/refs errors in src/hooks/useMessageStream.ts.
  • corepack pnpm run build compiles successfully and progresses through static generation when given a process-local dummy OpenAI key, then stops because the local checkout has no Supabase service-role configuration.

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 31, 2026

Greptile Summary

This PR caps oversized offset query parameters in the public followers and following endpoints by adding a MAX_FOLLOW_OFFSET = 100_000 constant and threading a max argument through parseNonNegativeInt, consistent with how limit was already bounded via parsePositiveInt.

  • followers/route.ts & following/route.ts: parseNonNegativeInt now accepts and enforces a maximum, bounding any offset above 100,000 before the Supabase .range() call is built.
  • Test files: New regression tests verify that an offset of 999999999 is clamped to 100000 and that the capped value is reflected correctly in both the Supabase call and the pagination response field.

Confidence Score: 4/5

Safe to merge; the fix is narrowly scoped to offset parsing and does not touch auth, data writes, or other query logic.

The clamping logic is correct and the tests verify both the Supabase range call and the pagination metadata. The main concern is that the capped offset is returned silently in the response — a cursor-driven client has no way to distinguish a legitimately-requested offset of 100,000 from one that was forced there by the cap. The duplicated constant and utility functions across the two route files also introduce a maintenance risk if the values ever need to diverge.

Both route files share identical utility functions and the same constant — any future change to the cap or parsing logic must be applied in both places.

Important Files Changed

Filename Overview
src/app/api/users/[username]/followers/route.ts Adds MAX_FOLLOW_OFFSET cap (100_000) via updated parseNonNegativeInt signature; logic is correct but duplicates the constant and utility functions present in the following route
src/app/api/users/[username]/following/route.ts Mirror of followers/route.ts change; identical constant and utility functions duplicated here
src/app/api/users/[username]/followers/route.test.ts Adds regression test for huge-offset capping; test assertions are correct (range(100000, 100009) and pagination.offset === 100000)
src/app/api/users/[username]/following/route.test.ts Identical regression test mirrored for the following endpoint; assertions are correct

Sequence Diagram

sequenceDiagram
    participant Client
    participant Route as followers/following route
    participant Parser as parseNonNegativeInt
    participant Supabase

    Client->>Route: "GET ?offset=999999999&limit=10"
    Route->>Parser: parseNonNegativeInt("999999999", 0, 100_000)
    Parser-->>Route: 100_000 (clamped)
    Route->>Supabase: .range(100000, 100009)
    Supabase-->>Route: "{ data: [], count: 0 }"
    Route-->>Client: "{ pagination: { offset: 100000, limit: 10, total: 0 } }"
Loading

Reviews (1): Last reviewed commit: "fix(follows): cap huge public offsets" | Re-trigger Greptile

Comment on lines 4 to 20
@@ -9,12 +11,12 @@ function parsePositiveInt(value: string | null, fallback: number, max: number):
return Math.min(parsed, max);
}

function parseNonNegativeInt(value: string | null, fallback: number): number {
function parseNonNegativeInt(value: string | null, fallback: number, max: number): number {
const parsed = Number.parseInt(value ?? "", 10);
if (!Number.isFinite(parsed) || parsed < 0) {
return fallback;
}
return parsed;
return Math.min(parsed, max);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Duplicated constant and utility functions across route files

MAX_FOLLOW_OFFSET, parsePositiveInt, and parseNonNegativeInt are defined identically in both followers/route.ts and following/route.ts. If the cap value or parsing logic needs to change (e.g., lowering the max, fixing an edge case), both files must be updated in sync — a future maintainer will likely only update one. Extracting these to a shared module (e.g., src/app/api/users/[username]/pagination.ts) would eliminate the drift risk.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

const searchParams = request.nextUrl.searchParams;
const limit = parsePositiveInt(searchParams.get("limit"), 20, 100);
const offset = parseNonNegativeInt(searchParams.get("offset"), 0);
const offset = parseNonNegativeInt(searchParams.get("offset"), 0, MAX_FOLLOW_OFFSET);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Silently capped offset reflected back in pagination metadata

When a client sends offset=999999999, the response returns pagination.offset: 100000 with no indication that the value was clamped. A cursor-driven client that uses the returned offset to drive its next page request (e.g., assumes returned_offset + limit is the next page start) will silently resume from 100000 rather than seeing an error. Consider returning a 400 for offsets above the cap, or at minimum including a field like pagination.offset_clamped: true so callers can detect the truncation.

@ralyodio ralyodio closed this Jun 4, 2026
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.

Cap huge public follow-list offsets before Supabase range queries

2 participants