import test from "node:test"; import assert from "node:assert/strict"; import { BrowserAccessPolicy } from "../src/access-policy.js"; test("browser policy requires at least one exact HTTP origin", () => { assert.throws(() => new BrowserAccessPolicy([]), /must not be empty/); assert.throws(() => new BrowserAccessPolicy(["file:///tmp"]), /HTTP/); assert.throws(() => new BrowserAccessPolicy(["http://app:5173/path"]), /origin/); }); test("browser policy permits exact allowed origins and rejects SSRF destinations", () => { const policy = new BrowserAccessPolicy(["http://dev-server-worker:5173"]); assert.equal(policy.assertUrl("http://dev-server-worker:5173/dashboard", { navigation: true }), "http://dev-server-worker:5173/dashboard"); assert.throws(() => policy.assertUrl("http://dev-server-worker:3000/mcp", { navigation: true }), /not allowed/); assert.throws(() => policy.assertUrl("http://169.254.169.254/latest/meta-data", { navigation: true }), /not allowed/); assert.throws(() => policy.assertUrl("file:///etc/passwd", { navigation: true }), /not allowed/); assert.throws(() => policy.assertUrl("http://user:pass@dev-server-worker:5173/", { navigation: true }), /not allowed/); assert.equal(policy.assertWebSocketUrl("ws://dev-server-worker:5173/socket"), "ws://dev-server-worker:5173/socket"); assert.throws(() => policy.assertWebSocketUrl("ws://dev-server-worker:3000/mcp"), /not allowed/); }); test("auxiliary data URLs are allowed only for subresources", () => { const policy = new BrowserAccessPolicy(["https://app.example"]); assert.match(policy.assertUrl("data:text/plain,ok"), /^data:/); assert.throws(() => policy.assertUrl("data:text/html,hello", { navigation: true }), /not allowed/); }); test("a port range covers every port an execution may be given", () => { // One entry instead of one per concurrent execution: an allowlist that has to be rewritten as // executions come and go is an allowlist somebody will eventually replace with a wildcard. const policy = new BrowserAccessPolicy(["http://dev-server-worker:5200-5219"]); assert.equal(policy.assertUrl("http://dev-server-worker:5200/", { navigation: true }), "http://dev-server-worker:5200/"); assert.equal(policy.assertUrl("http://dev-server-worker:5219/app", { navigation: true }), "http://dev-server-worker:5219/app"); assert.throws(() => policy.assertUrl("http://dev-server-worker:5220/", { navigation: true }), /not allowed/); assert.throws(() => policy.assertUrl("http://dev-server-worker:5199/", { navigation: true }), /not allowed/); }); test("a range widens the port and nothing else", () => { const policy = new BrowserAccessPolicy(["http://dev-server-worker:5200-5219"]); assert.throws(() => policy.assertUrl("http://elsewhere:5200/", { navigation: true }), /not allowed/); assert.throws(() => policy.assertUrl("https://dev-server-worker:5200/", { navigation: true }), /not allowed/); }); test("hot reload is allowed as far as the page it belongs to", () => { const policy = new BrowserAccessPolicy(["http://dev-server-worker:5200-5219"]); assert.equal(policy.assertWebSocketUrl("ws://dev-server-worker:5207/?token=x"), "ws://dev-server-worker:5207/?token=x"); assert.throws(() => policy.assertWebSocketUrl("ws://dev-server-worker:5300/"), /not allowed/); }); test("exact origins and ranges live together", () => { const policy = new BrowserAccessPolicy(["http://fixed-app:8080", "http://dev-server-worker:5200-5201"]); assert.equal(policy.assertUrl("http://fixed-app:8080/", { navigation: true }), "http://fixed-app:8080/"); assert.equal(policy.assertUrl("http://dev-server-worker:5201/", { navigation: true }), "http://dev-server-worker:5201/"); assert.throws(() => policy.assertUrl("http://fixed-app:8081/", { navigation: true }), /not allowed/); }); test("an impossible range is refused at startup, not at the first navigation", () => { assert.throws(() => new BrowserAccessPolicy(["http://app:5219-5200"]), /impossible port range/); assert.throws(() => new BrowserAccessPolicy(["http://app:5200-99999"]), /impossible port range/); });