Skip to content

VNX-938: Unvalidated redirect or forward to a request-supplied destination

Reports Python redirects whose destination is request data, and Java/Kotlin server-side forwards (getRequestDispatcher, Spring forward:) and Kotlin redirects built from request input without validation.

Updated 02 Nov 2026Checked against cli@de9b52a
Field Value
Rule ID VNX-938
Severity Medium
SARIF level warning
Kind sast
Languages Python, Java, Kotlin
CWE CWE-938
CAPEC CAPEC-66
ATT&CK T1190
CWSS vector CWSS:1.0/TI:M/AP:A/AL:A/IC:N/FC:LT/RP:N/RL:A/AV:I/AS:N/IN:A/SC:A/BI:M/DI:H/EX:M/EC:N/P:C
Tags open-redirect forward cwe-938
Source internal/sast/rules/vnx-938.rego

Overview

VNX-938 maps to CWE-938: OWASP Top Ten 2013 Category A10 - Unvalidated Redirects and Forwards. A redirect to a user-supplied URL sends victims from a trusted domain to a phishing page; a forward to a user-supplied path runs inside the application and can serve pages the user’s role should not reach. This rule covers the shapes the per-language open-redirect rules do not: Python redirects, Java/Kotlin forwards, and Kotlin redirects (VNX-JAVA-007 covers Java redirects, and the Node.js, PHP and Go open-redirect rules cover those languages).

What Gets Flagged

return redirect(request.args.get("next", "/"))            # flagged (Flask)

next_url = request.GET.get("next")
return HttpResponseRedirect(next_url)                      # flagged (Django)
String page = request.getParameter("page");
request.getRequestDispatcher("/WEB-INF/views/" + page + ".jsp").forward(request, response);   // flagged

public String next(@RequestParam("step") String step) {
    return "forward:" + step;                              // flagged
}
fun jump(@RequestParam target: String): String = "forward:$target"   // flagged
response.sendRedirect(request.getParameter("url"))                    // flagged

Not flagged: a destination that starts with a fixed literal (redirect(f"/blog/{id}")), url_for/reverse lookups and named URL patterns, a value looked up in a constant map, a value checked first (url_has_allowed_host_and_scheme, urlparse(...).netloc, an allowlist membership test, containsKey/startsWith/equals), request attributes and other server-side values, comments and string literals. A fixed prefix on a forward path does not prevent ../ traversal, so it is still reported.

Remediation

Map the request value to a fixed set of destinations, or validate it against an allowlist:

from django.utils.http import url_has_allowed_host_and_scheme

if not url_has_allowed_host_and_scheme(next_url, allowed_hosts={request.get_host()}):
    next_url = "/"
return HttpResponseRedirect(next_url)

References

Suppressing this rule

Put a nosec comment on the line, or on line 1 to cover the whole file. Use the comment marker of the file’s language (#, //, -- or ;):

# nosec vnx-938
// nosec vnx-938

Or record an ignore rule: vulnetix ignore add --rule vnx-938 --reason "...". See Suppressing findings.

Edit this pageSources: cli/internal/sast/rules/vnx-938.rego, cli/internal/sast/docs/vnx-938.md
esc
↑ ↓ move↵ openSearches in your browser. A key like autonomy.max or a code like threshold_required jumps straight to it.