AshHooks.Ssrf (AshHooks v1.0.0)

Copy Markdown View Source

The SSRF destination classifier (ADR-0005 — enforced at endpoint registration AND again at send time).

A URL is safe when: the scheme is http/https, the host is not a known cloud-metadata name, and EVERY address the host resolves to is public. Any private/loopback/link-local/ULA/CGNAT/reserved answer rejects the URL — one rogue A record among the answers is exactly the bypass; resolver failure rejects too (fail-closed: an unresolvable destination cannot be verified safe).

IPv4-mapped and IPv4-compatible IPv6 forms (::ffff:a.b.c.d, ::a.b.c.d) are normalized to their embedded v4 before classification. Registration-time resolution is bounded (@resolve_timeout_ms) — an offline or slow resolver fails the cast loudly rather than hanging it.

Residual (documented, by design of the :httpc adapter): send-time re-resolution narrows but cannot eliminate the DNS-rebinding TOCTOU window — full elimination needs connect-time IP pinning the adapter cannot express.

Summary

Functions

The REGISTRATION-time check: scheme, metadata hostnames, and literal-IP hosts — deterministic and offline-safe (hostname DNS is the delivery runtime's send-time check, per ADR-0005's split).

The VALIDATED ADDRESSES behind a URL — the adapter's pinning input: {:ok, %{uri: URI, addresses: [ip]}} when every resolved address is public, {:error, :unsafe | :unresolvable} otherwise. The connection target comes FROM this same resolution, which is what closes the rebinding TOCTOU (validate-then-connect-on-the-same-answer).

True when url is an http(s) URL whose host is not metadata and whose resolved addresses are all public.

Functions

registration_safe?(url)

@spec registration_safe?(term()) :: boolean()

The REGISTRATION-time check: scheme, metadata hostnames, and literal-IP hosts — deterministic and offline-safe (hostname DNS is the delivery runtime's send-time check, per ADR-0005's split).

resolve_public(url)

@spec resolve_public(term()) ::
  {:ok, %{uri: URI.t(), addresses: [:inet.ip_address()]}} | {:error, atom()}

The VALIDATED ADDRESSES behind a URL — the adapter's pinning input: {:ok, %{uri: URI, addresses: [ip]}} when every resolved address is public, {:error, :unsafe | :unresolvable} otherwise. The connection target comes FROM this same resolution, which is what closes the rebinding TOCTOU (validate-then-connect-on-the-same-answer).

safe_url?(url)

@spec safe_url?(term()) :: boolean()

True when url is an http(s) URL whose host is not metadata and whose resolved addresses are all public.