Skip to main content

url-safety hook

The url-safety hook decides whether a server-side fetch destination is safe from SSRF (a request that can be aimed at internal services, cloud metadata, or private hosts). It is the deterministic engine the ssrf sub-skill uses, and you can run it yourself.

Run it

Pass the URL or host as a single quoted argument. The hook prints one word, read in the table below.

node ./.bluespec/hooks/url-safety.mjs 'http://example.com/'
# => safe
node ./.bluespec/hooks/url-safety.mjs 'http://0x7f000001/'
# => private-target

It resolves the host the way the network does, so encoded forms of an internal address do not slip past. Hex, octal, dotless decimal, shorthand, IPv6, and IPv4-mapped forms all read private-target.

node ./.bluespec/hooks/url-safety.mjs 'http://[::1]/'
# => private-target

Where a sloppy validator and the real fetcher would read different hosts, the verdict is parser-divergent.

node ./.bluespec/hooks/url-safety.mjs 'http://allowed.com@evil.example/'
# => parser-divergent

How to read the verdict

VerdictMeaning
safeThe destination connects to a public host, and a sloppy validator would read the same host.
private-targetThe host resolves to a private, loopback, link-local, unspecified, or cloud-metadata address.
parser-divergentA sloppy text validator would read a different real host than the one the fetcher connects to.
invalid urlThe string does not parse as a URL, so there is nothing to fetch.

Only safe is an allow. Treat private-target, parser-divergent, and invalid url all as a block.

Why a quoted argument

The destination is passed as a positional argument, never interpolated into the command. A value with quotes or backticks stays inert and cannot inject into the shell. Always wrap it in single quotes so your shell does not expand it first.

What it does not cover

The hook is pure and never resolves DNS, so two runtime gaps are out of scope: DNS rebinding (a name that resolves public when checked, internal when fetched) and redirect chains (a first hop that passes, then redirects inward). Close those by resolving once and connecting to the validated address, and by refusing redirects. It pins the reserved loopback names (localhost and the .localhost family) to internal with no lookup, but any other DNS name reads safe, because asserting it resolves inward would require resolution: the allowlist guards those, and is what this checker validates.

tip

This is the same check the ssrf sub-skill runs to prove a destination filter holds. Point it at each host in an allowlist or denylist, one call per host.