Server-side request forgery has been in the OWASP Top 10 for years and is well understood: an attacker gets a server to make an HTTP request on their behalf, reaching internal resources the attacker cannot reach directly. The defenses are known.
Agents reintroduce it in a form those defenses were not built for. A conventional SSRF requires finding an endpoint that takes a URL parameter and coaxing it past validation. An agent with a web-fetch tool takes URLs by design, chooses them at runtime, and can be influenced through any content that reaches its context. The attack surface is not a parameter you can audit; it is a decision the model makes.
What Makes the Agent Case Different
The URL is chosen by the model. There is no fixed set of URLs to validate against a business rule. The tool exists to fetch arbitrary things, which is why it is useful.
Influence is indirect and cheap. The attacker does not need to supply the URL. A document in a RAG corpus, a web page the agent is asked to summarize, an email in the inbox it processes — any of these can contain a URL along with a plausible reason to fetch it. See threat model: indirect prompt injection.
The response comes back into context. Classic SSRF often gives the attacker only blind reachability. An agent typically returns the fetched content into its own context and then into its output, turning SSRF into a direct read primitive. If the agent summarizes what it fetched, the attacker gets the contents of internal services in natural language.
Agent runtimes sit in privileged network positions. Agents are deployed inside VPCs, often with service-mesh membership and instance credentials, precisely because they need to reach internal systems. That is the worst possible location for an unconstrained fetch capability.
The Target Set
Cloud instance metadata. 169.254.169.254 and its equivalents. On misconfigured instances this returns credentials with the workload's full IAM authority. IMDSv2's token requirement helps considerably, but an agent that can issue arbitrary requests including headers can perform the token handshake. Enforce IMDSv2 and block metadata egress at the network layer; do not rely on either alone.
Localhost services. Debug endpoints, admin interfaces, sidecar proxies, unauthenticated Redis or Elasticsearch, Kubernetes kubelet ports. Services that skip authentication because "only local processes can reach it" — and the agent is now a local process that takes instructions from strangers.
Internal HTTP APIs. Anything on a private range that authenticates by network position rather than credential. The agent's requests originate from a trusted network position.
Non-HTTP schemes. file:// reads local files, gopher:// can forge arbitrary TCP payloads against internal services, ftp://, dict://, and others expand reach depending on the HTTP client. Restrict schemes to https — and, if unavoidable, http — and reject everything else at parse time.
Out-of-band exfiltration. The reverse direction: the agent fetches an attacker-controlled URL with sensitive data in the path or query string. No response needed; the request itself is the leak. This is the same channel as rendered-image exfiltration, and it means egress control matters even when the agent never returns the response. See data exfiltration risks in agentic AI.
Why Denylists Fail
Blocking private ranges by string matching or single-pass validation loses to a long list of bypasses:
- Alternate encodings. Decimal (
2130706433), octal, hex, mixed forms — all resolve to127.0.0.1. - IPv6 forms.
[::1],[::ffff:127.0.0.1], unique-localfc00::/7, link-localfe80::/10. - DNS resolution to private addresses. An attacker-controlled domain with an A record pointing at
10.0.0.5. The hostname passes any lexical check. - DNS rebinding. The name resolves to a public address during validation and to a private one when the connection is made. A time-of-check-to-time-of-use race, and the reason validation must happen after resolution.
- Redirects. A permitted URL returns
302tohttp://169.254.169.254/. Each hop needs revalidation. - Credential and fragment tricks in the authority component.
https://allowed.example.com@evil.tld/and similar parser divergences. Different libraries disagree about which part is the host, which is why hand-rolled parsing is a liability.
Every one of these has been used against production systems. A denylist has to be right about all of them simultaneously and forever.
The Controls That Hold
Egress allow-list at the network layer. The strongest control and the one to build first: the agent's runtime can reach an enumerated set of destinations and nothing else, enforced by a proxy, security group, or egress gateway rather than by application code. Default deny. Every encoding trick above becomes irrelevant because the packet does not leave. See tool use safety and sandboxing agent actions.
All outbound traffic through a forward proxy. Give the agent no direct route to the internet. The proxy performs URL validation, scheme restriction, host allow-listing, redirect revalidation, response size limits, and logging. This puts enforcement outside the process that the model influences, which is the same argument for putting every other agent control in the proxy.
Resolve, validate, then connect to the validated address. Resolve the hostname, check every returned address against the permitted set, then connect to that specific address rather than re-resolving from the name. This closes the rebinding race. Many HTTP clients support supplying a resolved address with an explicit SNI hostname; use it.
Revalidate on every redirect, and cap redirect depth. Or disable redirect following entirely and let the agent decide explicitly whether to follow, which routes the new URL back through the same validation.
Block link-local and metadata ranges unconditionally at the network layer. 169.254.0.0/16 and fd00:ec2::254. There is no legitimate agent use case, and this is a one-line rule with outsized value.
Bound response size and time. Caps prevent an agent from being used to pull large internal datasets and prevent slow-loris-style resource consumption.
Scope the fetch tool per agent. Most agents do not need arbitrary web fetch. Give the ones that do a domain allow-list appropriate to their task, and remove the tool from the rest — the functionality-reduction argument from excessive agency.
Detection
Useful signals, in rough order of value:
- Any request to a metadata or link-local address. This should page, not merely log.
- Requests to private ranges from an agent whose allow-list contains none.
- Requests to domains never previously seen for that agent.
- Redirect chains terminating at internal addresses.
- Fetch volume or unique-host count spiking for one agent.
- Response bodies that trigger secret-pattern guardrails on the way back — a strong indicator that something internal was reached.
That last one is worth wiring deliberately: applying secret and credential detection to inbound tool responses catches metadata-credential retrieval at the point where it would otherwise enter the context window.
Common questions
Can I let the model validate URLs before fetching?
No. The model is the component being manipulated; asking it to police its own decisions puts the control inside the compromised trust boundary. Model-side checks are worth having as a first filter for obvious cases, but the authoritative decision must be made by the proxy, which is not subject to the injected content.
Our agent genuinely needs to browse arbitrary web pages. What then?
Run it with no route to internal networks at all — a separate network segment or a hardened browsing service whose only egress is the public internet through a proxy, with metadata and private ranges blocked at the network layer. Then treat everything it returns as untrusted input subject to injection scanning, and give that agent no credentials to internal systems. Arbitrary browsing and internal reachability should never coexist in one runtime. See securing browser-use agents.
Does a service mesh with mTLS solve this?
It helps: if every internal service requires a valid workload certificate, an SSRF that reaches a service without one fails. But the agent's own runtime usually has a valid certificate, so a request it originates authenticates fine. Mesh authorization policy — which workloads may call which services — is the part that matters, and it has to be restrictive for the agent's identity specifically. Mesh membership without per-workload policy converts SSRF into authenticated SSRF.
How do I handle webhooks and callbacks the agent configures?
Same problem, delayed. A callback URL the agent supplies is fetched later by your infrastructure, often with different network placement and fewer controls. Validate callback URLs at registration against the same allow-list, re-validate at delivery time, and sign outbound deliveries so a recipient cannot be confused about origin. See webhook security: signing and verifying events.
How Praesidia approaches egress control
Praesidia mediates agent traffic through governed connections, so an agent reaches external and internal destinations through a policy-evaluated proxy rather than by opening its own sockets. Each connection enumerates its permitted destination, which makes destination allow-listing the default shape of a grant rather than an extra configuration step, and guardrails evaluate both directions — so credentials or secrets appearing in an inbound tool response are caught before they enter the context.
Every call records the agent, the connection, the destination, and the policy outcome, so first-seen-destination and private-range signals are available without building a separate egress pipeline. For related controls see governed agent resource connections and the Praesidia documentation.