diff --git a/src/utils/sanitize.ts b/src/utils/sanitize.ts index 219865198..4d1e91d3b 100644 --- a/src/utils/sanitize.ts +++ b/src/utils/sanitize.ts @@ -24,7 +24,10 @@ export function sanitizeStatusCode( if (typeof statusCode === "string") { statusCode = Number.parseInt(statusCode, 10); } - if (statusCode < 100 || statusCode > 999) { + // `Number.parseInt` returns `NaN` for non-numeric strings, and `NaN` passes + // the range check below (every comparison with `NaN` is `false`), so an + // invalid status code would leak through unchanged. Guard against it. + if (Number.isNaN(statusCode) || statusCode < 100 || statusCode > 999) { return defaultStatusCode; } return statusCode; diff --git a/test/sanitize.test.ts b/test/sanitize.test.ts new file mode 100644 index 000000000..c795d036e --- /dev/null +++ b/test/sanitize.test.ts @@ -0,0 +1,26 @@ +import { describe, it, expect } from "vitest"; +import { sanitizeStatusCode } from "../src/utils/sanitize"; + +describe("sanitizeStatusCode", () => { + it("returns valid status codes unchanged", () => { + expect(sanitizeStatusCode(200)).toBe(200); + expect(sanitizeStatusCode(404)).toBe(404); + expect(sanitizeStatusCode("301")).toBe(301); + }); + + it("returns the default for missing or out-of-range input", () => { + expect(sanitizeStatusCode(undefined)).toBe(200); + expect(sanitizeStatusCode(0)).toBe(200); + expect(sanitizeStatusCode(99)).toBe(200); + expect(sanitizeStatusCode(1000)).toBe(200); + expect(sanitizeStatusCode(99, 500)).toBe(500); + }); + + it("returns the default for non-numeric strings instead of NaN", () => { + // `Number.parseInt("abc", 10)` is NaN and NaN passes the range check, so + // the function used to return NaN here, which breaks downstream consumers + // (e.g. setting `res.statusCode` or building a `Response`). + expect(sanitizeStatusCode("abc")).toBe(200); + expect(sanitizeStatusCode("abc", 500)).toBe(500); + }); +});