URL parse gate
Reject missing schemes, userinfo surprises, encoded host tricks, non-HTTP schemes, overlong inputs, and ambiguous normalization before DNS resolution.
Fetch SSRF checklist
A URL tool can reach whatever the MCP server can reach. If that server runs in a cloud, CI, laptop, VPC, or cluster, open fetch becomes a credential and internal-network boundary. The safe default is to deny dangerous targets before the request leaves the runtime.
Fast answer
MCP Route Review fit check
If the question is no longer “should fetch be allowed?” but “can this agent safely call this fetch route again?”, send the route to MCP Route Review with one allowed public URL fixture, one closest denied neighbor, the credential and budget owners, and the receipt or typed-denial fields you expect to preserve.
The production checklist
Reject missing schemes, userinfo surprises, encoded host tricks, non-HTTP schemes, overlong inputs, and ambiguous normalization before DNS resolution.
Resolve the hostname at request time, classify every A/AAAA result, and deny link-local, loopback, private, carrier-grade NAT, multicast, IPv6 ULA, and service-network addresses by default.
Apply the same host and IP policy after every redirect. A safe first URL cannot redirect into metadata, loopback, or private infrastructure.
Record which server, cloud role, proxy, token, cookie jar, or provider credential would be exposed if the request were allowed.
If internal access is intentional, require a named route card with target host/CIDR, caller, tenant, purpose, review owner, credential lane, and quota owner.
Treat Docker or loopback access as an explicit deployment-mode assertion, not a URL property. Match only a tiny pre-DNS local host-token set and keep the normal SSRF policy on redirects.
Return a policy denial with raw URL, normalized host, resolved IP class, rule id, blocked credential lane, and recovery hint instead of a generic network failure.
Denied neighbors
Examples: 169.254.169.254, metadata.google.internal, instance-data, IMDS-style aliases
Expected: Deny before request; receipt names metadata/link-local policy and credential lane protected.
Examples: 127.0.0.1, ::1, localhost, decimal/hex/octal host encodings
Expected: Deny before request; receipt shows normalized host and loopback classification.
Examples: 10.0.0.0/8, 172.16/12, 192.168/16, fd00::/8, Kubernetes service ranges
Expected: Deny unless a specific internal route card authorizes that target for the caller and tenant.
Examples: Public URL returning 30x to metadata, loopback, or RFC1918 address
Expected: Re-run DNS/IP policy on every hop and deny with redirect hop preserved in trace.
Examples: host.docker.internal, localhost, 127.0.0.1, ::1 in local MCP development
Expected: Deny by default; allow only when local/dev mode is asserted by deployment config and the pre-DNS host token is in the known-local set.
Trace evidence
Fetch SSRF protection is only operator-grade if the denial is reconstructable. Store enough evidence to show the target was classified and blocked before any credential, proxy, cookie, or cloud role was exposed.
Internal exception card
Some agents legitimately need to reach internal services. That should never be granted by weakening public fetch policy. Give the internal lane its own route card, review owner, target scope, credential lane, and expiration.
Internal target / CIDR:
Caller / tenant / workspace allowed:
Business purpose:
Credential lane exposed:
Quota owner / retry ceiling:
Review owner:
Allowed methods and response size:
Forbidden neighboring targets:
Receipt fields:
Expiration / re-review date: Local development exception
Docker and local MCP workflows often need `host.docker.internal` or loopback during development. Do not turn that into a broad private-network allow-list. Require an explicit local mode flag, match the pre-DNS host token from a tiny known-local set, and re-run policy on every redirect hop.
In shared deployments, even loopback is part of the attack surface: the agent is reaching the Letta/container host, not the developer's laptop. Keep localhost, 127.0.0.1, ::1, and host.docker.internal separate unless single-tenant deployment proof says otherwise.
Deployment mode flag:
Known-local host tokens:
Allowed port / scheme:
Single-tenant or shared deployment:
Redirect policy per hop:
Denied metadata / private / IPv6 controls:
Typed denial fields:
Positive fixture:
Negative fixtures: Common misreads
Related operator guides
Bring one fetch route with an allowed public URL, a denied metadata or private-network neighbor, credential lane, budget owner, and typed receipt fields.
Use the same denied-neighbor discipline for filesystem, repo, workspace, and local-resource tools.
Use a route card to bind caller, authority surface, credential lane, denied neighbor, and receipt before a fetch route repeats.
Scope, principals, and evidence are the security model when fetch becomes network authority.
Run fetch SSRF as a readiness gate beside tenant isolation, tool scope, quota, and recovery checks.