Back to Blog
Close-up of network cabling connected to switch ports, representing controlled outbound egress from an AI agent

AI Agent SSRF: Securing Every Outbound HTTP Tool Call

11 min read

AI agent SSRF starts when a model-controlled URL becomes a server-side request. A link from a prompt, document, OpenAPI description, or tool result may point to a normal website. It may instead resolve to a private service or cloud metadata endpoint. A one-time string check will miss DNS changes between validation and connection, along with public endpoints that redirect to blocked addresses. Route these requests through one outbound fetch broker that owns URL parsing, name resolution, connection policy, redirects, response limits, and audit records. Back the broker with a separate network boundary.

Why AI agents turn URL fetching into SSRF

Server-side request forgery occurs when an application makes a request to a destination selected by an attacker. The OWASP SSRF Prevention Cheat Sheet separates two common cases: applications that call a known set of destinations and applications that must reach arbitrary external destinations. The first can use strict allowlists. The second needs careful address checks plus network controls because a string denylist cannot describe every unsafe target.

An AI agent adds several indirect sources of destination input. The user does not need direct access to an HTTP client parameter. A document can tell the model to fetch a URL. A generated API plan can select an OpenAPI server, and a search result can become the next crawl target. The model can follow its task correctly while carrying attacker-controlled data into a privileged network request.

This is not theoretical framework plumbing. An author report against the reference MCP fetch server described an unconstrained URL reaching cloud metadata and returning IAM credentials when host defenses allowed it. The MCP servers issue is a practitioner report rather than an independent security advisory, but it shows the exact trust transition: an agent argument became an outbound request from a cloud host.

Cloud metadata is one possible target. The same request path can probe loopback services, cluster APIs, admin consoles, databases with HTTP front ends, service discovery endpoints, or another tenant's private service. Returning the fetched content to model context also creates a path for data exfiltration.

Define the outbound request contract

Centralize URL checks instead of repeating them across tool handlers. Every component that fetches a model-influenced URL should call the same broker. This includes crawlers, HTTP tools, webhook follow-up jobs, OpenAPI clients, document importers, and MCP adapters.

The broker needs a narrow request contract:

FetchRequest {
  tenant_id
  workflow_id
  purpose
  method
  url
  permitted_destination_policy
  maximum_redirects
  connect_timeout_ms
  total_timeout_ms
  maximum_response_bytes
  allowed_response_types
  credential_reference
}

The caller supplies intent and limits, not a preapproved address. The broker looks up the named policy from trusted configuration. A prompt cannot switch from public-document-fetch to internal-admin-api, raise the response budget, enable arbitrary methods, or attach a different credential.

Use separate tools when the trust models differ. A public web fetcher should normally accept only GET and HEAD, send no reusable credential, and reach public addresses. An internal API tool should have a fixed service allowlist, a narrow method set, and destination-bound credentials. Combining both into a universal request tool makes policy hard to review and gives prompt injection more room to move.

Validate the URL before DNS

Parse with one standards-aware URL library, then use the parsed object for every later decision. Do not validate one representation and pass the original string to another client. Reject malformed URLs, embedded user information, fragments that the tool does not need, control characters, and schemes outside an explicit allowlist. Public fetch tools normally need only HTTPS. If HTTP is required, make it a separate policy decision.

Normalize the hostname according to the parser's rules, including internationalized names, trailing dots, and IPv6 literals. Apply destination allowlists to normalized hostnames, not substring matches. trusted.example.attacker.test is not a child of trusted.example, and a username that contains a trusted hostname does not make the destination trusted.

Literal IP addresses go through the same address classifier as DNS answers. Reject unspecified, loopback, link-local, private, multicast, reserved, and other non-global ranges unless the selected internal policy explicitly permits a specific destination. Cover both IPv4 and IPv6, including IPv4 values embedded in IPv6 forms. Do not maintain an informal list of familiar strings such as localhost and 169.254.169.254.

