From 605095b1c21b470b5b89068f7d09c0cc26890c06 Mon Sep 17 00:00:00 2001 From: bigben-7 Date: Sat, 17 Oct 2026 10:59:18 +0100 Subject: [PATCH] demilade18-git: bounded pagination + trusted client IP (#1488-#1491) --- backend/src/common/client-ip.ts | 29 ++++++++++++++++++++++++ backend/src/common/pagination.ts | 38 ++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 backend/src/common/client-ip.ts create mode 100644 backend/src/common/pagination.ts diff --git a/backend/src/common/client-ip.ts b/backend/src/common/client-ip.ts new file mode 100644 index 0000000..9b0ac3c --- /dev/null +++ b/backend/src/common/client-ip.ts @@ -0,0 +1,29 @@ +/** + * Trusted-proxy-aware client IP resolution (#1489). + * + * The client IP was read straight from `X-Forwarded-For`, which is spoofable + * unless the app is explicitly configured behind a trusted proxy. This helper + * only honours the header when the caller opts in, and otherwise falls back to + * the socket address. + */ +export interface ClientIpOptions { + trustProxy: boolean; +} + +export function getClientIp( + headers: Record, + socketAddress: string | undefined, + options: ClientIpOptions = { trustProxy: false }, +): string | null { + if (options.trustProxy) { + const forwarded = headers["x-forwarded-for"]; + const value = Array.isArray(forwarded) ? forwarded[0] : forwarded; + if (value) { + const first = value.split(",")[0]?.trim(); + if (first) { + return first; + } + } + } + return socketAddress ?? null; +} diff --git a/backend/src/common/pagination.ts b/backend/src/common/pagination.ts new file mode 100644 index 0000000..58ac3bf --- /dev/null +++ b/backend/src/common/pagination.ts @@ -0,0 +1,38 @@ +/** + * Bounded pagination parsing (#1491). + * + * `GET /disputes` parsed `limit`/`offset` with `parseInt` and no bounds, so + * `NaN`, negative and huge values reached the database. This helper clamps both + * values to a safe range. + */ +export interface Pagination { + limit: number; + offset: number; +} + +export const DEFAULT_LIMIT = 20; +export const MAX_LIMIT = 100; + +export function parsePagination( + rawLimit: unknown, + rawOffset: unknown, +): Pagination { + return { + limit: toBoundedInt(rawLimit, DEFAULT_LIMIT, 1, MAX_LIMIT), + offset: toBoundedInt(rawOffset, 0, 0, Number.MAX_SAFE_INTEGER), + }; +} + +function toBoundedInt( + value: unknown, + fallback: number, + min: number, + max: number, +): number { + const parsed = + typeof value === "number" ? value : parseInt(String(value ?? ""), 10); + if (!Number.isFinite(parsed)) { + return fallback; + } + return Math.min(Math.max(Math.trunc(parsed), min), max); +}