import test from "node:test"; import assert from "node:assert/strict"; import { Policy } from "../src/Policy.js"; import { DESTINATIONS, REFUSALS } from "../src/spec.js"; const policy = new Policy(); test("allows https on the allowlisted hosts and subdomains", () => { for (const url of [ "https://reddit.com/r/python.json", "https://www.reddit.com/r/python.json", "https://old.reddit.com/", "https://redd.it/abc", "https://REDDIT.COM./x", ]) { assert.equal(policy.check("GET", url).ok, true, url); } }); test("suffix matching is dot safe", () => { for (const url of [ "https://evilreddit.com/", "https://reddit.com.evil.net/", "https://notredd.it/", "https://reddit.co/", ]) { const verdict = policy.check("GET", url); assert.equal(verdict.ok, false, url); assert.equal(verdict.code, REFUSALS.HOST_NOT_ALLOWED); } }); test("userinfo tricks are refused", () => { for (const url of [ "https://reddit.com@evil.net/", "https://user:pass@reddit.com/", "https://evil.net\\@reddit.com/", ]) { assert.equal(policy.check("GET", url).ok, false, url); } }); test("non https schemes are refused", () => { for (const url of ["http://reddit.com/", "ftp://reddit.com/", "ws://reddit.com/", "file:///etc/passwd"]) { const verdict = policy.check("GET", url); assert.equal(verdict.ok, false, url); } }); test("only port 443 passes over https", () => { assert.equal(policy.check("GET", "https://reddit.com:443/").ok, true); for (const url of ["https://reddit.com:80/", "https://reddit.com:8443/", "https://reddit.com:22/"]) { const verdict = policy.check("GET", url); assert.equal(verdict.ok, false, url); assert.equal(verdict.code, REFUSALS.PORT_NOT_ALLOWED); } }); test("method must be permitted", () => { const verdict = policy.check("POST", "https://reddit.com/"); assert.equal(verdict.code, REFUSALS.METHOD_NOT_ALLOWED); assert.equal(policy.check("get", "https://reddit.com/").ok, true); }); test("ip literals, junk and oversize urls are refused", () => { for (const url of ["https://127.0.0.1/", "https://[::1]/", "https://10.0.0.1/", "nonsense", "", null, 5]) { assert.equal(policy.check("GET", url).ok, false, String(url)); } assert.equal(policy.check("GET", "https://reddit.com/" + "a".repeat(3000)).ok, false); }); test("a redirect leaving the allowlist is refused with redirect_not_allowed", () => { const base = new URL("https://www.reddit.com/r/x"); const bad = policy.checkRedirect("https://evil.net/landing", base, "GET"); assert.equal(bad.ok, false); assert.equal(bad.code, REFUSALS.REDIRECT_NOT_ALLOWED); assert.equal(policy.checkRedirect("http://reddit.com/x", base, "GET").code, REFUSALS.REDIRECT_NOT_ALLOWED); assert.equal(policy.checkRedirect("//evilreddit.com/x", base, "GET").code, REFUSALS.REDIRECT_NOT_ALLOWED); assert.equal(policy.checkRedirect("/r/y", base, "GET").ok, true); assert.equal(policy.checkRedirect("https://old.reddit.com/r/y", base, "GET").ok, true); }); test("a refreshed allowlist may only be equal or narrower", () => { const narrower = [{ ...DESTINATIONS[0], suffix: "old.reddit.com" }]; const kept = Policy.narrow(DESTINATIONS, narrower); assert.equal(kept.destinations.length, 1); assert.equal(kept.rejected.length, 0); const equal = Policy.narrow(DESTINATIONS, DESTINATIONS.map((item) => ({ ...item }))); assert.equal(equal.destinations.length, DESTINATIONS.length); const widened = Policy.narrow(DESTINATIONS, [ { suffix: "evil.net", adapter: "x", methods: ["GET"], ports: [443], modes: ["fetch"] }, { suffix: "reddit.com", adapter: "reddit", methods: ["GET", "POST"], ports: [443], modes: ["fetch"] }, { suffix: "reddit.com", adapter: "reddit", methods: ["GET"], ports: [443, 8080], modes: ["fetch"] }, { suffix: "reddit.com", adapter: "reddit", methods: ["GET"], ports: [443], modes: ["tunnel"] }, { suffix: "evilreddit.com", adapter: "reddit", methods: ["GET"], ports: [443], modes: ["fetch"] }, ]); assert.equal(widened.destinations.length, 0); assert.equal(widened.rejected.length, 5); const empty = Policy.narrow(DESTINATIONS, []); assert.equal(empty.destinations.length, 0); assert.equal(Policy.narrow(DESTINATIONS, "x").destinations.length, 0); }); test("a narrowed policy refuses what the baked policy would allow", () => { const { policy: narrow } = Policy.fromRefreshed(DESTINATIONS, [ { ...DESTINATIONS[0], suffix: "old.reddit.com" }, ]); assert.equal(narrow.check("GET", "https://old.reddit.com/").ok, true); assert.equal(narrow.check("GET", "https://www.reddit.com/").ok, false); assert.equal(narrow.check("GET", "https://redd.it/x").ok, false); });