Web Fetch DNS and SSRF Model
Purpose
web.fetch.v1 is a pre-action authorization policy. It decides whether a
proposed web request is authorized by the passport and policy context before a
harness or application performs the request.
It is not, by itself, an egress proxy, DNS resolver, or network sandbox. That
distinction matters for private-network and SSRF claims.
Current Guarantees
Hosted verifier
The hosted verifier evaluates the request metadata it receives:
urlmethod- configured
allowed_domains - configured
blocked_domains - headers and body size when supplied
max_requests_per_min is NOT evaluated. validateWebFetchRateLimit infunctions/utils/policy/custom-validators.ts is a stub: it returns{ valid: true } without reading the context, with a TODO for a KV- or
D1-backed counter. A passport that sets the limit is not rate limited by this
policy, so do not rely on it as a control.
Hosted web.fetch.v1 requires an explicit domain allowlist. An emptyallowed_domains list denies. This means an arbitrary attacker-owned domain is
not authorized unless the passport explicitly allows it or uses a wildcard such
as "*".
The hosted private-network validator blocks deterministic URL host forms:
localhostand.localhost- loopback and private IPv4 literals
- shortened, integer, octal, and hex IPv4 forms
- IPv4-mapped IPv6 private literals
- unique-local, link-local, unspecified, and loopback IPv6 literals
The hosted verifier does not fetch the URL, does not resolve DNS, and does not
pin the eventual outbound connection to a checked IP address. Therefore, hosted
verification is not vulnerable to APort-server SSRF from web.fetch.v1, but it
cannot prove that an allowed hostname will not resolve to a private address at
execution time.
Example:
| Request | Hosted decision input can detect |
|---|---|
http://169.254.169.254/latest/meta-data
|
Yes, private IP literal |
http://127.1/admin
|
Yes, normalized loopback literal |
http://attacker.example/internal, where DNS resolves to 169.254.169.254
|
No, unless the runtime supplies trusted resolution evidence or APort controls the fetch path |
Local bash guardrail
The local bash guardrail performs similar literal checks before allowing localweb.fetch.v1 decisions. It is intentionally small and offline-first.
As of this note, the local bash guardrail does not resolve DNS before applying
private-network checks. If a local passport has no effective domain allowlist,
or uses a broad allowlist, a hostname that resolves to a blocked private address
can bypass the literal-only private-network check.
This is a valid local guardrail gap and should be treated as security-relevant
for users who rely on local web.fetch.v1 to contain prompt-injected web
requests.
What Is Out Of Scope For A Pure Verifier
A verifier that only receives a proposed URL cannot fully guarantee DNS/IP
safety for the eventual network request. The unsafe cases include:
- DNS records changing between authorization and fetch
- DNS rebinding
- split-horizon DNS
- runtime-specific proxy and resolver behavior
- redirects to newly resolved destinations
- connection reuse or alternate address-family selection
Full SSRF containment requires the policy enforcement point to control or prove
the network path.
Correct High-Assurance Pattern
For strong private-network protection, use one of these patterns:
- APort-controlled egress proxy. The proxy resolves, checks, connects, and
records the final destination. The verifier can trust the proxy evidence.
- Trusted runtime evidence. The harness resolves the hostname, checks every
A/AAAA answer, passes resolved-address evidence to the verifier, and pins the
actual fetch to that checked resolution.
- Network sandbox. The harness or container blocks private-network egress
regardless of the verifier decision.
Best-effort DNS resolution inside the verifier can catch static DNS names that
point at private addresses, but it is not a complete TOCTOU defense and adds
latency and nondeterminism to the hot path.
Recommended Posture Today
For hosted deployments:
- Keep
allowed_domainsnarrow. - Avoid
allowed_domains: ["*"]for agents that can read sensitive data or
act on prompt-injected web content.
- Treat private-IP literal blocking as deterministic hardening, not complete
DNS-aware SSRF prevention.
- Use a controlled egress proxy or sandbox when private-network egress must be
impossible.
For local guardrail deployments:
- Configure explicit
allowed_domainsinstead of relying on default broad
local behavior.
- Treat broad local
web.fetchaccess as experimental unless the harness also
provides network isolation.
- Patch the local bash guardrail to resolve hostnames before private-network
checks as a defense-in-depth improvement.
Disclosure Classification
A report that a hostname resolving to a private address bypasses local
literal-only checks should be accepted as a real issue in the local guardrail.
Suggested classification:
- Local bash guardrail: High when broad or absent domain allowlists are used.
- Hosted verifier: Lower impact. Same DNS-resolution limitation exists in
private-IP semantics, but hosted verification does not perform the fetch and
hosted web.fetch.v1 requires explicit domain allowlists.
- APort API SSRF: Not demonstrated by this finding.
Relationship To Jev-Style Tool Gates
OpenRouter's
Jev tool-call gate cookbook
describes a tool-call gate where application code sends the proposed tool call,
ticket evidence, and policy text to a Decisions model. The model returns
probabilities for yes/no questions. Application-owned thresholds turn those
probabilities into approve, block, or review.
This is directionally similar to APort because both patterns sit before the
tool call and decide whether the action should run.
They differ in important ways:
| Dimension | Jev-style gate | APort/OAP |
|---|---|---|
| Identity model | Application-defined state | Passport-backed agent identity and capabilities |
| Decision shape | Probabilities plus app thresholds | Signed binary allow/deny decision |
| Policy source | Policy text and task evidence supplied per gate | Versioned policy packs, passport limits, and context |
| Audit | App logs the reason and probabilities | Hosted decisions can be signed, persisted, queried, and joined to runtime telemetry |
| Human review | Middle probability range maps to review | Human approval is an enforcement workflow layered around OAP decisions |
| Enforcement | Application hook | Harness, gateway, GitHub Action, middleware, or local guardrail |
The useful lesson for APort is product framing: "gate the risky call, allow the
clearly safe call, block the clear violation, and only escalate ambiguous cases"
is the same buyer-friendly story. The implementation should stay OAP-native:
signed decisions, passport capabilities, policy packs, and explicit runtime
disposition instead of probabilistic output as the canonical audit record.