Replace hand-rolled SSRF address denylists with an IANA special-purpose registry table
src/utils/ssrf-protection.ts classifies outbound destinations with two hand-maintained denylists: PRIVATE_IP_RANGES (IPv4 regexes) and the prefix tests inside isPrivateOrMappedIpv6. Four advisories have now been point-fixes to this same file, the most recent (GHSA-2x5j-hrmv-ccrq, v2.69.0) because one range was matched by a string prefix that covered only part of its CIDR.
The v2.69.0 fix moved IPv6 to numeric first-hextet masks, which is more robust, but the underlying shape is unchanged: a curated list that has to be remembered and extended by hand. Proposal is to drive classification from a table of IANA special-purpose CIDRs with an explicit globally-reachable flag, matched numerically, so adding a range is a data change rather than a regex.
Do not use ipaddr.js range() for this. It was evaluated and rejected during the v2.69.0 work. At the pinned 1.9.1:
| Address | range() |
Problem |
|---|---|---|
fec0::1 |
unicast |
deprecated site-local, currently blocked — would regress |
198.18.0.1 |
unicast |
benchmarking range reported as ordinary unicast |
192.0.0.9 |
reserved |
globally reachable (PCP anycast) — would over-block |
100::1, 3fff::1 |
unicast |
non-global ranges reported as ordinary unicast |
unicast is the library's fallback bucket, not a reachability signal, and its table is not a maintained IANA mirror.
Constraints for whoever picks this up:
- Tunneling extraction (NAT64 / 6to4 / Teredo) must keep running before generic range rejection; those prefixes carry an embedded IPv4 whose own classification decides the outcome.
- Cloud-metadata endpoints must stay blocked in every security mode, including
permissive. - Documentation and benchmarking ranges are currently allowed on purpose (
192.0.2.0/24,198.51.100.0/24,203.0.113.0/24,198.18.0.0/15,2001:db8::/32) and a test pins2001:db8::1. Changing that is a separate decision. - The existing boundary tests in
tests/unit/utils/ssrf-protection.test.tsshould survive the rewrite unchanged — they are the contract.
Source: czlonkowski/n8n-mcp