Destination validation for outbound webhooks.
Endpoint registration uses registration_safe?/1: it checks the HTTP(S)
scheme, known metadata hostnames, and literal IP addresses without DNS.
Hostname resolution happens at send time, so registration does not depend
on the destination's current DNS availability.
resolve_host_public/2, safe_url?/1, and resolve_public/2 resolve IPv4
and IPv6 addresses and require a nonempty result containing only public
addresses. Private, loopback, link-local, unique-local, shared, and reserved
ranges are rejected.
IPv4-mapped and IPv4-compatible IPv6 addresses are classified using their
embedded IPv4 address, as does the NAT64 64:ff9b::/96 prefix. Other IPv6
addresses require an allocated prefix from the IANA IPv6 Global Unicast
Address Assignments registry, with non-global special-purpose ranges
refused and their globally reachable exceptions retained. Reserved and
unallocated space is refused. The allocation table was retrieved on
November 13, 2026; new allocations require a package update before admission.
A hostname that fails to resolve fails as :nxdomain when every queried
family returned an authoritative negative answer, and as :resolve_timeout
otherwise (timeout, temporary resolver failure, or mixed evidence), using the
conservative retryable label. safe_url?/1 stays boolean: both are
false there.
DNS resolution has a two-second ceiling, shortened by a supplied operation deadline. Both supplied HTTP adapters connect to an address from the validated result while retaining the original hostname for TLS and HTTP.
Callers of resolve_host_public/2 must resolve at every connect, reconnect,
redirect, failover, and broker-advertised address. Connect only to an address
returned by that call, and keep the original name for TLS verification and
server name indication. Resolving again through another connection API
would permit DNS rebinding between validation and connection.
Summary
Functions
Checks the scheme, metadata hostnames, and literal IP addresses at registration. This check does not resolve hostnames.
Resolves a host to a nonempty list containing only public IP addresses.
Returns validated addresses behind a URL for connection pinning:
{:ok, %{uri: URI, addresses: [ip]}} when every resolved address is
public, {:error, :unsafe} when an answer is non-public, and
{:error, :nxdomain | :resolve_timeout} when the name does not resolve.
The connection target must come from this same result to prevent DNS
rebinding between validation and connection.
True when url is an http(s) URL whose host is not metadata and whose
resolved addresses are all public.
Functions
Checks the scheme, metadata hostnames, and literal IP addresses at registration. This check does not resolve hostnames.
@spec resolve_host_public(term(), keyword()) :: {:ok, [:inet.ip_address()]} | {:error, :unsafe | :nxdomain | :resolve_timeout | :invalid_host}
Resolves a host to a nonempty list containing only public IP addresses.
Accepts a DNS name with IDNA-encoded ASCII labels (letters, digits,
underscores, and inner hyphens) and at most one trailing dot, a bare IPv4 literal, or a bare or bracketed IPv6 literal. Malformed
brackets, zone identifiers, ports, schemes, paths, credentials, whitespace,
control characters, empty labels, and other malformed hosts return
{:error, :invalid_host} without raising.
Returns {:ok, [ip_address]} only when every resolved address is public.
A non-public answer or known metadata hostname returns {:error, :unsafe}.
Resolution failures return {:error, :nxdomain | :resolve_timeout}.
There is no destination-policy bypass.
DNS resolution has a two-second ceiling. The optional :deadline is an
absolute System.monotonic_time(:millisecond) value that can shorten it.
Resolve at every connect, reconnect, redirect, failover, and broker-advertised address. Connect only to a returned address and retain the original name for TLS verification and server name indication.
@spec resolve_public(term(), keyword()) :: {:ok, %{uri: URI.t(), addresses: [:inet.ip_address()]}} | {:error, atom()}
Returns validated addresses behind a URL for connection pinning:
{:ok, %{uri: URI, addresses: [ip]}} when every resolved address is
public, {:error, :unsafe} when an answer is non-public, and
{:error, :nxdomain | :resolve_timeout} when the name does not resolve.
The connection target must come from this same result to prevent DNS
rebinding between validation and connection.
True when url is an http(s) URL whose host is not metadata and whose
resolved addresses are all public.