SSRF-safe URL validation library for Node.js
Lightweight TypeScript library for validating URLs to prevent Server-Side Request Forgery (SSRF) attacks. It blocks requests to private IP ranges, localhost, link-local addresses, and other special-purpose networks using Microsoft's maintained @microsoft/antissrf block lists.
node-safeurl provides the following exports:
fetch(input, init?)— SSRF-safe drop-in replacement for the globalfetch. Validates the URL (and every redirect hop) before making the request, and returns aTypedResponse. Pass{ safeUrl: false }to opt out of validation entirely, or{ safeUrlAllow: [...] }to allow specific private-network origins while keeping all other SSRF checks active.TypedResponse— Subclass ofResponsethat adds a.typed(schema)method for runtime-validated JSON parsing via TypeBox.FetchInit— TypeScript interface extendingRequestInitwith the optionalsafeUrlboolean andsafeUrlAllowstring-array fields.isSafeUrl(href, opts?)— Async function that validates a URL is safe to fetch. Checks protocol, hostname, IP literals, and performs DNS resolution to guard against DNS rebinding attacks. Pass{ allow: [...] }to exempt specific origins or hostnames from all checks.SafeUrlOptions— TypeScript interface for the options accepted byisSafeUrl.isPrivateIPv4(address)— Synchronous check for private/special-purpose IPv4 addresses.isPrivateIPv6(address)— Synchronous check for private/special-purpose IPv6 addresses.
npm install @tak-ps/node-safeurlimport fetch, { isSafeUrl, isPrivateIPv4, isPrivateIPv6 } from '@tak-ps/node-safeurl';
import { Type } from '@sinclair/typebox';
// SSRF-safe fetch — validates the URL and every redirect hop automatically
const res = await fetch('https://example.com/api/data');
const data = await res.typed(Type.Object({ id: Type.Number() }));
// Opt out of SSRF validation entirely for trusted internal calls
const internal = await fetch('http://localhost:3000/health', { safeUrl: false });
// Allow a specific private-network origin while keeping all other SSRF checks active
const api = await fetch('http://10.0.0.5:8080/api/status', {
safeUrlAllow: ['http://10.0.0.5:8080'],
});
// Validate a URL manually before fetching
const result = await isSafeUrl('https://example.com/api');
if (result.safe) {
// Safe to fetch
const response = await fetch(result.url);
} else {
console.error('Blocked:', result.reason);
}
// Validate a URL while allowing a known private endpoint
const result2 = await isSafeUrl('http://192.168.1.10/health', {
allow: ['192.168.1.10'],
});
// Check individual IPs
isPrivateIPv4('192.168.1.1'); // true
isPrivateIPv4('8.8.8.8'); // false
isPrivateIPv6('::1'); // true
isPrivateIPv6('2606:4700:4700::1111'); // falseSSRF-safe drop-in replacement for the global fetch. Validates the initial URL and every redirect destination against isSafeUrl before the request is made. Throws an Err(403) if a URL is deemed unsafe.
init accepts all standard RequestInit options plus:
safeUrl(boolean, defaulttrue) — set tofalseto skip all SSRF validation (e.g. for fully trusted internal endpoints).safeUrlAllow(string[], default[]) — list of origins (e.g."http://10.0.0.5:8080") or bare hostnames (e.g."10.0.0.5") that bypass SSRF checks while all other URLs remain validated. Use this instead ofsafeUrl: falsewhen only specific private-network endpoints need to be exempted.
Custom dispatcher options are rejected when safeUrl is true because they can bypass SSRF protection.
Subclass of Response returned by fetch. Adds:
Parses the response body as JSON and validates it against a TypeBox schema. Throws Err(500) if validation fails.
const res = await fetch('https://api.example.com/user/1');
const user = await res.typed(Type.Object({
id: Type.Number(),
name: Type.String(),
}));TypeScript interface extending RequestInit with additional fields:
interface FetchInit extends RequestInit {
safeUrl?: boolean; // default: true — set false to skip all SSRF checks
safeUrlAllow?: string[]; // origins or hostnames exempt from SSRF checks
}isSafeUrl(href: string, opts?: SafeUrlOptions): Promise<{ safe: boolean; url?: URL; reason?: string }>
Validates that a URL is safe to fetch from a server context. Returns an object with:
safe—trueif the URL is safe,falseif it should be blockedurl— The parsedURLobject (when the URL could be parsed)reason— A human-readable string explaining why the URL was blocked
opts accepts:
allow(string[]) — origins (e.g."http://10.0.0.5:8080") or bare hostnames that are unconditionally considered safe, bypassing all checks below.
Checks performed (when not matched by allow):
- URL must be parseable
- Protocol must be
http:orhttps: - Hostname must not be
localhostor0.0.0.0 - IP literal hostnames must not be in private/special-purpose ranges
- DNS resolution results must not map to private/special-purpose IPs
interface SafeUrlOptions {
allow?: string[]; // origins or hostnames exempt from all SSRF checks
}Returns true if the given string is a valid IPv4 address in a private or special-purpose range.
Returns true if the given string is a valid IPv6 address in a private or special-purpose range.
The following ranges are blocked (via @microsoft/antissrf):
- IPv4: Loopback (127.0.0.0/8), RFC 1918 (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16), Link-local (169.254.0.0/16), CGNAT (100.64.0.0/10), and more
- IPv6: Loopback (::1/128), ULA (fc00::/7), Link-local (fe80::/10), IPv4-mapped (::ffff:0:0/96), and more
MIT