Last reviewed: 2026-10-20

Direct answer

Prevent an open redirect in a coding-agent web patch by making the destination an application-owned decision instead of copying a request parameter into a Location header or browser navigation API. Prefer a fixed route or a short identifier that maps to a code-owned path. If a feature must accept a return URL, parse it with one standards-based URL implementation and accept only a relative path that cannot become an absolute, scheme-relative, or cross-origin URL. For an external destination, use an explicit allowlist of origins and construct the final URL from trusted components. Reject ambiguous or invalid values before any response, cookie operation, or state change.

The security question is not whether the URL string looks familiar. It is whether the patch can prove where a user will be sent after the server or browser consumes it. CWE-601 describes the weakness as redirecting a user to an untrusted site when the application does not sufficiently verify the target. The OWASP Unvalidated Redirects and Forwards Cheat Sheet recommends avoiding user-controlled redirects, using server-side mappings, and validating destinations when a redirect is required. The HTTP Location header is the browser-facing control surface: a 3xx response tells the user agent where to go, so the value deserves an authorization decision.

A redirect can be abused for phishing, token leakage through an unintended navigation, or bypassing a product’s trust boundary. It can also be a smaller part of a larger chain: a login callback, link shortener, invitation flow, or error handler may redirect after a state-changing request. Review the entire path from input to final navigation, including framework helpers and client-side fallback code.

Who this is for

This guide is for maintainers reviewing agent-written controllers, login callbacks, logout handlers, payment-return pages, link-preview routes, invitation links, and single-page application navigation. Start when a patch adds a next, returnTo, redirect, continue, or url parameter; reads a Location header; calls window.location, location.assign, or a router navigation method; or changes a reverse proxy’s redirect behavior.

Treat query strings, form fields, cookies, repository files, model output, issue text, webhook fields, and upstream response headers as untrusted until a destination policy says otherwise. A value that came from your own database may still be stale, imported, or written under an older policy. Authentication does not make a destination safe, and a valid URL does not make it an approved destination.

Pair this review with the site’s CSRF review workflow when a redirect follows a browser state change. Use the SSRF URL-fetcher guide when the same URL is also fetched server-side; navigation policy and network-fetch policy are separate decisions.

Key takeaways

  • Prefer a fixed post-action route or an opaque destination identifier mapped on the server. Do not let a request parameter select an arbitrary absolute URL.
  • Decide whether the feature permits same-origin paths, a closed set of external origins, or no caller-selected destination. Record the rule before reading the implementation.
  • Parse once with a standards-based URL parser. Compare normalized components, not substrings or a few rejected prefixes.
  • A path beginning with // is scheme-relative and can change the host. A backslash, encoded delimiter, user-info field, unusual scheme, or parser disagreement can also defeat a superficial check.
  • When external redirects are required, allowlist exact origins and construct the path and query from validated components. Do not allow an arbitrary host merely because it ends with a trusted-looking suffix.
  • Validate before setting Location, returning a client navigation instruction, or performing a state change whose outcome depends on the redirect.
  • Test the final browser-visible behavior, including login, error, logout, and retry paths. A unit test of a helper is not evidence that every route uses it.
  • Keep redirect failures controlled and side-effect free. Log a stable policy code and destination class, not the complete URL or query string.

Sources checked

Four primary public references were fetched live on 2026-10-20. OWASP provides redirect-specific defensive patterns and warns about user-controlled destinations. MITRE CWE-601 defines the weakness and its phishing and trust-boundary consequences. MDN documents how the Location response header is interpreted for 3xx and 201 responses. Node.js documents the WHATWG URL parser and normalized component properties used in a safe implementation. These sources support an evergreen engineering review; they do not report a new incident or prove a particular patch safe.

Contract details to verify

Write the redirect contract before approving the agent’s implementation. A reviewer should be able to answer what the destination means, who may choose it, what origins are trusted, and what happens when validation fails.

Contract fieldDecision to recordEvidence to retain
TriggerWhich route, method, or client event causes navigation?Route map and browser or HTTP trace
Input originWhich request, cookie, stored record, or upstream field supplies the destination?Data-flow note covering every source
Destination classFixed path, same-origin path, exact external origin, or no redirectVersioned policy identifier and mapping table
ParserWhich standards-based parser handles the value?Runtime and library version plus parser tests
ConstructionHow are path, query, fragment, and origin assembled?Code-owned template or allowlist mapping
FailureWhat status, body, and side effects occur on rejection?Integration assertions and stable error code
Credential boundaryWhich cookies, tokens, or headers are present before and after navigation?Sanitized browser trace and proxy rules

Prefer mappings over caller-supplied URLs

