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.
6.1 kB ยท 216 lines
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217import { expect } from "@std/expect";import { describe, it } from "@std/testing/bdd";
import { assertSafeUrl, assertSafeUrlSync, 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, with DNS resolution) ---
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); });});