mirror of
https://github.com/documenso/documenso.git
synced 2026-07-11 05:25:08 +10:00
94adea149d
Refresh README/docs for the current stack, add a security policy, note the external-PR pause, and remove dead config and workflows.
84 lines
1.9 KiB
TypeScript
84 lines
1.9 KiB
TypeScript
import { z } from 'zod';
|
|
|
|
const ZIpSchema = z.string().ip();
|
|
|
|
/**
|
|
* Synchronously check whether a URL's host is a known private/loopback address
|
|
* (localhost, RFC 1918, link-local, loopback, etc.), regardless of protocol.
|
|
*
|
|
* Best-effort and non-exhaustive: unrecognised or unparseable hosts return
|
|
* `false` (fail open). See `assertNotPrivateUrl` for the full SSRF caveats.
|
|
*/
|
|
export const isPrivateUrl = (url: string): boolean => {
|
|
try {
|
|
const parsed = new URL(url);
|
|
const hostname = parsed.hostname.toLowerCase();
|
|
|
|
// Strip IPv6 brackets.
|
|
const bare = hostname.startsWith('[') ? hostname.slice(1, -1) : hostname;
|
|
const normalizedHost = bare.replace(/\.+$/, '');
|
|
|
|
if (normalizedHost === 'localhost') {
|
|
return true;
|
|
}
|
|
|
|
const parsedIp = ZIpSchema.safeParse(normalizedHost);
|
|
|
|
if (!parsedIp.success) {
|
|
return false;
|
|
}
|
|
|
|
if (normalizedHost === '::1' || normalizedHost === '::') {
|
|
return true;
|
|
}
|
|
|
|
if (normalizedHost === '0.0.0.0') {
|
|
return true;
|
|
}
|
|
|
|
if (normalizedHost.startsWith('127.')) {
|
|
return true;
|
|
}
|
|
|
|
if (normalizedHost.startsWith('10.')) {
|
|
return true;
|
|
}
|
|
|
|
if (normalizedHost.startsWith('192.168.')) {
|
|
return true;
|
|
}
|
|
|
|
if (normalizedHost.startsWith('169.254.')) {
|
|
return true;
|
|
}
|
|
|
|
if (normalizedHost.startsWith('fe80:')) {
|
|
return true;
|
|
}
|
|
|
|
if (normalizedHost.startsWith('fc') || normalizedHost.startsWith('fd')) {
|
|
return true;
|
|
}
|
|
|
|
// 172.16.0.0/12
|
|
if (normalizedHost.startsWith('172.')) {
|
|
const second = parseInt(normalizedHost.split('.')[1], 10);
|
|
|
|
if (second >= 16 && second <= 31) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// IPv4-mapped IPv6 (e.g. ::ffff:127.0.0.1)
|
|
const v4Mapped = normalizedHost.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/i);
|
|
|
|
if (v4Mapped) {
|
|
return isPrivateUrl(`http://${v4Mapped[1]}`);
|
|
}
|
|
|
|
return false;
|
|
} catch {
|
|
return false;
|
|
}
|
|
};
|