The safest return target is not a URL. It is a short value such as dashboard, orders, or settings, mapped by the server to a fixed relative path. The mapping is code-owned, easy to review, and independent of URL parser edge cases. Reject unknown identifiers; do not silently treat them as arbitrary paths or substitute a caller-provided fallback.

When a route needs a record-specific destination, pass an opaque identifier and look up the destination under the authenticated user’s scope. A stored target should be validated when it is written and again when it is consumed, because policies, domains, and route meanings change. Keep the identifier separate from credentials, and do not encode a complete destination in a signed token unless the signer and verifier agree on the exact origin and expiry policy.

Restrict same-origin return paths

If a product must accept a return path, define it as an absolute-path reference under the current origin. In Node.js’s WHATWG URL API , the parser exposes protocol, origin, hostname, port, username, password, pathname, and related components; its documentation is the basis for using one parser for both validation and construction. Reject values that fail parsing, contain credentials, or resolve to a different origin. If fragments are not part of the product contract, reject them too.

A simple policy is: the input must be a string under a documented length limit, begin with exactly one /, not begin with //, contain no backslash, and resolve against the current trusted origin to the same origin. Use the parser to confirm the result; the character checks are a narrow precondition, not a replacement for parsing. Reject control characters and parser exceptions before constructing the response. Do not decode a value, validate it, then hand a differently decoded value to another navigation library.

Be cautious with query parameters. A path such as /complete?next=external-destination is still same-origin at the first hop, but a later client script may treat next as a redirect instruction. Validate nested destinations at the point where they become navigation authority, or remove the nested parameter from the contract. Do not assume that a safe outer path makes every embedded value safe.

Allow external origins explicitly

Some federated-login and partner workflows need an external redirect. Model that as a closed set of exact origins, with a separate policy for each destination. Compare parsed protocol, hostname, and effective port; do not accept a suffix such as trusted.example.com.evil.test, and do not use a substring search for the word trusted. Internationalized hostnames and default ports must be normalized consistently by the same parser used to build the final URL.

Keep the destination path and query constrained as well. An origin allowlist does not authorize every path, credential-bearing URL, or open redirect on that origin. Prefer a code-owned path template with validated identifiers. If the partner returns a callback URL, validate that callback against the partner-specific policy before using it, and do not forward authorization headers or cookies across an origin boundary unless the protocol explicitly requires it.

Review server and browser redirect surfaces together

The Location header has redirect semantics for a 3xx response and also identifies a created resource in a 201 response. Review status and header together: a patch that changes a 302 to a 201 may still expose a caller-controlled Location to browser or API clients. On the client, inspect window.location, location.assign, location.replace, router push methods, and framework link components. A server-side validation helper does not protect a second client-side path that reads the same query parameter.

If a redirect follows POST, login, logout, or account linking, establish the CSRF and session contract separately. Validate the target before mutating state where possible, and ensure a rejected target cannot still create a session, rotate a token, enqueue work, or change an account. Use a fixed local error page when validation fails.

Build a harmless adversarial test matrix

Use a disposable application and a recording browser or HTTP client. The tests should observe the response status, Location value, final origin, cookies, and side effects without visiting a real attacker site.

Fixture or transitionRequired observation
Opaque identifier dashboardMaps to the expected code-owned relative path
Valid path /account/settingsOne same-origin redirect with the expected status
Absolute external destination supplied by the callerRejected before a redirect response is sent
Scheme-relative //outside.invalid/pathRejected; no host change
Backslash or mixed-slash variantRejected or normalized to a documented safe path, never an external host
Encoded delimiter such as %2f%2foutside.invalidParser and construction policy agree; no cross-origin result
Username or password in a URLRejected before Location is set
Host suffix trusted.example.com.evil.testRejected by exact-origin comparison
Unexpected scheme such as javascript: or data:Rejected before client navigation
Malformed URL, control character, or oversized valueControlled error, no partial redirect, no log echo
Login or logout with a rejected targetNo session, token, or account side effect
Client router receives an untrusted query valueRouter does not navigate outside the approved origin
Redirect followed by another redirectEach hop obeys the policy; no automatic escape hatch

For a positive browser test, use a local fixture origin that the test owns and assert the final origin and path. For a negative test, use a reserved .invalid host and a recording transport that confirms no connection is made. Do not test by sending requests to real third-party sites, cloud metadata addresses, or another tenant’s domain. Include a known-good control that proves the harness can observe a navigation, then isolate hostile fixtures so a browser policy or network block cannot make a broken redirect look safe.

