1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | /**
* URL parameter validation for route handlers.
*
* Handlers were passing raw path segments straight into database predicates.
* `parseInt("abc", 10)` yields NaN, and a NaN or malformed-UUID comparison
* makes Postgres raise, which surfaces as a 500. So /:owner/:repo/issues/abc
* crashed while /:owner/:repo/issues/99999999 correctly 404'd — a hand-typed
* or stale URL returned a server error instead of "not found".
*
* Found by the authz-matrix gate, which probes every repo-scoped route with a
* placeholder id: eight routes answered 5xx (issues, pulls, milestones,
* agents and claude sessions).
*/
/**
* A positive integer path segment (issue/PR numbers), or null when the
* segment is not one. Rejects NaN, zero, negatives, floats, and numeric
* strings with trailing junk ("12abc") that parseInt would happily truncate.
*/
export function parseIdNumber(raw: string | undefined): number | null {
if (!raw || !/^\d+$/.test(raw)) return null;
const n = Number(raw);
if (!Number.isSafeInteger(n) || n <= 0) return null;
return n;
}
/**
* A UUID path segment, or null. Postgres raises `invalid input syntax for
* type uuid` on anything else, which the global error handler turns into a
* 500 — so this must be checked before the value reaches a query.
*/
export function parseIdUuid(raw: string | undefined): string | null {
if (!raw) return null;
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(raw)
? raw
: null;
}
|