SEC009-open-redirect — Open Redirect via User-Controlled URL
Dimension: Security Default severity: Medium CWE: CWE-601 OWASP: A01:2021 – Broken Access Control Languages: All (Python, JS/TS, Rust) Last reviewed: 2026-05-08
What it detects
Flags source lines that both issue an HTTP redirect and incorporate user-controlled input into the redirect target URL without validation.
A finding is emitted for each source line satisfying both conditions:
-
Redirect call — the line matches one of:
redirect(,res.redirect(,response.redirect(,Response.redirect(,HttpResponseRedirect(,RedirectResponse(,Redirect::to(,Redirect::permanent(,Redirect::temporary(, or aLocation:header assignment viaset_header(…Location…),headers["Location"] =, orlocation =. -
Untrusted-input signal — the line contains at least one of:
- An interpolation marker:
${, f-string{," +,' +,+ ",+ ',format!(,.format(,%s. - A known untrusted-source token:
req.query,req.params,req.body,request.args,request.form,request.GET,request.POST,request.json,params[,query[.
- An interpolation marker:
Comment lines (trimmed start begins with //, #, *, /*) are skipped.
Why it matters
An open redirect allows an attacker to craft a legitimate-looking URL that
redirects the victim to a malicious site. The victim trusts the domain in the
initial URL (e.g. https://app.example.com/go?url=https://evil.example.com)
and may disclose credentials or be phished. CWE-601 / OWASP A01:2021 (Broken
Access Control) covers this class.
Configuration
This rule has no configurable threshold. Disable per project:
[rules.SEC009-open-redirect]
enabled = false
Examples flagged
Python — Flask redirect to a query-parameter URL:
from flask import Flask, redirect, request
app = Flask(__name__)
@app.get("/login")
def login_redirect():
return redirect(request.args.get("next")) # flagged: user input in redirect
JS/TS — Express redirect to a query-string URL:
import express from "express";
const app = express();
app.get("/go", (req, res) => {
res.redirect(req.query.url as string); // flagged: user input in redirect
});
Rust — Axum redirect with formatted user input:
use axum::response::Redirect;
pub async fn redirect_handler(user_input: String) -> Redirect {
Redirect::to(&format!("{}", user_input)) // flagged: user input in redirect
}
Examples not flagged
Python — redirect to a fixed path:
from flask import redirect
def home():
return redirect("/dashboard") # not flagged: static target
JS/TS — redirect to a hardcoded URL:
res.redirect("https://app.example.com/home"); // not flagged: no user input
Fix guidance
- Maintain an allow-list of safe redirect targets (relative paths or a small set of known-good domains). Reject anything not on the list.
- Prefer relative paths (
/dashboard,/account) rather than absolute URLs to eliminate cross-domain redirects entirely. - Validate the scheme and host if absolute URLs are needed: ensure the resulting URL's hostname is on your allow-list before redirecting.
- For OAuth / SSO
nextparameters, store the intended destination server-side (e.g. in the session) keyed to an opaque token, and look it up after authentication completes.
Implementation
- Source:
crates/zuit-analyzers/src/open_redirect.rs - Severity / supported languages: see
RuleMetain source.