Nous.Tools.UrlGuard (nous v0.17.1)

Copy Markdown View Source

SSRF protection for outbound HTTP from tools and providers.

Prevents prompt-injected agents from reaching cloud-metadata (169.254.169.254), internal services on private networks, loopback, and link-local ranges. By default only http:// and https:// schemes are accepted; other schemes (file://, gopher://, ftp://, etc.) are rejected.

Usage

case Nous.Tools.UrlGuard.validate("https://example.com/foo") do
  {:ok, uri} -> proceed_with(uri)
  {:error, reason} -> {:error, reason}  # human-readable
end

Opt-in: allowing private hosts

For local dev / Docker dev-loop you can pass allow_private_hosts: true:

Nous.Tools.UrlGuard.validate(url, allow_private_hosts: true)

Do NOT enable this in production. It re-opens the SSRF channel.

Escape hatch: exact-IP allowlist

config :nous, url_guard_allow_ips: [{127, 0, 0, 1}] exempts those exact :inet.ip_address() tuples from the address blocklist — nothing else. No CIDR, no hostnames, no ranges; the scheme check, DNS resolution, and the per-hop re-validation callers do on redirects all still run. It exists so the test suite can point a fetch at a loopback-bound Bypass server (and so an egress proxy on a fixed private IP can be whitelisted deliberately). Defaults to [].

Do NOT set it in production. Every address you list is an address a prompt-injected agent can reach.

Summary

Functions

Validate a URL string. Returns {:ok, %URI{}} or {:error, reason}.

Like validate/2, but also returns one validated IP address to pin the subsequent connection to — closing the DNS-rebinding TOCTOU window where the guard resolves one IP and the HTTP client independently resolves another.

Functions

validate(url, opts \\ [])

@spec validate(
  String.t(),
  keyword()
) :: {:ok, URI.t()} | {:error, String.t()}

Validate a URL string. Returns {:ok, %URI{}} or {:error, reason}.

Options

  • :allow_private_hosts — when true, skips the private/loopback blocklist. Defaults to false.

validate_pinned(url, opts \\ [])

@spec validate_pinned(
  String.t(),
  keyword()
) :: {:ok, URI.t(), :inet.ip_address() | nil} | {:error, String.t()}

Like validate/2, but also returns one validated IP address to pin the subsequent connection to — closing the DNS-rebinding TOCTOU window where the guard resolves one IP and the HTTP client independently resolves another.

Returns {:ok, %URI{}, ip_tuple} (or {:ok, %URI{}, nil} when host checking was skipped via allow_private_hosts: true). Because validation rejects the URL if any resolved address is blocked, the returned address is always safe.