For fixed integrations, prefer exact hostname and port allowlists. OWASP recommends an allowlist when the application can identify every legitimate destination. That approach reduces parser and address edge cases, but it does not eliminate DNS validation. A compromised or misconfigured allowlisted domain can still resolve somewhere unexpected.

Resolve once and bind the connection

A safe-looking hostname is not a safe connection target until the broker resolves it. Resolve every address record, classify every answer, and reject the request if any candidate address falls outside policy. Mixed public and private answers should fail closed. Choosing the first public answer leaves behavior dependent on resolver order and retry logic.

The HTTP client must connect to an address that the broker approved. Resolving during validation and resolving again in the client leaves room for a different DNS answer. A current Semantic Kernel issue demonstrates this check-time versus use-time gap and recommends reusing the vetted address or validating the connected peer.

A robust connector should:

  1. Resolve the normalized hostname once through the broker's resolver.
  2. Reject the request unless all answers satisfy the selected policy.
  3. Select an approved address and pass it to a transport that does not perform an independent lookup.
  4. Preserve the original hostname for the TLS Server Name Indication value and certificate verification.
  5. Verify that the connected peer is the approved address when the client exposes that information.

Connecting to the numeric IP while disabling certificate checks is not a fix. The broker still needs normal TLS verification against the original hostname. Otherwise the SSRF control creates a separate man-in-the-middle weakness.

DNS results should have a short bounded lifetime inside one request. Do not turn validation into a long-lived global pin that ignores legitimate DNS changes. Resolve again for a later fetch and repeat the complete policy decision.

Treat every redirect as a new request

Disable automatic redirects in the underlying client. A redirect changes the destination, so the broker must parse, normalize, resolve, classify, and connect again. OWASP explicitly warns that redirect following can bypass input validation.

Set a small redirect limit and record each hop. Reject a change to an unsupported scheme, embedded credentials, a blocked port, or a non-public address. For allowlisted integrations, decide whether redirects may leave the original host. The safe default is no.

Credentials need their own redirect rule. Never forward an Authorization header, cookie, client certificate, or signed request to a different origin. Even when the new origin passes network policy, it was not necessarily the destination for which the credential was issued. Rebuild headers from broker policy at each approved hop instead of copying the previous request.

The final response record should preserve the original URL, each normalized redirect target, approved connection addresses, final status, byte count, content type, and rejection reason. Do not record full credentials, sensitive query parameters, or unrestricted response bodies in the audit event.

Put a second boundary in the network

Application validation can regress. A new HTTP library might ignore the custom resolver. A worker could bypass the broker, or a proxy environment variable could route traffic around the expected connector. Enforce the destination policy again outside the process.

In Kubernetes, start the fetch worker with no allowed egress, then permit only DNS through the intended resolver and traffic through an approved egress proxy. The Kubernetes NetworkPolicy documentation explains that a pod becomes isolated for egress when a matching policy selects it, provided the cluster network plugin enforces NetworkPolicy. A default-deny policy with explicit exceptions is easier to audit than a long list of blocked ranges.

Place the public fetcher in a network segment that cannot route to application databases, control planes, internal services, or tenant networks. An egress proxy can repeat hostname and address policy, resolve names from a controlled point, cap destination ports, and provide connection logs. Do not treat proxy configuration as proof that every client uses it. Block direct egress at the network layer.

Harden cloud metadata independently. AWS documents that its Instance Metadata Service uses the link-local address 169.254.169.254 and that IMDSv2 requires a session token. Require IMDSv2 where available, limit metadata access from workloads, and avoid attaching broad instance roles. These controls reduce impact, but they do not replace the fetch broker because internal services remain reachable targets.

Bound the response and downstream use

SSRF prevention does not end after the connection succeeds. A public server can return an unlimited stream, misleading content type, compressed bomb, or huge redirect chain. Enforce connect, read, and total deadlines. Stop after the configured number of bytes, including after decompression, and allow only response types needed for the tool.

