#985·n8n-mcp

Replace hand-rolled SSRF address denylists with an IANA special-purpose registry table

Author: czlonkowskiCreated Aug 9, 2026Updated Aug 9, 2026

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 pins 2001:db8::1. Changing that is a separate decision.
  • The existing boundary tests in tests/unit/utils/ssrf-protection.test.ts should survive the rewrite unchanged — they are the contract.