Date: 2026-07-15. Class: SSRF guard completeness. Result: Flowise WALK (saturated) + noted residual.
Two ways to block internal/metadata SSRF after resolving the IP:
- DENY-LIST (enumerate bad ranges/IPs) - e.g. Flowise DEFAULT_DENY_LIST. Inherently INCOMPLETE:
you must remember EVERY internal range + EVERY cloud's metadata IP. Flowise's (good-faith, fairly
thorough) list still omitted 100.64.0.0/10 (CGNAT -> Alibaba metadata 100.100.100.200) and
192.0.0.192 (Oracle metadata), and decodes only IPv4-mapped IPv6 (not 6to4/NAT64/ISATAP/Teredo).
- RANGE-SET / is_private (allow-list-by-property) - e.g. pydantic-ai: check the resolved IP with
ipaddress .is_private/.is_link_local/.is_reserved + a small ALWAYS-BLOCK metadata set +
EXHAUSTIVE transition-form decode. Much closer to complete because it keys on address PROPERTIES.
When auditing an SSRF fix, check whether the internal-IP test is a hand-maintained deny-LIST or a property-based range check. A deny-list -> ALWAYS look for a missing range (CGNAT/Alibaba 100.64, Oracle 192.0.0.192, IPv6 ULA/link-local, benchmarking 198.18, 240/4, and non-IPv4-mapped transition forms). It's almost always incomplete. BUT: on a SATURATED target (Flowise = 5+ SSRF advisories) this residual is low-EV (swarm will sweep it / triaged as minor hardening) -> note + deprioritize, don't force it as a cash candidate. On a FRESH/unswept target the same deny-list gap can be a real finding.
Reference remediation: [[pydantic-ai-safe-download-complete-ssrf-fetcher]] (range-set + pin + redirect re-validate). Real deny-list-gap HIT (unsaturated): Forgejo webhook reserved-range SSRF (cycle282). SATURATED-SURFACE doctrine (CLAUDE.md): 2+ disclosure strikes on a class -> deprioritize the target.