Problem
apps/api/src/lib/pagination.ts lines 14–18:
export const cursorSchema = z.object({
ts: z.string(), // only validated as string, not ISO date
id: z.string(), // only validated as string, not UUID
});
In keysetCondition (list.ts line 53): const ts = new Date(cursor.ts) — a crafted cursor with ts: "not-a-date" produces Invalid Date → NaN in the SQL expression (unhandled 500). A crafted id: "' OR '1'='1" is correctly parameterized by Drizzle but causes a DB UUID cast error (also 500).
Fix
export const cursorSchema = z.object({
ts: z.string().datetime(),
id: z.string().uuid(),
});
Then decodeCursor returns null for any malformed cursor and list routes safely start from page 1.
Problem
apps/api/src/lib/pagination.tslines 14–18:In
keysetCondition(list.tsline 53):const ts = new Date(cursor.ts)— a crafted cursor withts: "not-a-date"producesInvalid Date→NaNin the SQL expression (unhandled 500). A craftedid: "' OR '1'='1"is correctly parameterized by Drizzle but causes a DB UUID cast error (also 500).Fix
Then
decodeCursorreturnsnullfor any malformed cursor and list routes safely start from page 1.