Documentation
¶
Overview ¶
Package ipguard holds the SSRF private/reserved membership test shared by the egress proxy's policy guard and the composer transport guard. MEMBERSHIP ONLY by design: each consumer keeps its own predicate around it since their semantics deliberately differ. Carries only what stdlib lacks (netip.Addr.IsPrivate covers RFC1918 + IPv6 ULA, so those are NOT re-listed in ReservedV4 — they ARE in Liftable, a different job).
Index ¶
Constants ¶
This section is empty.
Variables ¶
var Liftable = []netip.Prefix{ netip.MustParsePrefix("10.0.0.0/8"), netip.MustParsePrefix("172.16.0.0/12"), netip.MustParsePrefix("192.168.0.0/16"), netip.MustParsePrefix("fc00::/7"), netip.MustParsePrefix("100.64.0.0/10"), }
Liftable is the private/reserved space an operator may declare an internal hostname's SSRF-guard exception into (SiteConfig's InternalHosts). SECURITY invariant: deliberately narrower than PrivateReserved's full denial set — RFC1918, IPv6 ULA, and CGNAT only. Loopback/link-local/metadata/ unspecified/multicast/NAT64-embedded stay un-liftable by construction.
var NAT64Prefixes = []netip.Prefix{ netip.MustParsePrefix("64:ff9b::/96"), netip.MustParsePrefix("64:ff9b:1::/48"), }
NAT64Prefixes are the well-known + local-use NAT64 translation prefixes (RFC 6052 / RFC 8215). SECURITY: an address inside one carries a real IPv4 in its low 32 bits, so a private/metadata target can be smuggled as an IPv6 literal (64:ff9b::a9fe:a9fe -> 169.254.169.254) past every stdlib predicate — every guard must block these wholesale and re-check the embedded v4.
var NeverLift = []netip.Prefix{ netip.MustParsePrefix("fd00:ec2::/64"), netip.MustParsePrefix("100.100.100.200/32"), }
NeverLift carves the cloud instance-metadata endpoints out of Liftable: they sit inside the ULA and CGNAT ranges an operator may lift, but lifting one would hand a sandbox the host's cloud credentials.
var ReservedV4 = []netip.Prefix{ netip.MustParsePrefix("100.64.0.0/10"), netip.MustParsePrefix("0.0.0.0/8"), netip.MustParsePrefix("192.0.0.0/24"), netip.MustParsePrefix("198.18.0.0/15"), netip.MustParsePrefix("255.255.255.255/32"), netip.MustParsePrefix("192.0.2.0/24"), netip.MustParsePrefix("198.51.100.0/24"), netip.MustParsePrefix("203.0.113.0/24"), netip.MustParsePrefix("192.88.99.0/24"), netip.MustParsePrefix("240.0.0.0/4"), }
ReservedV4 are the reserved IPv4 ranges every guard denies that netip.Addr.IsPrivate doesn't cover.
SECURITY invariant: canonical surface is the IANA IPv4 Special-Purpose Address Registry, not a remembered RFC list — an entry belongs here when the registry's "Globally Reachable" column is False and no stdlib predicate covers it; diff against the registry, not intuition. Rows that ARE globally reachable (192.31.196.0/24, 192.52.193.0/24, 192.175.48.0/24) stay out.
Order matters only for the reason string: PrivateReserved names the FIRST matching prefix, so 255.255.255.255/32 stays listed ahead of the 240.0.0.0/4 that now contains it.
var ReservedV6 = []netip.Prefix{ netip.MustParsePrefix("fec0::/10"), netip.MustParsePrefix("2002::/16"), netip.MustParsePrefix("2001::/32"), netip.MustParsePrefix("2001:2::/48"), netip.MustParsePrefix("2001:10::/28"), netip.MustParsePrefix("2001:db8::/32"), netip.MustParsePrefix("3fff::/20"), netip.MustParsePrefix("5f00::/16"), netip.MustParsePrefix("100::/64"), }
ReservedV6 are the reserved IPv6 ranges every guard denies that no stdlib predicate covers (IsPrivate covers fc00::/7, IsLinkLocalUnicast covers fe80::/10). Same canonical surface as ReservedV4.
2002::/16 is denied wholesale here, not parsed in NAT64Prefixes: it embeds a real IPv4 at bits 16..48, not the low 32 NAT64EmbeddedV4 extracts, and 6to4 is deprecated and unroutable anyway.
Functions ¶
func GatewayIPRefused ¶ added in v0.7.0
GatewayIPRefused reports whether ip is a kind an operator-configured internal model gateway can never legitimately be: loopback, link-local (metadata address included), unspecified, multicast, or NAT64-embedded. RFC1918/ULA/CGNAT are NOT refused — an internal gateway is expected to live there, so this is a DIFFERENT predicate from PrivateReserved, not a subset of it.
TRUST BOUNDARY: shared body for two consumers whose agreement matters — the control plane validates the configured gateway at boot, and the proxy re-checks the RESOLVED answer per request before dialling it with the brokered model credential.
A nil/!ok address is REFUSED: addrOf fails only on a slice that isn't an address at all, and an unparseable gateway address is not one this may admit.
func InLiftable ¶ added in v0.7.0
InLiftable reports whether ip falls inside the Liftable set and outside NeverLift.
func NAT64EmbeddedV4 ¶ added in v0.3.1
NAT64EmbeddedV4 returns the IPv4 embedded in ip's low 32 bits and true when ip is inside a NAT64 prefix, else (nil, false). SECURITY: callers must block the prefix wholesale and re-run the embedded v4 through their own v4 guard, so a NAT64-smuggled private/metadata target can't slip past To4()==nil.
func PrivateReserved ¶ added in v0.4.3
PrivateReserved reports whether ip is in a private/reserved range every guard denies regardless of mode (RFC1918 + IPv6 ULA via stdlib IsPrivate, plus ReservedV4/ReservedV6), naming the matching range for the denial reason. IPv4-mapped IPv6 addresses are unwrapped first.
Types ¶
This section is empty.