Fetch into an isolated buffer or object store rather than directly into model context. Parse active or complex formats in a restricted worker. Mark fetched text as untrusted content, because a network-safe destination can still host prompt injection. The broker decides where a request may go. It does not decide whether the returned instructions are trustworthy or authorized to select tools.

Keep method support narrow. Public retrieval rarely needs POST, PUT, or DELETE. If an agent must call a state-changing API, use a dedicated integration with a fixed destination, scoped credential, argument validation, and approval rules. Do not extend the generic fetcher until it becomes a universal side-effect tool.

Handle failures without opening a bypass

Fail closed when URL parsing, DNS resolution, peer validation, policy lookup, or egress enforcement is unavailable. Falling back to an ordinary HTTP client bypasses the policy.

Return stable error categories to the workflow, such as destination_not_allowed, resolution_failed, redirect_blocked, response_too_large, or deadline_exceeded. Do not expose private addresses, resolver internals, or network topology to the model. The workflow can ask for another public source or route the task to a human without learning which internal target existed.

Retries must repeat validation. A DNS failure followed by an ordinary client retry can reintroduce the second-lookup problem. Keep retries inside the broker and bind every attempt to a fresh approved resolution. Do not retry policy denials.

Monitor denials by tool, tenant, workflow, destination category, and reason. Alert on repeated metadata attempts, private-address targets, redirect blocks, parser failures, and direct-egress violations. These events can reveal prompt injection or a broken integration, but a single denied request is not proof of an attack.

Verify the whole destination path

A unit test for is_private_ip covers only the address classifier. Exercise the real resolver, connector, redirect loop, proxy, and network policy. Use a controlled test service that can return DNS changes and redirects without touching production private services.

At minimum, verify these cases:

  • Decimal, hexadecimal, shortened, IPv6, and IPv4-mapped address forms do not bypass parsing and classification.
  • A hostname with public and private answers is rejected.
  • DNS returns a public address during validation and a private address on a second lookup, but the connector never performs that second lookup.
  • A public URL redirects to loopback, link-local metadata, a private subnet, or an unapproved port.
  • A cross-origin redirect receives no credential from the original request.
  • The response exceeds the compressed or expanded byte budget.
  • The resolver, policy service, or egress proxy is unavailable.
  • A tool tries to bypass the broker with a direct socket, and network policy blocks it.
  • A fetched page contains tool-like instructions, but downstream code treats them as untrusted text.
  • Logs preserve the decision trail without credentials or sensitive response bodies.

Run the tests from the same workload identity and network segment as the real agent. A laptop test cannot prove that a cluster policy blocks metadata or internal service routes.

Start with one HTTP tool

Choose the agent tool with the broadest destination input. Route it through the broker, disable client redirects, pin the approved connection address, and put its worker behind default-deny egress. Then run a public fetch, a private-address attempt, a DNS-rebinding fixture, a redirect-to-private fixture, and an oversized response.

AI agent SSRF is contained only when the application and network enforce the same destination decision. A successful URL validation call proves little on its own. Verify that every connection used an approved address, each redirect received a new decision, responses stayed within budget, and alternate clients could not reach a private target. Establish that result for one tool before migrating the rest.

References

  1. OWASP SSRF Prevention Cheat Sheet supports strict URL and address validation, destination allowlists, DNS checks, redirect restrictions, and network-layer defenses.
  2. AWS EC2 Instance Metadata Service supports the documented metadata addresses and the IMDSv2 session-token control.
  3. Kubernetes Network Policies supports pod egress isolation and default-deny policy design when the network plugin enforces the resource.
  4. MCP servers issue 4143 supplies an author-reported example of an agent-controlled fetch reaching cloud metadata and returning IAM credentials when host configuration permitted it.
  5. Semantic Kernel issue 14312 supplies a reproducible author report of the DNS validation and connection gap that address pinning must close.

About Fire In Belly: Independent senior engineering from Tallinn, Estonia. We design and build AI workflow automation, internal tools, and custom software with the outbound fetch controls described above, at published fixed prices. Schedule a call to discuss your next project.