Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317// Node test for the param-redirect security validator and the v1→v2// migration. Zero dependencies: run with `node --test tests/`."use strict";
const test = require("node:test");const assert = require("node:assert/strict");
const { DEFAULT_STATE, PRESET_TWITTER, PRESET_REDDIT_ACCEPT, PRESET_REDDIT_MEDIA, migrateState, isValidMatchPattern, matchPatternTest, allowedHostMatches, resolveParamDestination, simulateRewrite,} = require("../common.js");
// --------------------------------------------------------------------------// allowedHostMatches — the allowlist is a redirect primitive, must be airtight// --------------------------------------------------------------------------
test("allowedHostMatches: *.redd.it matches subdomains on label boundaries", () => { assert.ok(allowedHostMatches("i.redd.it", "*.redd.it")); assert.ok(allowedHostMatches("preview.redd.it", "*.redd.it")); assert.ok(!allowedHostMatches("evilredd.it", "*.redd.it")); assert.ok(!allowedHostMatches("redd.it.evil.com", "*.redd.it")); assert.ok(!allowedHostMatches("aredd.it", "*.redd.it"));});
test("allowedHostMatches: matches the bare apex too (matches the old reddit-media behavior)", () => { assert.ok(allowedHostMatches("redd.it", "*.redd.it")); assert.ok(allowedHostMatches("redditmedia.com", "*.redditmedia.com"));});
test("allowedHostMatches: plain host matches itself and subdomains", () => { assert.ok(allowedHostMatches("example.com", "example.com")); assert.ok(allowedHostMatches("img.example.com", "example.com")); assert.ok(!allowedHostMatches("example.com.evil.net", "example.com"));});
test("allowedHostMatches: empty/undefined allowed host never matches", () => { assert.ok(!allowedHostMatches("i.redd.it", "")); assert.ok(!allowedHostMatches("i.redd.it", undefined));});
// --------------------------------------------------------------------------// resolveParamDestination — scheme + allowlist + parse// --------------------------------------------------------------------------
test("resolveParamDestination: accepts valid https on an allowed host", () => { const r = resolveParamDestination("https://i.redd.it/abc.jpeg", ["*.redd.it"]); assert.ok(r.ok); assert.equal(r.href, "https://i.redd.it/abc.jpeg");});
test("resolveParamDestination: accepts http on an allowed host", () => { const r = resolveParamDestination("http://i.redd.it/abc.jpeg", ["*.redd.it"]); assert.ok(r.ok);});
test("resolveParamDestination: handles spaces in a decoded URL", () => { // The content script already decodes the query param, so the destination // arrives here decoded; spaces are re-encoded by `new URL`. const r = resolveParamDestination("https://i.redd.it/ab c.jpeg", ["*.redd.it"]); assert.ok(r.ok); assert.equal(r.href, "https://i.redd.it/ab%20c.jpeg");});
test("resolveParamDestination: rejects non-http(s) schemes", () => { assert.equal(resolveParamDestination("javascript:alert(1)", ["*"]).reason, "scheme"); assert.equal(resolveParamDestination("file:///etc/passwd", ["*"]).reason, "scheme"); assert.equal(resolveParamDestination("data:text/html,x", ["*"]).reason, "scheme");});
test("resolveParamDestination: rejects hosts not on the allowlist", () => { const r = resolveParamDestination("https://evil.example/x", ["*.redd.it"]); assert.equal(r.ok, false); assert.equal(r.reason, "host-not-allowed"); assert.equal(r.host, "evil.example");});
test("resolveParamDestination: rejects unparseable destinations", () => { assert.equal(resolveParamDestination("not a url", ["*"]).reason, "invalid-destination"); assert.equal(resolveParamDestination("", ["*"]).reason, "no-url-param"); assert.equal(resolveParamDestination(undefined, ["*"]).reason, "no-url-param");});
test("resolveParamDestination: dotted-suffix hosts fail the allowlist", () => { // *.redd.it must not match redd.it.evil.com assert.equal( resolveParamDestination("https://i.redd.it.evil.com/x", ["*.redd.it"]).reason, "host-not-allowed" );});
// --------------------------------------------------------------------------// match pattern validation + matching// --------------------------------------------------------------------------
test("isValidMatchPattern: accepts well-formed patterns", () => { assert.ok(isValidMatchPattern("*://*.reddit.com/media*")); assert.ok(isValidMatchPattern("*://*.reddit.com/*")); assert.ok(isValidMatchPattern("https://example.com/*")); assert.ok(isValidMatchPattern("http://example.net/")); assert.ok(isValidMatchPattern("*://example.org/watch"));});
test("isValidMatchPattern: rejects malformed patterns", () => { assert.ok(!isValidMatchPattern("")); assert.ok(!isValidMatchPattern("example.com")); assert.ok(!isValidMatchPattern("https://")); assert.ok(!isValidMatchPattern("javascript://x")); assert.ok(!isValidMatchPattern("https://x y/"));});
test("isValidMatchPattern: a missing path defaults to /", () => { assert.ok(isValidMatchPattern("https://*.reddit.com")); assert.ok(isValidMatchPattern("https://reddit.com"));});
test("matchPatternTest: broad star host matches any host", () => { assert.ok(matchPatternTest("*://*/*", "https://example.com/a")); assert.ok(matchPatternTest("*://*/*", "http://whatever.org/x"));});
test("matchPatternTest: *.reddit.com matches subdomains and the apex", () => { assert.ok(matchPatternTest("*://*.reddit.com/media*", "https://www.reddit.com/media?url=x")); assert.ok(matchPatternTest("*://*.reddit.com/media*", "https://old.reddit.com/media?url=x")); assert.ok(matchPatternTest("*://*.reddit.com/media*", "https://reddit.com/media?url=x"));});
test("matchPatternTest: does not match other hosts or non-matching paths", () => { assert.ok(!matchPatternTest("*://*.reddit.com/media*", "https://evilreddit.com/media")); assert.ok(!matchPatternTest("*://*.reddit.com/media*", "https://www.reddit.com/other")); assert.ok(!matchPatternTest("https://example.com/a", "http://example.com/a"));});
test("matchPatternTest: path wildcard", () => { assert.ok(matchPatternTest("*://*.example.com/foo*", "https://a.example.com/foobar")); assert.ok(!matchPatternTest("*://*.example.com/foo*", "https://a.example.com/bar"));});
// --------------------------------------------------------------------------// migration v1 → v2// --------------------------------------------------------------------------
test("migrateState: no stored state seeds fresh v2 defaults", () => { const { state, changed } = migrateState(null); assert.equal(state.version, 2); assert.equal(changed, true); assert.ok(!("builtins" in state)); assert.ok(state.rules.some((r) => r.id === "preset-reddit-accept")); assert.ok(state.rules.some((r) => r.id === "preset-reddit-media")); assert.equal(state.rules.length, 5);});
test("migrateState: v2 state is returned unchanged", () => { const v2 = { version: 2, enabled: true, rules: [] }; const { state, changed } = migrateState(v2); assert.equal(changed, false); assert.equal(state, v2);});
test("migrateState: v1 keeps user rules and appends the two presets", () => { const v1 = { version: 1, enabled: true, builtins: { redditAcceptHeader: true, redditMediaRedirect: true }, rules: [{ id: "user-1", type: "host", enabled: true }], }; const { state, changed } = migrateState(v1); assert.equal(changed, true); assert.equal(state.version, 2); assert.ok(!("builtins" in state)); assert.equal(state.rules.length, 3); assert.equal(state.rules[0].id, "user-1"); const accept = state.rules.find((r) => r.id === "preset-reddit-accept"); const media = state.rules.find((r) => r.id === "preset-reddit-media"); assert.equal(accept.enabled, true); assert.equal(media.enabled, true);});
test("migrateState: v1 disabled builtins become disabled preset rules", () => { const v1 = { version: 1, enabled: true, builtins: { redditAcceptHeader: false, redditMediaRedirect: false }, rules: [], }; const { state } = migrateState(v1); assert.equal(state.rules.find((r) => r.id === "preset-reddit-accept").enabled, false); assert.equal(state.rules.find((r) => r.id === "preset-reddit-media").enabled, false);});
test("migrateState: preserves the global kill switch", () => { const v1 = { version: 1, enabled: false, builtins: {}, rules: [] }; const { state } = migrateState(v1); assert.equal(state.enabled, false);});
// --------------------------------------------------------------------------// legacy preset upgrade (stock flat preset-twitter → health-checked pool)// --------------------------------------------------------------------------
test("migrateState: stock old preset-twitter upgrades to the pool", () => { const old = { version: 2, enabled: true, rules: [ { id: "preset-twitter", name: "Twitter / X → xcancel", type: "host", enabled: true, allRequests: false, fromDomains: ["twitter.com", "x.com"], toHosts: ["xcancel.com"], }, ], }; const { state, changed } = migrateState(old); assert.equal(changed, true); const tw = state.rules.find((r) => r.id === "preset-twitter"); assert.equal(tw.name, "Twitter / X → nitter"); assert.equal(tw.toHosts.length, 8); assert.equal(tw.healthCheck, true); assert.ok(tw.toHosts.includes("xcancel.com")); assert.ok(tw.toHosts.includes("lightbrd.com"));});
test("migrateState: customized preset-twitter is left alone", () => { const custom = { version: 2, enabled: true, rules: [ { id: "preset-twitter", name: "My twitter rule", type: "host", enabled: true, allRequests: false, fromDomains: ["twitter.com"], toHosts: ["my-nitter.example.com"], }, ], }; const { state, changed } = migrateState(custom); assert.equal(changed, false); assert.equal(state, custom); assert.equal(state.rules[0].toHosts[0], "my-nitter.example.com");});
test("migrateState: modern pool preset-twitter is left alone", () => { const modern = { version: 2, enabled: true, rules: [Object.assign({}, PRESET_TWITTER)], }; const { state, changed } = migrateState(modern); assert.equal(changed, false); assert.equal(state, modern);});
// --------------------------------------------------------------------------// simulateRewrite — the two new rule types show up in the tester// --------------------------------------------------------------------------
test("simulateRewrite: accept rule reports a header match", () => { const res = simulateRewrite("https://i.redd.it/abc.jpeg", { enabled: true, rules: [Object.assign({}, PRESET_REDDIT_ACCEPT)], }); assert.equal(res.status, "header"); assert.equal(res.header, "Accept"); assert.ok(res.hosts.includes("i.redd.it"));});
test("simulateRewrite: param-redirect decodes and validates the destination", () => { const res = simulateRewrite( "https://www.reddit.com/media?url=https%3A%2F%2Fi.redd.it%2Fabc.jpeg", { enabled: true, rules: [Object.assign({}, PRESET_REDDIT_MEDIA)] } ); assert.equal(res.status, "param"); assert.equal(res.decoded, "https://i.redd.it/abc.jpeg"); assert.equal(res.dest.ok, true);});
test("simulateRewrite: param-redirect flags a disallowed destination", () => { const res = simulateRewrite( "https://www.reddit.com/media?url=https%3A%2F%2Fevil.example%2Fx", { enabled: true, rules: [Object.assign({}, PRESET_REDDIT_MEDIA)] } ); assert.equal(res.status, "param"); assert.equal(res.dest.ok, false); assert.equal(res.dest.reason, "host-not-allowed");});
test("simulateRewrite: global kill switch disables everything", () => { const res = simulateRewrite("https://i.redd.it/abc.jpeg", { enabled: false, rules: [Object.assign({}, PRESET_REDDIT_ACCEPT)], }); assert.equal(res.status, "disabled");});
test("defaults: the seeded presets are valid and self-consistent", () => { assert.equal(DEFAULT_STATE.version, 2); const accept = DEFAULT_STATE.rules.find((r) => r.id === "preset-reddit-accept"); const media = DEFAULT_STATE.rules.find((r) => r.id === "preset-reddit-media"); assert.equal(accept.type, "accept"); assert.equal(media.type, "param-redirect"); assert.ok(isValidMatchPattern(media.pattern)); assert.deepEqual(media.allowedHosts, ["*.redd.it", "*.redditmedia.com"]);});