Documentation
¶
Overview ¶
Package dialguard blocks the addresses a platform-initiated outbound connection must never reach.
Anything that dials a URL supplied by a customer — a vault credential's MCP server or token endpoint, an agent's `mcp_servers` entry — is an SSRF vector, and one that returns response bodies is a full-read vector. The guard refuses the address classes that are never a legitimate third-party endpoint but are prime exfiltration targets: loopback (the platform's own surfaces), link-local (cloud metadata, 169.254.169.254 / fe80::/10), the unspecified address, and multicast.
Two properties matter more than the list. The check runs on the *resolved* IP at connect time (net.Dialer.Control), on every dial, so DNS rebinding cannot slip a blocked address past a name that resolved innocently a moment earlier. And RFC 1918 private ranges are deliberately allowed: this platform's premise is on-prem / in-VPC operation (CLAUDE.md), where MCP servers and token endpoints legitimately live on the operator's own private network — the address-based guard is therefore not the network policy, only the floor beneath it (an agent's egress policy is enforced separately, and refusing RFC 1918 here would break the deployment model rather than protect it).
Redirects are a separate matter this package does not address: a caller that follows one replays its request body to a new target, which a per-hop IP check cannot see is wrong. Callers refuse to follow them (http.ErrUseLastResponse).
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ErrRefused = errors.New("disallowed address")
ErrRefused is wrapped by every refusal this guard produces, so a caller can tell a destination that can never be dialled from a network that may recover: no retry makes a refused address reachable. For an address the guard refuses by class the sentinel is the phrase the message already ended with, so that text is unchanged; the unreadable-address refusal gains it as a suffix, which no caller can reach through Control (net.ParseIP rejects it first) and which nothing asserts on.
Functions ¶
func Control ¶
Control builds a net.Dialer Control hook that runs allow on the resolved address of every dial — the address the connection is about to be made to rather than the name it came from, which is what makes DNS rebinding ineffective. allow is a parameter rather than a hard-wired call so a caller can hand in a closure over its own overridable seam (a test pointing at an httptest server on loopback needs one); the closure itself is captured once and called per dial, so a seam it reads is consulted afresh on each one.
func IPAllowed ¶
IPAllowed reports whether a resolved address may be dialed, returning an error naming the refusal. The text names the resolved address and says only that it is disallowed — never which class matched it, and never whether anything is listening there: the refusal is produced before any connect(2), so it reads identically either way and a caller that surfaces it cannot become an internal-host oracle.
Types ¶
This section is empty.