Happy-path operator workflow

  1. Inventory every changed server route and browser navigation call, including error, retry, login, logout, and callback paths.
  2. Write the destination contract and choose a mapping or same-origin path policy before changing code.
  3. Trace each input from query, form, cookie, database, model output, upstream header, or repository text to the final navigation operation.
  4. Replace complete caller-supplied URLs with opaque identifiers or fixed paths wherever the feature permits it.
  5. Parse remaining values with one standards-based URL implementation and reject credentials, unsupported schemes, ambiguous slashes, control characters, and unexpected origins.
  6. For external partners, compare exact normalized origins and use a code-owned path template. Preserve no ambient credentials across an origin change without a documented protocol reason.
  7. Apply the decision before setting Location, returning a navigation instruction, or performing dependent state changes.
  8. Run the positive and negative matrix through the real router, middleware, proxy, cookie, and client stack.
  9. Record the policy revision, parser/runtime version, fixture IDs, final origin, response status, and side-effect result in a sanitized review note.
  10. Inspect the final diff for a fallback that still copies the raw parameter, a second client-side navigation path, automatic redirect following, or logging of full query strings.

Error-path operator workflow

Treat parse failure, an unknown mapping key, a disallowed origin, or an invalid nested target as a normal controlled rejection. Return a fixed local error page or a documented 4xx response. Do not reflect the rejected URL in HTML, JavaScript, headers, or logs. Stable codes such as redirect_target_missing, redirect_parse_failed, redirect_origin_denied, and redirect_mapping_unknown make tests and operations reviewable without retaining hostile input.

Do not “repair” a rejected target by trimming suspicious characters, decoding repeatedly, adding a scheme, or falling back to the raw value. Those transformations can create a different destination from the one that was checked. If a legitimate partner is blocked, update the explicit policy through review and rerun the same matrix; do not add a wildcard or disable validation during an incident.

A redirect may be safe while the preceding action is not. Keep the rejection path side-effect free and review the state transition independently. If a login callback cannot validate its return target, send the user to a fixed local completion page and preserve only the minimum diagnostic class needed for support.

Failure modes

The patch checks only for http or https. A permitted scheme does not make an arbitrary host safe. Use a mapping or exact-origin policy.

The patch rejects javascript: but allows //host/path. Scheme-relative URLs change the host while inheriting the current scheme. Reject them under a same-origin path policy.

The patch uses startsWith("trusted-origin"). A longer attacker-controlled hostname can share that prefix. Parse and compare normalized origin components.

The patch validates one representation and navigates another. Decoding, trimming, slash conversion, or a second parser can change the target after authorization. Keep one parser and one construction path.

A safe server helper is bypassed by the SPA. Client routers and window.location calls need the same destination contract. Test the final browser-visible origin.

The patch validates after changing state. A rejected redirect must not leave a session, token, purchase, or queued job behind. Move validation earlier or make the state transition independent and explicit.

A redirect chain is trusted after the first hop. Every hop can introduce a new destination. Disable automatic following where possible and reapply the policy when a workflow requires multiple hops.

Logs capture the full return URL. Query strings can contain tokens, personal data, or hostile control characters. Record only a stable failure code and destination class.

A test passes because the network blocked the attacker host. Assert the response policy and final URL, and use a recording transport. Network failure is not proof that application validation ran.

FAQ

Is a relative URL always safe?

No. A value beginning with // is scheme-relative, and a path can contain a nested URL that a later client step interprets as a new destination. Define the accepted path grammar, parse the resolved result, and test the final origin.

Can a signed next parameter solve open redirects?

A signature proves that a trusted signer approved the bytes; it does not prove that the signer approved the right origin or that the bytes will be interpreted the same way later. Sign a short identifier or a normalized destination under an explicit policy, include expiry and context, and validate again at use.

Should every external redirect be blocked?

Many applications can avoid external redirects and should prefer that design. If a product requires one, use exact origin and path allowlists for named partners, document the credential and consent boundary, and test every callback path.

Does HttpOnly protect a redirect target?

No. HttpOnly limits script access to a cookie; it does not make a Location value trustworthy or stop the browser from navigating to an attacker-controlled site. Review redirect and session policy separately, including the site’s CSRF workflow .

What evidence should a reviewer retain?

Keep the destination contract, policy revision, parser/runtime version, exact fixture IDs, response status, final origin, side-effect assertions, and sanitized failure codes. Omit complete URLs with query strings, tokens, cookies, response bodies, and private account data.

Reader next step

Choose one redirect or navigation call changed by the next coding-agent patch. Replace its raw URL parameter with a mapping or a documented same-origin path contract, then add the scheme-relative, encoded-delimiter, host-suffix, malformed-input, and rejected-login cases to a disposable integration suite. Run one known-good local navigation and one denied navigation through the real middleware and client path, recording only the policy revision, final origin, and side-effect result. Keep the patch blocked if any fallback still copies an unchecked value into Location or a browser navigation API.