// SSRF guard for user-supplied media URLs (safe-url.js). Uses literal // IPs so the address checks need no DNS / network. import { test, describe } from "node:test"; import assert from "node:assert/strict"; import { isBlockedAddress, assertPublicHttpUrl, BlockedUrlError, } from "../safe-url.js"; describe("isBlockedAddress", () => { test("blocks private / loopback / link-local / reserved IPv4", () => { for (const ip of [ "127.0.0.1", "10.0.0.5", "172.16.0.1", "172.31.255.255", "192.168.1.1", "169.254.169.254", // cloud metadata "100.64.0.1", "0.0.0.0", "198.18.0.1", "224.0.0.1", "255.255.255.255", ]) { assert.equal(isBlockedAddress(ip), true, `${ip} should be blocked`); } }); test("allows public IPv4 (incl. the /12 boundaries around 172.16/12)", () => { for (const ip of ["8.8.8.8", "1.1.1.1", "172.15.0.1", "172.32.0.1", "93.184.216.34"]) { assert.equal(isBlockedAddress(ip), false, `${ip} should be allowed`); } }); test("blocks loopback / ULA / link-local / IPv4-mapped IPv6", () => { for (const ip of [ "::1", "::", "fe80::1", "febf::1", "fc00::1", "fd12:3456::1", "ff02::1", "::ffff:127.0.0.1", "::ffff:192.168.0.1", ]) { assert.equal(isBlockedAddress(ip), true, `${ip} should be blocked`); } }); test("allows public IPv6", () => { assert.equal(isBlockedAddress("2606:4700:4700::1111"), false); }); }); describe("assertPublicHttpUrl", () => { test("rejects non-http(s) schemes", async () => { for (const u of [ "file:///etc/passwd", "gopher://x/_", "ftp://h/f", "data:text/plain,hi", ]) { await assert.rejects(() => assertPublicHttpUrl(u), BlockedUrlError); } }); test("rejects literal private / metadata IP hosts (no DNS needed)", async () => { for (const u of [ "http://127.0.0.1/x", "http://169.254.169.254/latest/meta-data/", "http://[::1]/x", "http://192.168.0.10:9000/a", "https://10.1.2.3/audio.mp3", ]) { await assert.rejects(() => assertPublicHttpUrl(u), BlockedUrlError); } }); test("rejects malformed URLs", async () => { await assert.rejects(() => assertPublicHttpUrl("not a url"), BlockedUrlError); }); test("allows a public literal IP host", async () => { const u = await assertPublicHttpUrl("https://8.8.8.8/audio.mp3"); assert.equal(u.hostname, "8.8.8.8"); }); });