Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/openstatusHQ/openstatus. ๐ซ Status page with uptime monitoring & API monitoring as code ๐ซ openstatus.dev
bun drizzle-orm monitoring monitoring-as-code nextjs observability on-call open-source shadcn-ui status-page statuspage synthetic-monitoring tinybird turso uptime uptime-checker uptime-monitor
Something went wrong. Try again.
9.4 kB ยท 338 lines
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339import { expect } from "@std/expect";import { describe, it } from "@std/testing/bdd";
import { assertSafeUrl, assertSafeUrlSync, safeFetch, safeUrlSchema,} from "./ssrf";
// --- assertSafeUrlSync (no DNS, used in Zod schemas) ---
describe("assertSafeUrlSync", () => { describe("allows valid public URLs", () => { const allowed = [ "https://hooks.slack.com/services/T00/B00/xxx", "https://discord.com/api/webhooks/123/abc", "https://chat.googleapis.com/v1/spaces/xxx", "https://ntfy.sh/my-topic", "https://example.com/webhook", "http://example.com/webhook", "https://172.32.0.1/webhook", // 172.32 is NOT private (only 172.16-31) "https://172.15.255.255/webhook", // 172.15 is NOT private ];
for (const url of allowed) { it(`allows ${url}`, () => { expect(() => assertSafeUrlSync(url)).not.toThrow(); }); } });
describe("blocks non-HTTP protocols", () => { const blocked = [ "ftp://example.com/file", "file:///etc/passwd", "gopher://evil.com/", "javascript:alert(1)", "data:text/html,<h1>hi</h1>", ];
for (const url of blocked) { it(`blocks ${url}`, () => { expect(() => assertSafeUrlSync(url)).toThrow("not allowed"); }); } });
describe("blocks private IPv4 addresses", () => { const blocked = [ "http://127.0.0.1/", "http://127.0.0.1:8080/test", "http://10.0.0.1/", "http://10.255.255.255/", "http://172.16.0.1/", "http://172.31.255.255/", "http://192.168.0.1/", "http://192.168.1.100:3000/", "http://169.254.169.254/latest/meta-data/", // AWS metadata "http://0.0.0.0/", ];
for (const url of blocked) { it(`blocks ${url}`, () => { expect(() => assertSafeUrlSync(url)).toThrow("not allowed"); }); } });
describe("blocks private hostnames", () => { const blocked = [ "http://localhost/", "http://localhost:8080/test", "http://LOCALHOST/", // case insensitive "http://metadata.google.internal/computeMetadata/v1/", "http://metadata.internal/", ];
for (const url of blocked) { it(`blocks ${url}`, () => { expect(() => assertSafeUrlSync(url)).toThrow("not allowed"); }); } });
describe("blocks IPv6 private addresses", () => { const blocked = [ "http://[::1]/", // loopback "http://[::1]:8080/", "http://[fc00::1]/", // unique local "http://[fd12:3456::1]/", // unique local "http://[fe80::1]/", // link-local ];
for (const url of blocked) { it(`blocks ${url}`, () => { expect(() => assertSafeUrlSync(url)).toThrow("not allowed"); }); } });
describe("blocks IPv4-mapped IPv6 addresses", () => { it("blocks ::ffff:127.0.0.1 (dotted-quad form)", () => { expect(() => assertSafeUrlSync("http://[::ffff:127.0.0.1]/")).toThrow( "not allowed", ); });
it("blocks ::ffff:7f00:1 (hex form, Node URL parser output)", () => { // Node converts ::ffff:127.0.0.1 to ::ffff:7f00:1 expect(() => assertSafeUrlSync("http://[::ffff:7f00:1]/")).toThrow( "not allowed", ); });
it("blocks ::ffff:a9fe:a9fe (169.254.169.254 AWS metadata in hex)", () => { expect(() => assertSafeUrlSync("http://[::ffff:a9fe:a9fe]/")).toThrow( "not allowed", ); });
it("blocks ::ffff:a00:1 (10.0.0.1 in hex)", () => { expect(() => assertSafeUrlSync("http://[::ffff:a00:1]/")).toThrow( "not allowed", ); });
it("blocks ::ffff:c0a8:1 (192.168.0.1 in hex)", () => { expect(() => assertSafeUrlSync("http://[::ffff:c0a8:1]/")).toThrow( "not allowed", ); }); });
describe("rejects invalid URLs", () => { it("throws on empty string", () => { expect(() => assertSafeUrlSync("")).toThrow(); });
it("throws on garbage", () => { expect(() => assertSafeUrlSync("not-a-url")).toThrow(); }); });});
// --- assertSafeUrl (async, same string-only checks) ---
describe("assertSafeUrl", () => { it("allows a valid public URL", async () => { await expect( assertSafeUrl("https://example.com/webhook"), ).resolves.toBeUndefined(); });
it("blocks localhost", async () => { await expect(assertSafeUrl("http://localhost/")).rejects.toThrow( "not allowed", ); });
it("blocks private IPs", async () => { await expect(assertSafeUrl("http://192.168.1.1/")).rejects.toThrow( "not allowed", ); });
it("blocks non-HTTP protocols", async () => { await expect(assertSafeUrl("ftp://example.com/")).rejects.toThrow( "not allowed", ); });
it("blocks AWS metadata endpoint", async () => { await expect( assertSafeUrl("http://169.254.169.254/latest/meta-data/"), ).rejects.toThrow("not allowed"); });});
// --- safeUrlSchema (Zod schema) ---
describe("safeUrlSchema", () => { it("accepts valid public URLs", () => { const result = safeUrlSchema.safeParse( "https://hooks.slack.com/services/T00/B00/xxx", ); expect(result.success).toBe(true); });
it("rejects non-URL strings", () => { const result = safeUrlSchema.safeParse("not-a-url"); expect(result.success).toBe(false); });
it("rejects private IPs", () => { const result = safeUrlSchema.safeParse("http://127.0.0.1/"); expect(result.success).toBe(false); });
it("rejects localhost", () => { const result = safeUrlSchema.safeParse("http://localhost:8080/"); expect(result.success).toBe(false); });
it("rejects metadata endpoint", () => { const result = safeUrlSchema.safeParse( "http://169.254.169.254/latest/meta-data/", ); expect(result.success).toBe(false); });
it("rejects ftp protocol", () => { const result = safeUrlSchema.safeParse("ftp://example.com/file"); expect(result.success).toBe(false); });
it("rejects IPv4-mapped IPv6 in hex form", () => { const result = safeUrlSchema.safeParse("http://[::ffff:7f00:1]/"); expect(result.success).toBe(false); });});
describe("safeFetch", () => { it("rejects a private target without fetching", async () => { const original = globalThis.fetch; let called = false; globalThis.fetch = () => { called = true; return Promise.resolve(new Response()); }; try { await expect( safeFetch("http://169.254.169.254/latest/meta-data"), ).rejects.toThrow(); expect(called).toBe(false); } finally { globalThis.fetch = original; } });
it("does not follow a 302", async () => { const original = globalThis.fetch; let seen: RequestInit | undefined; globalThis.fetch = (_input, init) => { seen = init; return Promise.resolve( new Response(null, { status: 302, headers: { location: "http://169.254.169.254/" }, }), ); }; try { const res = await safeFetch("https://example.com/hook", { method: "POST", }); expect(seen?.redirect).toBe("manual"); expect(res.ok).toBe(false); } finally { globalThis.fetch = original; } });
function redirecting(hops: Record<string, [number, string]>) { const seen: string[] = []; const original = globalThis.fetch; globalThis.fetch = (input) => { const url = String(input); seen.push(url); const hop = hops[url]; return Promise.resolve( hop ? new Response(null, { status: hop[0], headers: { location: hop[1] }, }) : new Response("ok"), ); }; return { seen, restore: () => (globalThis.fetch = original) }; }
it("follows a same-host 307/308, e.g. an http to https upgrade", async () => { const { seen, restore } = redirecting({ "http://example.com/hook": [308, "https://example.com/hook"], "https://example.com/hook": [307, "/v2/hook"], }); try { const res = await safeFetch("http://example.com/hook", { method: "POST", }); expect(res.ok).toBe(true); expect(seen).toEqual([ "http://example.com/hook", "https://example.com/hook", "https://example.com/v2/hook", ]); } finally { restore(); } });
it("does not follow a 307 that would replay headers elsewhere", async () => { for (const location of [ "https://evil.example/hook", "http://169.254.169.254/latest/meta-data", // downgrade to plaintext "http://example.com/hook", // same host, different service "https://example.com:8443/hook", ]) { const { seen, restore } = redirecting({ "https://example.com/hook": [307, location], }); try { const res = await safeFetch("https://example.com/hook"); expect(res.ok).toBe(false); expect(seen).toEqual(["https://example.com/hook"]); } finally { restore(); } } });
it("stops after a bounded number of hops", async () => { const { seen, restore } = redirecting({ "https://example.com/a": [307, "/b"], "https://example.com/b": [307, "/a"], }); try { const res = await safeFetch("https://example.com/a"); expect(res.ok).toBe(false); expect(seen.length).toBe(4); } finally { restore(); } });});