/** * CORS policy verification harness. * * Exercises the pure helpers in src/lib/http/cors.ts with simulated requests * (real NextRequest/NextResponse instances) and asserts the policy: * - preflight from an allowed origin -> 204 + correct headers * - preflight from a disallowed origin -> 403 * - normal request, disallowed origin -> 403 * - no Origin header / allowed origin -> undefined (continue) * - allowlist normalization (trailing slash, case, default port) * - never emits `Access-Control-Allow-Origin: *` * * Run: node scripts/verify_cors.mjs * (Node >= 23.6 / 24 runs the imported .ts directly via built-in type stripping; * no tsx required.) */ import assert from "node:assert/strict"; import { NextRequest } from "next/server"; import { getAllowedOrigins, isOriginAllowed, corsHeadersFor, handleCors, originComparisonKey, CORS_ALLOWED_METHODS, CORS_ERROR_CODE, } from "../src/lib/http/cors.ts"; let passed = 0; let failed = 0; function check(name, fn) { try { fn(); passed += 1; console.log(` ok ${name}`); } catch (err) { failed += 1; console.error(`FAIL ${name}`); console.error(String(err instanceof Error ? err.stack : err).replace(/^/gm, " ")); } } /** Env that looks like production: app origin + a few extra allowlisted origins. */ const PROD_ENV = { CORS_ORIGINS: "https://admin.example.com, http://localhost:8080/", NEXT_PUBLIC_APP_URL: "https://app.example.com", }; const APP_ORIGIN = "https://app.example.com"; function req(method, headers, url = "https://app.example.com/api/receipts") { return new NextRequest(url, { method, headers }); } // --------------------------------------------------------------------------- console.log("CORS policy verification\n"); console.log("1) Allowlist construction & normalization"); check("getAllowedOrigins returns full origins (echo form), scheme & case preserved", () => { const list = getAllowedOrigins(PROD_ENV); assert.ok(list.includes("https://app.example.com"), `NEXT_PUBLIC_APP_URL missing: ${list}`); assert.ok(list.includes("https://admin.example.com"), `missing admin origin: ${list}`); assert.ok(list.includes("http://localhost:8080"), `missing localhost origin: ${list}`); // Trailing slashes are stripped during normalization (spec). assert.ok(!list.some((o) => o.endsWith("/")), `trailing slash survived: ${list}`); }); check("allowlist is de-duplicated by comparison key; app origin wins over duplicates", () => { const dupEnv = { CORS_ORIGINS: "https://app.example.com, https://APP.EXAMPLE.COM:443", NEXT_PUBLIC_APP_URL: "https://app.example.com", }; // All three spellings share the key "app.example.com" -> exactly one entry, // and it is the canonical NEXT_PUBLIC_APP_URL spelling (added first). const list = getAllowedOrigins(dupEnv); assert.deepEqual(list, ["https://app.example.com"], `expected single canonical entry: ${list}`); const keys = getAllowedOrigins(PROD_ENV).map(originComparisonKey); assert.equal(new Set(keys).size, keys.length, `duplicate keys: ${keys}`); }); check("comparison key strips scheme, trailing slash, lowercases host, collapses default port", () => { assert.equal(originComparisonKey("https://App.Example.com/"), "app.example.com"); assert.equal(originComparisonKey("https://app.example.com:443"), "app.example.com"); assert.equal(originComparisonKey("http://app.example.com:80"), "app.example.com"); assert.equal(originComparisonKey("HTTPS://app.example.com:3000/"), "app.example.com:3000"); assert.equal(originComparisonKey("http://localhost:8080"), "localhost:8080"); }); check("comparison key drops path/query/fragment defensively", () => { assert.equal( originComparisonKey("https://app.example.com/some/path?x=1#frag"), "app.example.com" ); }); check("comparison key handles IPv6 literals", () => { assert.equal(originComparisonKey("http://[::1]:3000"), "[::1]:3000"); assert.equal(originComparisonKey("http://[::1]"), "[::1]"); }); // --------------------------------------------------------------------------- console.log("\n2) isOriginAllowed"); check("no Origin header (null/empty) is allowed (curl, same-origin, server-to-server)", () => { assert.equal(isOriginAllowed(null, PROD_ENV), true); assert.equal(isOriginAllowed("", PROD_ENV), true); }); check("exact allowed origin matches", () => { assert.equal(isOriginAllowed(APP_ORIGIN, PROD_ENV), true); assert.equal(isOriginAllowed("https://admin.example.com", PROD_ENV), true); assert.equal(isOriginAllowed("http://localhost:8080", PROD_ENV), true); }); check("normalization: trailing slash, mixed case, explicit default port all match", () => { assert.equal(isOriginAllowed("https://app.example.com/", PROD_ENV), true); assert.equal(isOriginAllowed("HTTPS://APP.EXAMPLE.COM", PROD_ENV), true); assert.equal(isOriginAllowed("https://app.example.com:443", PROD_ENV), true); }); check("disallowed origins are rejected (never *, exact match only)", () => { assert.equal(isOriginAllowed("https://evil.example.com", PROD_ENV), false); assert.equal(isOriginAllowed("https://example.com", PROD_ENV), false); assert.equal(isOriginAllowed("http://localhost:9999", PROD_ENV), false); assert.equal(isOriginAllowed("null", PROD_ENV), false); }); check("substring / suffix attacks do not match", () => { assert.equal(isOriginAllowed("https://app.example.com.evil.com", PROD_ENV), false); assert.equal(isOriginAllowed("https://app.example.com.evil.com:443", PROD_ENV), false); assert.equal(isOriginAllowed("https://evilexample.com", PROD_ENV), false); assert.equal(isOriginAllowed("https://admin.example.com.evil.com", PROD_ENV), false); }); check("host + port are exact; scheme is ignored for comparison (documented policy)", () => { // Port is part of the exact match. assert.equal(isOriginAllowed("https://app.example.com:444", PROD_ENV), false); assert.equal(isOriginAllowed("http://localhost:8081", PROD_ENV), false); // The http/https prefix is stripped for comparison (policy), so the same // host:port under the other scheme is treated as the same origin. assert.equal(isOriginAllowed("http://app.example.com", PROD_ENV), true); }); check("empty allowlist => same-origin only (everything with an Origin rejected)", () => { const empty = { CORS_ORIGINS: "", NEXT_PUBLIC_APP_URL: "" }; assert.equal(isOriginAllowed(null, empty), true); assert.equal(isOriginAllowed("https://app.example.com", empty), false); }); // --------------------------------------------------------------------------- console.log("\n3) corsHeadersFor"); check("allowed origin -> exact echo + Vary: Origin + credentials; never '*'", () => { const h = corsHeadersFor(APP_ORIGIN, PROD_ENV); assert.equal(h["Access-Control-Allow-Origin"], APP_ORIGIN); assert.equal(h["Vary"], "Origin"); assert.equal(h["Access-Control-Allow-Credentials"], "true"); assert.ok(!Object.values(h).includes("*"), "wildcard leaked into headers"); }); check("disallowed / missing origin -> no CORS headers", () => { assert.deepEqual(corsHeadersFor("https://evil.example.com", PROD_ENV), {}); assert.deepEqual(corsHeadersFor(null, PROD_ENV), {}); assert.deepEqual(corsHeadersFor("", PROD_ENV), {}); }); // --------------------------------------------------------------------------- console.log("\n4) handleCors — preflight"); check("preflight from allowed origin -> 204 with full header set", () => { const res = handleCors( req("OPTIONS", { origin: APP_ORIGIN, "access-control-request-method": "POST", "access-control-request-headers": "content-type, authorization", }), PROD_ENV ); assert.ok(res, "expected a response"); assert.equal(res.status, 204); assert.equal(res.headers.get("access-control-allow-origin"), APP_ORIGIN); assert.equal(res.headers.get("vary"), "Origin"); assert.equal(res.headers.get("access-control-allow-credentials"), "true"); assert.equal(res.headers.get("access-control-allow-methods"), CORS_ALLOWED_METHODS); assert.equal(res.headers.get("access-control-max-age"), "600"); assert.equal(res.headers.get("access-control-allow-headers"), "content-type, authorization"); }); check("preflight echo uses the exact request origin, never a wildcard", () => { const res = handleCors( req("OPTIONS", { origin: "https://admin.example.com", "access-control-request-method": "GET", }), PROD_ENV ); assert.ok(res); assert.equal(res.status, 204); const acao = res.headers.get("access-control-allow-origin"); assert.equal(acao, "https://admin.example.com"); assert.ok(acao && !acao.includes("*"), "Access-Control-Allow-Origin contains '*'"); }); check("preflight from disallowed origin -> 403, no ACAO header", () => { const res = handleCors( req("OPTIONS", { origin: "https://evil.example.com", "access-control-request-method": "POST", }), PROD_ENV ); assert.ok(res); assert.equal(res.status, 403); assert.equal(res.headers.get("access-control-allow-origin"), null, "no ACAO on refusal"); }); check("preflight 403 carries the machine-readable error code", async () => { const res = handleCors( req("OPTIONS", { origin: "https://evil.example.com", "access-control-request-method": "POST", }), PROD_ENV ); assert.ok(res); const body = await res.json(); assert.deepEqual(body, { error: CORS_ERROR_CODE }); }); check("Access-Control-Allow-Headers echo is validated (invalid tokens dropped)", () => { const res = handleCors( req("OPTIONS", { origin: APP_ORIGIN, "access-control-request-method": "PUT", "access-control-request-headers": "content-type, bad header!, x-foo", }), PROD_ENV ); assert.ok(res); assert.equal(res.headers.get("access-control-allow-headers"), "content-type, x-foo"); }); check("Access-Control-Allow-Headers echo is bounded (oversized input omitted)", () => { const res = handleCors( req("OPTIONS", { origin: APP_ORIGIN, "access-control-request-method": "PUT", "access-control-request-headers": "x-big-" + "a".repeat(5000), }), PROD_ENV ); assert.ok(res); assert.equal(res.headers.get("access-control-allow-headers"), null); }); check("Access-Control-Allow-Headers echo is de-duplicated", () => { const res = handleCors( req("OPTIONS", { origin: APP_ORIGIN, "access-control-request-method": "POST", "access-control-request-headers": "content-type, content-type, authorization", }), PROD_ENV ); assert.ok(res); assert.equal(res.headers.get("access-control-allow-headers"), "content-type, authorization"); }); // --------------------------------------------------------------------------- console.log("\n5) handleCors — non-preflight"); check("normal request from allowed origin -> undefined (continue)", () => { const res = handleCors(req("GET", { origin: APP_ORIGIN }), PROD_ENV); assert.equal(res, undefined); }); check("normal request with no Origin -> undefined (continue)", () => { const res = handleCors(req("GET", {}), PROD_ENV); assert.equal(res, undefined); }); check("normal request from disallowed origin -> 403", () => { const res = handleCors(req("GET", { origin: "https://evil.example.com" }), PROD_ENV); assert.ok(res); assert.equal(res.status, 403); }); check("bare OPTIONS without Access-Control-Request-Method is not a preflight", () => { // Disallowed origin on a bare OPTIONS is still refused (cross-origin). const disallowed = handleCors(req("OPTIONS", { origin: "https://evil.example.com" }), PROD_ENV); assert.ok(disallowed); assert.equal(disallowed.status, 403); // Allowed origin on a bare OPTIONS passes through. const allowed = handleCors(req("OPTIONS", { origin: APP_ORIGIN }), PROD_ENV); assert.equal(allowed, undefined); }); check("POST (credentials path) from allowed origin -> undefined", () => { const res = handleCors(req("POST", { origin: APP_ORIGIN }), PROD_ENV); assert.equal(res, undefined); }); // --------------------------------------------------------------------------- console.log("\n6) global: no '*' is ever emitted as a header value"); function collectHeaderValues() { const values = []; const origins = ["https://app.example.com", "https://admin.example.com", "http://localhost:8080"]; for (const origin of origins) { const preflight = handleCors( req("OPTIONS", { origin, "access-control-request-method": "GET", "access-control-request-headers": "content-type, authorization", }), PROD_ENV ); if (preflight) preflight.headers.forEach((v) => values.push(v)); values.push(...Object.values(corsHeadersFor(origin, PROD_ENV))); } return values; } check("no emitted header value equals or contains '*'", () => { const values = collectHeaderValues(); assert.ok(values.length > 0, "expected to collect some header values"); for (const v of values) { assert.ok(!v.includes("*"), `wildcard value emitted: ${JSON.stringify(v)}`); } }); check("allowlist itself contains no wildcard entries", () => { for (const origin of getAllowedOrigins(PROD_ENV)) { assert.ok(!origin.includes("*"), `wildcard in allowlist: ${origin}`); } }); // --------------------------------------------------------------------------- console.log(`\n${passed} passed, ${failed} failed`); if (failed > 0) { console.error("CORS verification FAILED"); process.exit(1); } console.log("CORS verification PASSED");