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.0 kB ยท 205 lines
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206import { z } from "zod";
// --- SSRF Protection ---
const BLOCKED_IPV4_RANGES = [ // Loopback { prefix: "127.", mask: null }, // Link-local { prefix: "169.254.", mask: null }, // Private 10.x.x.x { prefix: "10.", mask: null }, // Private 172.16.0.0 - 172.31.255.255 { prefix: "172.", mask: (ip: string) => { const second = Number.parseInt(ip.split(".")[1] ?? "", 10); return second >= 16 && second <= 31; }, }, // Private 192.168.x.x { prefix: "192.168.", mask: null }, // Current network { prefix: "0.", mask: null },];
const BLOCKED_HOSTNAMES = new Set([ "localhost", "metadata.google.internal", "metadata.internal",]);
function isBlockedIPv4(ip: string): boolean { for (const range of BLOCKED_IPV4_RANGES) { if (ip.startsWith(range.prefix)) { if (range.mask === null || range.mask(ip)) return true; } } return false;}
function isBlockedIPv6(ip: string): boolean { const normalized = ip.toLowerCase(); if (normalized === "::1" || normalized === "::") return true; // Unique local addresses (fc00::/7) if (normalized.startsWith("fc") || normalized.startsWith("fd")) return true; // Link-local (fe80::/10) if (normalized.startsWith("fe80")) return true; // IPv4-mapped IPv6 addresses // Node's URL parser converts ::ffff:127.0.0.1 to ::ffff:7f00:1 (hex form) // Handle both dotted-quad (::ffff:127.0.0.1) and hex (::ffff:7f00:1) forms if (normalized.startsWith("::ffff:")) { const mappedPart = normalized.slice(7); // Dotted-quad form if (isBlockedIPv4(mappedPart)) return true; // Hex form โ parse back to IPv4 dotted-quad const hexParts = mappedPart.split(":"); if (hexParts.length === 2) { const high = Number.parseInt(hexParts[0] ?? "0", 16); const low = Number.parseInt(hexParts[1] ?? "0", 16); if (!Number.isNaN(high) && !Number.isNaN(low)) { const ipv4 = `${(high >> 8) & 0xff}.${high & 0xff}.${(low >> 8) & 0xff}.${low & 0xff}`; if (isBlockedIPv4(ipv4)) return true; } } } return false;}
function isBlockedHost(hostname: string): boolean { const lower = hostname.toLowerCase(); if (BLOCKED_HOSTNAMES.has(lower)) return true;
// Check if hostname is a raw IP address if (/^\d{1,3}(\.\d{1,3}){3}$/.test(lower)) { return isBlockedIPv4(lower); } if (lower.startsWith("[") && lower.endsWith("]")) { return isBlockedIPv6(lower.slice(1, -1)); }
return false;}
/** * Validates that a URL is safe to fetch (not targeting internal/private infrastructure). * Checks protocol and hostname/literal IP only; DNS is never resolved, so a * public name that resolves to a private address still passes. Same checks as * `assertSafeUrlSync`, kept async for existing callers. * Throws an error if the URL is not safe. */export async function assertSafeUrl(urlString: string): Promise<void> { let parsed: URL; try { parsed = new URL(urlString); } catch { throw new Error("Invalid URL"); }
// Only allow HTTP(S) if (parsed.protocol !== "http:" && parsed.protocol !== "https:") { throw new Error( `URL protocol "${parsed.protocol}" is not allowed. Only http: and https: are permitted.`, ); }
// Block known dangerous hostnames and raw private IPs if (isBlockedHost(parsed.hostname)) { throw new Error( "URL targets a private or internal address, which is not allowed.", ); }}
const MAX_REDIRECTS = 3;
function isSameService(from: URL, to: URL): boolean { return ( to.hostname === from.hostname && to.port === from.port && (to.protocol === from.protocol || (from.protocol === "http:" && to.protocol === "https:")) );}
function isSafeRedirect(from: URL, to: URL): boolean { return ( to.hostname === from.hostname && to.port === from.port && (to.protocol === from.protocol || (from.protocol === "http:" && to.protocol === "https:")) );}
/** * `fetch` for customer-supplied URLs. Follows only 307/308 (the redirects that * keep method and body) to the same host and port, never downgrading https, * re-checking every hop; any other 3xx comes back as a non-ok response. * `init.body` must be replayable. */export async function safeFetch( url: string, init?: Omit<RequestInit, "redirect">,): Promise<Response> { let target = url; for (let hop = 0; ; hop++) { await assertSafeUrl(target); const res = await fetch(target, { ...init, redirect: "manual" });
const location = res.headers.get("location"); if ((res.status !== 307 && res.status !== 308) || !location) return res;
// Headers are replayed on the next hop, so it must be the same service and // never plaintext: same host and port, protocol unchanged or http โ https. if ( hop >= MAX_REDIRECTS || !isSafeRedirect(new URL(target), new URL(location, target)) ) { return res; } const next = new URL(location, target); await res.body?.cancel(); target = next.href; }}
/** * Synchronous URL safety check for use in Zod schemas. * Checks protocol and hostname/IP without DNS resolution. */export function assertSafeUrlSync(urlString: string): void { let parsed: URL; try { parsed = new URL(urlString); } catch { throw new Error("Invalid URL"); }
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") { throw new Error( `URL protocol "${parsed.protocol}" is not allowed. Only http: and https: are permitted.`, ); }
if (isBlockedHost(parsed.hostname)) { throw new Error( "URL targets a private or internal address, which is not allowed.", ); }}
/** * Zod schema for URLs that are safe from SSRF. * Validates format and blocks private/internal addresses. */export const safeUrlSchema = z.url({ protocol: /^https?$/ }).refine( (val) => { try { assertSafeUrlSync(val); return true; } catch { return false; } }, { message: "URL must not target private or internal addresses" },);