HTTP redirect validation for authenticated agent requests
HTTP redirect validation keeps agent API credentials from crossing unapproved destinations, unsafe methods, DNS tricks, and hidden SSRF paths.

HTTP redirect validation must happen before an agent forwards credentials to the next URL. A redirect is not a harmless continuation of one request. It is an instruction from a remote server to make another request, often to a different authority, with a different method and a different network destination.
That distinction gets lost when people wire an agent to an API client and leave the client's default redirect setting switched on. The agent submits a request to an approved API. The API returns 302 Location: https://somewhere-else/.... The client follows it. If authentication gets added too early, an attacker who controls either the first endpoint, a redirect parameter, or a compromised dependency can turn a permitted call into a credential delivery service.
I have seen this dismissed because mature HTTP libraries often remove Authorization on a host change. That protection helps, but it is neither universal nor enough. It says nothing about custom credential headers, signed query parameters, cookies, redirected request bodies, DNS changes, or same-host redirects into a dangerous endpoint. Build an explicit redirect decision instead of treating library defaults as your policy.
Redirects create a new authorization decision
Each redirect hop needs the same scrutiny as the first URL because the server chooses the next destination. The client requested one resource and received a response that proposes another URI in the Location field. The original allow decision does not automatically cover the proposed URI.
RFC 9110 defines redirects by their status codes and tells user agents how they may or should react. It does not say that an API credential attached to the initial request is authorized for every URI a server might return. That gap belongs to the caller. For an agent gateway, the caller is responsible for deciding which downstream actions it will perform.
Keep these two events separate in code and in your logs:
- The server sends a redirect response for a request already made.
- The client elects to issue a new request to the resolved target.
The first event is a fact. The second is a privileged action. If your implementation combines them inside a convenience call such as client.Do(request) with automatic redirects enabled, you have hidden the moment when policy must run.
A redirect can move across several boundaries at once. https://api.example.test/v1/export might point to https://downloads.example.test/file, which then points to a time-limited object URL on a storage provider. That chain may be legitimate. It also means that a loose rule like "the first host is approved" says almost nothing about the final connection.
Relative redirects need the same care. Resolve Location: ../admin against the URL that produced it using a standards-compliant URL parser. Do not concatenate strings. A path beginning with // is scheme-relative and can change the host. Encoded delimiters and unusual forms such as an empty Location value have produced enough client inconsistencies that a gateway should reject ambiguity rather than guess.
Inject credentials only after the destination passes
The safe ordering is simple: receive the response, parse the candidate destination, validate it, construct the next request, then inject only the credentials approved for that destination. Credential injection belongs at the final dispatch point, not in a mutable request object that survives a redirect chain.
This is the design error that causes most leaks. A wrapper builds a request with Authorization: Bearer ..., sends it, and lets the HTTP client clone or replay the request after a redirect. The wrapper may intend the token for a single host, but it no longer controls each send.
Use a request description that contains no secret material. It can carry the method, body, permitted credential reference, and initial URL. For each hop, create a new wire request after validation. The dispatch layer looks up the credential only when it has a permitted target.
A compact model looks like this:
request intent:
method: POST
initial URL: https://api.acme.test/v1/reports
credential reference: billing-api-prod
body digest: sha256:...
for each response:
if status is not a supported redirect: return response
target = resolve(response.request_url, response.headers["Location"])
decision = validate(target, request intent, response.status)
if decision is reject: record rejection and stop
next_request = build(decision.method, target, permitted body)
inject(credential reference, target, next_request)
send(next_request)
The important detail is not the pseudocode. It is the lifetime of the secret. The token exists only in the request that has already passed destination validation. It never sits in a generic object that a redirect callback can forward by accident.
This ordering also makes credential scope enforceable. A bearer token for api.acme.test should not automatically authenticate uploads.acme.test, even if both names belong to one company. Give each destination rule an explicit credential reference. If two services intentionally share a credential, document that relationship in the rule rather than inferring it from a DNS suffix.
Origin matching prevents one leak but misses others
A strict origin match blocks obvious host changes, but it does not settle whether a redirected request is safe. An origin consists of scheme, host, and port. Treat changes to any of those fields as a boundary unless you have an explicit exception.
The scheme matters. A redirect from HTTPS to HTTP can expose headers or request contents on the network. Reject downgrades for authenticated traffic. A redirect from HTTP to HTTPS may sound safe, yet it still changes the authority and can disguise an unexpected endpoint. Validate it normally.
The port matters too. https://api.example.test and https://api.example.test:8443 are different origins. Developers often forget this because browsers hide default ports and local test environments use alternate ones constantly. A token intended for the public API may end up on an administration service if your allow rule only checks the hostname.
The path matters when an API host serves unrelated applications. A redirect from /v1/files to /internal/debug/export stays on the same origin, but it may expose a body or cause an unsafe state change. Do not use a path allowlist as a substitute for application authorization, but do limit redirect targets to the API prefixes your integration actually needs.
Query parameters deserve special handling. A redirect target can carry a pre-signed URL, state parameter, or one-time download token. These values are credentials in practice even when they do not use an Authorization header. Do not copy query parameters from the old request into the new URL. Use exactly the parsed redirect target, after applying any rule that forbids userinfo fields, fragments, or credential-looking parameters.
A practical baseline is to permit same-origin redirects only if the scheme remains HTTPS, the port remains approved, the target path lies within the integration's allowed API surface, and the redirect status permits the intended method. Treat cross-origin redirects as a separate class with named destination rules.
Redirect status codes change the request you might send
Redirect status codes carry method semantics, and a client that ignores them can turn a safe read into an unexpected write or replay a sensitive body. Do not reduce every 3xx response to "follow Location."
RFC 9110 says that 307 and 308 preserve the request method and content. If the original request was a POST containing a report definition, a 307 or 308 asks the client to send that same POST and body to the new target. This is the highest-risk redirect case because the new host may receive both business data and a request with side effects.
Status 303 directs the client to retrieve a representation using GET or HEAD, regardless of the original method. This usually appears after a form-style submission. For an agent action, permit it only if the redirected GET target is separately approved and your integration expects that pattern. A 303 should not inherit an authorization header merely because the initial POST had one.
The historical troublemakers are 301 and 302. RFC 9110 describes the long-standing practice of changing POST to GET for these responses, while 307 and 308 exist for callers that must preserve methods. Libraries vary in details, particularly around non-POST methods and bodies. Never let this ambiguity decide what an autonomous client sends.
Write the behavior down. For example:
- Follow 301 and 302 only for
GETandHEADrequests. - Follow 303 only by issuing a credential-free
GETorHEAD, then validate that target again before adding any approved read credential. - Follow 307 and 308 only when the destination rule explicitly permits the original method and body class.
- Reject a redirect after a non-idempotent method such as
POST,PATCH, orDELETEunless the integration has a documented redirect flow.
This may break a poorly designed API. That is preferable to silently replaying a payment instruction, source upload, or administrative command somewhere a remote server selected. If a vendor requires redirected writes, add a narrowly scoped exception and test the exact sequence.
Destination rules need fields, not a hostname string
A useful redirect validator evaluates a target against a rule with enough detail to describe the service you intended to call. A bare hostname allowlist is attractive because it is short. It is also where exceptions accumulate until nobody can explain what an agent may send where.
For every permitted redirect destination, decide the following:
- Which schemes are allowed, normally HTTPS only.
- Which normalized hostnames and ports are allowed.
- Which methods and path prefixes the redirected request may use.
- Which credential reference, if any, may be injected there.
- Whether the target may resolve to private or local network addresses.
Normalize before matching. Lowercase DNS hostnames, remove a trailing dot consistently, parse bracketed IPv6 literals correctly, and reject malformed percent encoding. Never compare raw URL strings. https://[email protected]/ has evil.test as its host, despite containing a trusted name before the @ sign.
Do not use endsWith("example.test") as an authority check. It accepts notexample.test. Even a delimiter-aware suffix rule such as *.example.test needs ownership review. Wildcard subdomains often include preview systems, customer-controlled names, redirectors, or infrastructure managed by another group.
The validator should return a reasoned decision rather than a boolean. Operators need to know whether it rejected a scheme downgrade, an unapproved port, a disallowed method, a private address, or an exhausted redirect limit. This information belongs in the action record, with token values removed.
Set a small, fixed redirect limit. A limit stops loops and makes a long chain visible. Do not accept a count supplied by the agent. The gateway owns this limit because it owns the network actions.
A familiar download flow can leak a privileged request
The dangerous cases rarely arrive labeled as attacks. They often look like ordinary convenience endpoints that accept a URL or return a download location.
Consider an agent asked to fetch a report from an approved billing API. The agent sends POST /v1/exports with a bearer token. The API returns a 303 to a download URL. The gateway follows it automatically and carries the bearer token because its custom header injector runs before the HTTP library's redirect hook.
At first, the download host is another service run by the same team. Months later, a configuration change makes the export endpoint accept a destination parameter so customers can use a storage provider. An attacker with access to report parameters supplies a URL they control. The approved API returns a redirect to that URL. The HTTP library treats it as a normal 303, and the gateway has already attached X-Service-Token.
Nothing exotic happened. The first API request was allowed, the redirect syntax was valid, and the token never appeared in an agent prompt. The failure came from assigning credentials before the target was known.
A correct implementation records the 303, resolves the target, recognizes that the host lacks a destination rule, and stops. The operator sees a rejected redirect rather than a mysterious outbound call. If the intended download service needs access, create a separate rule for its exact host and use a download credential that cannot call the billing API.
Agents add another route to this failure. An agent may be able to influence a URL through issue text, a repository configuration file, an API response field, or an instruction from a tool. Do not assume that a URL originated from a developer just because it appears inside a request your agent created. Data can become routing input very quickly.
DNS checks must happen at connection time
Hostname validation alone does not stop server-side request forgery. A hostname can resolve to a public address during review and to loopback, link-local, or private space when the client connects. Redirect handling gives an attacker repeated opportunities to exploit that gap.
Resolve each approved hostname near connection time and evaluate every returned address against your network rules. Reject loopback ranges, unspecified addresses, link-local ranges, private ranges, multicast addresses, and IPv6 equivalents unless a rule explicitly permits them. Apply the same treatment to literal IP addresses in redirect URLs.
Do not resolve a hostname once, approve one address, then let a separate HTTP stack resolve it again. That creates a time-of-check and time-of-use gap. The connection must use the address set you evaluated, or the transport must provide a trustworthy way to confirm the peer address and reject a mismatch. This is harder than it sounds when connection pools, proxies, and dual-stack DNS enter the picture.
Corporate proxy deployments need a stated exception model. If the gateway connects only to an explicit proxy, validate the proxy as the network peer and retain the application destination rules before forming the proxy request. Do not declare all private ranges safe just because an organization uses private service addresses. Name the internal hosts and ports that an agent needs.
DNS rebinding is one reason a redirect rule cannot be a permanent approval of a hostname. Cache behavior, TTLs, and resolver differences can change the answer. Make the decision for the connection you are about to make, and log the selected peer address without treating it as proof that the application destination was authorized.
Cookies, signed URLs, and bodies are credentials too
Teams often protect Authorization and leave the other outbound material untouched. A redirect can leak credentials or sensitive data through several other channels.
Cookies have domain and path rules, but an agent gateway should not rely on a general browser-style cookie jar for machine credentials. Keep cookies disabled by default for privileged calls. If an integration needs them, scope the jar to that integration and re-evaluate cookie attachment for every redirect target.
Pre-signed URLs intentionally put authorization in the query string. They should usually work without adding any other service token. If a destination rule recognizes a pre-signed storage URL, dispatch it with exactly the URL's existing authorization material and a restricted method, usually GET or PUT. Do not append your standard headers out of habit.
Request bodies carry secrets too. A JSON body may contain a source archive, customer data, or an opaque signed assertion. For 307 and 308, require an explicit rule that permits body replay to that precise service. For other redirect codes, do not silently transform or resend the body. A client library's convenience is not a reason to duplicate sensitive content.
HTTP Referer can disclose paths and query values when a client adds it. API clients should generally avoid carrying a Referer header across redirects. Likewise, strip headers that describe the original internal route, agent session, or user identity unless the destination rule explicitly needs them.
Classify headers into three groups: headers that can always be regenerated, headers allowed only for a named destination, and headers that must never cross a redirect. This is more reliable than maintaining a vague "sensitive headers" list. Content-Type may be harmless for a permitted upload; X-Internal-Actor may identify a human who never authorized the next host.
Audit the chain as one action with many hops
An audit record should show the redirect chain without exposing the material that made the request privileged. A final URL and status code are not enough. They hide whether the client crossed an origin, changed method, dropped authentication, or rejected a suspicious target before connecting.
Record the initial request intent, each response status, each raw Location value after redaction, the resolved target, the policy result, the selected method, and the credential class used for that hop. Record a stable credential identifier, never a token, password, or full signed URL. Hashing a full URL can help correlation, but do not mistake a hash for safe redaction when the input has low entropy.
Tie all hops to one parent action identifier. Then an investigator can distinguish "the agent requested an export" from "the gateway made four network calls while completing the export." This also lets an operator revoke a live session when the chain begins to behave unexpectedly.
A tamper-evident log adds useful evidence after the fact, but it does not stop an unsafe redirect. Prevention belongs in the forwarding path. Sallyport records agent sessions and individual calls from one encrypted, hash-chained audit log, so redirect-aware action records can retain the decision trail rather than flattening the chain into a final result.
Test the log with deliberately rejected cases. Trigger a cross-origin 302, an HTTPS-to-HTTP redirect, a 307 after a POST, a malformed Location field, and a target that resolves to loopback. If those records do not tell a reviewer why the gateway stopped, improve the event schema before an incident forces the issue.
Make redirect behavior part of the tool contract
A tool that calls an HTTP API needs to tell its users whether it follows redirects and under what terms. "Uses standard HTTP" is not a contract. The behavior varies across libraries and changes when someone replaces a client, adds a proxy, or moves authentication into middleware.
Write tests against a local redirect fixture with distinct endpoints. The fixture should return controlled status codes and Locations, then capture the incoming method, body digest, host, and headers. Your assertions should prove that an unapproved target receives no credential header, that a 303 does not replay a POST body, and that a permitted 307 sends only the headers and body the rule allows.
Do not let the agent select a redirect policy in its tool arguments. An agent can request a known integration and provide ordinary request parameters. The gateway decides whether redirect following exists for that integration, how many hops it permits, and which credentials it may use. That separation prevents a prompt injection from becoming follow_redirects=true.
For a first implementation, choose a deliberately narrow contract: follow approved HTTPS redirects for GET and HEAD only, inject credentials per destination after validation, and reject every redirected write. Add exceptions only after you can explain the vendor flow, the destination boundary, the method behavior, and the audit record. A few explicit rejections will annoy developers. A token sent to an attacker will annoy everyone much more.
FAQ
Should an HTTP client automatically follow redirects when using API credentials?
No. A redirect response asks the client to make another request to a new URL. Treat that next request as a fresh authorization decision, especially if it would carry a bearer token, client credential, custom authentication header, or SSH-related bootstrap data.
Is a same-origin redirect always safe to follow?
A same-origin redirect keeps the scheme, hostname, and port unchanged. It still deserves validation because the method, path, query string, destination IP, and redirect count can change. Same-origin is a useful condition, not a complete security policy.
Do HTTP libraries remove Authorization on cross-domain redirects?
Many HTTP libraries remove the standard Authorization header when the host changes, but you cannot treat that behavior as a security boundary. Custom headers, cookies, signed URLs, request bodies, and credentials injected below the library layer can still cross the boundary unless your forwarding code blocks them.
What should happen when a redirect target is not approved?
Reject the redirect if the target does not meet your destination rules. Record the original URL, status code, Location value, parsed target, and rejection reason so an operator can determine whether the redirect was accidental, malicious, or a service configuration error.
Which HTTP redirect status codes are safe for POST requests?
No. Status 301, 302, 303, 307, and 308 have different method semantics, and clients have historical compatibility behavior around POST requests. A credentialed client should define its own method rules instead of relying on whatever its HTTP library happens to do.
Should agents be allowed to follow redirects to private IP addresses?
Usually, no. Redirects that lead to private addresses, loopback, link-local ranges, or Unix-like local services create SSRF paths. Resolve and evaluate each destination connection, then apply explicit exceptions only for infrastructure you intentionally operate.
How specific should an API redirect allowlist be?
An allowlist should identify the exact service boundary: scheme, hostname, port, and often a path prefix. A suffix match such as *.example.com can be too broad when unrelated teams, customer-controlled subdomains, or redirect services live under that domain.
Can a user approval make an unsafe redirect acceptable?
No. An approval tells you that a person accepted an action at one moment; it does not make an uninspected later destination safe. Show the final or redirected destination when possible, and force a new approval when a redirect crosses a meaningful boundary.
What should an audit log record for HTTP redirects?
A trace should preserve every hop in order: request URL, response status, Location header, resolved target, method used, credential class, and final result. Redact secret values, but retain a stable credential reference so investigators can tell which authorization path was requested.
What redirect checks should an AI agent gateway perform?
Reject malformed Location values, unsupported schemes, excessive hops, credential-bearing URLs, and targets outside the approved destination set. Do this before injecting credentials or opening a connection to the next host.