115 lines
4.6 KiB
JavaScript
115 lines
4.6 KiB
JavaScript
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);
|
|
});
|