# `Nous.Tools.UrlGuard`
[🔗](https://github.com/nyo16/nous/blob/v0.17.1/lib/nous/tools/url_guard.ex#L1)

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.

# `validate`

```elixir
@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`

```elixir
@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.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
