
`import url_policy` decides whether a URL is safe to fetch from a public
API or webhook. It is the runtime side of the `UrlPolicy` validator
declared inside an `api` block and is used by the `PublicUrl` and
`PublicWebhookUrl` validators.

## SSRF threat model

A public API that fetches user-supplied URLs is exposed to server-side
request forgery: a malicious client points the server at an internal
address, a cloud metadata endpoint, or a Via control-plane port. This
module rejects the unsafe categories before any HTTP request is issued.

| Category | Blocked because |
|---|---|
| Non-HTTPS schemes | Only `https` is allowed; `http`, `file`, `gopher` are rejected. |
| Loopback | `localhost`, `*.localhost`, IPv4 loopback `127/8`, IPv6 `::1`. |
| Private and link-local IPv4 | `10/8`, `172.16/12`, `192.168/16`, `169.254/16`, `0.0.0.0/8`. |
| IPv6 unique-local and link-local | `fc00::/7`, `fe80::/10`. |
| Cloud and platform metadata | Common metadata and cluster suffixes (see list below). |
| Bad hostnames | Empty host, `..`, embedded backslash or newline. |

Metadata and cluster suffixes blocked under "Cloud and platform metadata":
`metadata.google.internal`, `host.docker.internal`,
`kubernetes.default.svc`, `.svc`, `.cluster.local`, `.local`, `.internal`.

`public_outbound_url_allowed(url, [resolved_addresses], allow_via_internal)`
returns `True` only when the scheme, hostname, and (optionally) every
resolved address pass.

`public_webhook_url_allowed(url)` is the strict webhook entry point: it
never allows internal services and never accepts resolved addresses.

## Redirect chains

```text
import url_policy

let chain = ["https://example.com/initial", "https://example.com/step-2"]
if not url_policy.public_outbound_redirect_chain_allowed(target, chain) {
    return Err(api_boundary_error("validation_error", "url", "redirect chain not allowed"))
}
```

`public_outbound_redirect_chain_allowed(url, chain, allow_via_internal)`
re-runs the per-URL check for the original target and every redirect
target. A single internal-host redirect aborts the chain.

## Webhook example

```text
import url_policy
import api_response

def register_webhook(request: RegisterWebhookRequest, ctx: RequestContext) -> Str {
    if not url_policy.public_webhook_url_allowed(request.url) {
        return api_response.validation_error("url", "must be a public https URL", ctx.request_id.value)
    }
    store.create_webhook(request.url, request.events)
    return api_response.ok_object("webhook", Json.empty_object())
}
```

For DSL-declared validators, prefer the `UrlPolicy { ... }` block inside
the `api` declaration; it forwards to these functions and is the source
of truth for OpenAPI metadata and release artifacts.

## When to allow internal services

`allow_via_internal = True` is reserved for inter-service calls inside the
Via platform itself, where the destination is a trusted control-plane
endpoint. Application code should never set this flag.
