Skip to main content

Module ssrf

Module ssrf 

Source
Expand description

SSRF classifier.

A pure address classifier plus a DNS-resolving host guard. The rule it enforces is blunt: the HTTP client refuses RFC-1918 and link-local destinations by default. This module is the mechanism; it is composed at every call site that introduces a model/agent/peer-supplied URL (A2A push targets, http workflow nodes), while the only operator-configured outbound (intel/client.rs) is exempt — its address comes from the operator, not from anything the model can steer.

§Resolve once, dial what you vetted

A guard that resolves a name, likes the answer, and then lets the caller dial the name is decorative: the connect re-resolves, and an attacker who controls the authoritative DNS answers the guard with a public address and the connect with 169.254.169.254. That is DNS rebinding, and it defeats an address check that does not carry its result forward.

So the guard hands back the addresses it vetted (resolve_guarded) and the dial takes addresses, never a name (connect_vetted / connect_addrs, which re-assert is_global on every address immediately before the syscall). TLS and the Host header stay on the original hostname — connect by IP, verify by name — so SNI and certificate validation are unaffected.

guard_host serves the yes/no admission check at registration time, where there is no socket to dial yet; it is not sufficient on its own at delivery time.

§What “non-global” means here

is_global returns false — i.e. the address is blocked — for any address an attacker could pivot to from inside the appliance’s network namespace:

  • loopback (127.0.0.0/8, ::1)
  • RFC-1918 private (10/8, 172.16/12, 192.168/16)
  • link-local (169.254/16, fe80::/10) — this is the cloud metadata range (169.254.169.254)
  • IPv6 unique-local (fc00::/7)
  • unspecified (0.0.0.0, ::)
  • multicast and the IPv4 limited broadcast (255.255.255.255)
  • “this network” 0.0.0.0/8 and the IETF/benchmark documentation ranges, which never route on the public Internet
  • any IPv4-mapped / IPv4-compatible IPv6 whose embedded v4 address is itself non-global — ::ffff:127.0.0.1 and friends are a classic guard bypass, so we unwrap before classifying.

We deliberately do NOT lean on std’s unstable IpAddr::is_global (feature ip, issue #27709) — it is not available on our MSRV and its semantics are still in flux. Every range below is spelled out by hand from primitives that are stable, in the same enumerate-the-bytes spirit as the rest of the crate.

§Logging posture

Hosts and IPs are operational identifiers, not tool/instruction content, so the diagnostic carries the host and the offending class — never request bodies, headers, or secrets. The codebase is content-capture-off by default and this module keeps that contract.

Structs§

SsrfError
A host failed the SSRF guard, or could not be resolved at all.

Functions§

connect_addrs
Dial one of addrs, re-asserting the classifier on every entry first.
connect_vetted
Guard and dial in one step: resolve once, vet, connect to a vetted address. This is what a model/peer-supplied URL must use instead of http::connect_tcp, which resolves the name a second time.
connect_vetted_with
connect_vetted against an injected resolver — the test seam.
guard_host
Resolve host and reject if any resolved address is non-global.
is_global
true iff ip is a globally routable unicast address that is safe to dial from inside the appliance — i.e. not in any of the blocked ranges documented on this module.
resolve_guarded
Resolve host:port once and return the addresses, having rejected the whole host if any of them is non-global.
resolve_guarded_with
resolve_guarded against an injected resolver. Public so the rebinding regression test can drive both halves — guard and dial — through a resolver that changes its mind between them.
std_resolve
The production resolver: std’s ToSocketAddrs, with the bracketed IPv6 literal form ([::1], as URLs write it) unwrapped first because ToSocketAddrs does not accept the brackets on a bare host.

Type Aliases§

ResolveFn
How a host is turned into addresses. A plain fn pointer, not a trait object: the only production implementation is std_resolve, and the seam exists so a test can install a hostile resolver that answers the guard and the dial differently — the rebinding shape this module has to survive.