SSRF guard for the host taken from a domain's _domainconnect TXT record.
Discovery fetches https://<host>/v2/<zone>/settings, and <host> is supplied
by whoever controls the target domain's DNS. Without a guard, a caller who can
submit an arbitrary domain could make this library request internal addresses.
check/2 enforces, before any HTTP request is made:
- the value is a syntactically valid DNS hostname (not an IP literal, no port, userinfo, path, scheme, or whitespace), and
- every address it resolves to is publicly routable — loopback, private (RFC 1918), link-local, unique-local (IPv6 ULA), CGNAT, and multicast/reserved ranges are rejected.
The address resolver is injectable via the :address_resolver option so the
guard can be unit-tested without real DNS, and so callers can supply a
hardened resolver.
Residual risk
This checks the addresses now; the HTTP client re-resolves when it connects, so a DNS-rebinding attacker could still point at a private address between the check and the connection. Run discovery from a network segment that cannot reach internal services for defence in depth.
Summary
Types
Functions
Returns :ok if host is a safe public hostname, else {:error, reason}.
Options
:address_resolver— a 1-arity fun(host :: String.t()) -> {:ok, [:inet.ip_address()]} | {:error, term()}. Defaults to an:inet.getaddrs-backed resolver (IPv4 + IPv6).
Like check/2, but takes a full URL and checks its host. Used to guard the
urlAPI endpoint (served in the discovered settings, hence DNS-influenced)
before this library makes an OAuth/apply request to it.