Flask url_for with _external=True

LOW

Detects url_for() called with _external=True, which generates absolute URLs using the Host header and can be abused for open redirect or host header injection attacks.

Rule Information

Language
Python
Category
Flask
Author
Shivasurya
Shivasurya
Last Updated
2026-03-22
Tags
pythonflaskurl-forexternalopen-redirecthost-header-injectionauditCWE-601OWASP-A01
CWE References

Interactive Playground

Experiment with the vulnerable code and security rule below. Edit the code to see how the rule detects different vulnerability patterns.

pathfinder scan --ruleset python/PYTHON-FLASK-AUDIT-005 --project .
1
2
3
4
5
6
7
rule.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

About This Rule

Understanding the vulnerability and how it is detected

This rule detects calls to Flask's url_for() with the _external=True keyword argument. When _external=True is set, url_for() generates an absolute URL (including scheme and host) rather than a relative path. Flask constructs the host portion of this URL from the incoming HTTP request's Host header. If the Host header is not validated, an attacker can supply an arbitrary host value and cause url_for() to return a URL pointing to an attacker-controlled domain.

This becomes a direct security vulnerability when the generated URL is used in an HTTP redirect (redirect(url_for(..., _external=True))), a password-reset email link, an OAuth redirect_uri, or any other context where the URL is sent to a user and the user's browser will follow it. In these cases, host header injection escalates to an open redirect or phishing attack.

The detection uses calls("url_for", match_name={"_external": True}) -- an Or-free pattern that matches any call to url_for() where the _external keyword argument is present and set to True. This is an audit-grade rule: not every url_for(_external=True) is vulnerable, but every use warrants review to confirm that the generated URL is not used in a redirect or emailed to a user without host validation.

Security Implications

Potential attack scenarios if this vulnerability is exploited

1

Open Redirect via Host Header Injection

If the generated URL is passed to redirect(), an attacker can set the Host header to an attacker-controlled domain. The application will return a 302 response pointing the user's browser to the attacker's site. Phishing, credential harvesting, and malware delivery are the typical payloads.

2

Password Reset Link Hijacking

Password reset flows frequently generate absolute URLs with _external=True for email links. If an attacker can influence the Host header during the reset request (possible in some proxy and load-balancer configurations), the reset link in the email will point to the attacker's domain, handing them the reset token.

3

OAuth redirect_uri Manipulation

OAuth flows that use url_for(_external=True) to build the redirect_uri parameter can be manipulated to point to attacker-controlled endpoints, capturing authorization codes and access tokens.

4

Cache Poisoning via Host Header

If the externally-generated URL is cached (CDN, application cache) with the attacker's host value, subsequent users who receive the cached response will see URLs pointing to the attacker's domain.

How to Fix

Recommended remediation steps

  • 1Set app.config['SERVER_NAME'] to your production domain so Flask uses a fixed host for external URL generation rather than trusting the Host header.
  • 2If you must generate external URLs without SERVER_NAME, validate that the generated URL's host matches an explicit allow-list of known-good domains before using it in redirects or emails.
  • 3Prefer relative URLs (url_for() without _external=True) for internal redirects within the application.
  • 4For email links, construct absolute URLs from a hardcoded BASE_URL configuration value rather than from request context.
  • 5Add server-side Host header validation middleware if your deployment architecture sends the original Host header through proxies.

Detection Scope

How Code Pathfinder analyzes your code for this vulnerability

This rule uses calls("url_for", match_name={"_external": True}) to match any call to the url_for() function where the _external keyword argument is present and set to True. This is a broad audit pattern -- it flags all such calls for manual review, not only those that directly feed into redirect() or email sending. The rule does not perform dataflow analysis from the generated URL to its use site. Use this finding as a signal to review each flagged call and determine whether the generated URL reaches a redirect or email context without host validation.

Compliance & Standards

Industry frameworks and regulations that require detection of this vulnerability

OWASP Top 10
A01:2021 - Broken Access Control: prevent open redirects to untrusted destinations
CWE Top 25
CWE-601 URL Redirection to Untrusted Site
PCI DSS v4.0
Requirement 6.2.4 -- prevent injection and redirect attacks
NIST SP 800-53
SI-10: Information Input Validation -- validate HTTP request headers

References

External resources and documentation

Similar Rules

Explore related security rules for Python

Frequently Asked Questions

Common questions about Flask url_for with _external=True

No. This is an audit-grade rule. url_for(_external=True) is vulnerable only when the generated URL is used in a context that sends it to a user's browser (redirect, email link, OAuth redirect_uri) and when the Host header is not validated. If you use app.config['SERVER_NAME'] to fix the host, or if the generated URL is only used server-side for logging or internal API calls, the risk is significantly reduced.
When app.config['SERVER_NAME'] is set, Flask uses that value as the host for external URL generation regardless of the incoming Host header. The Host header is not trusted for URL construction, which eliminates the host header injection vector.
The vulnerability requires the generated URL to reach a user-facing redirect or email flow without host validation, which is not guaranteed by the call pattern alone. The rule is a precision audit signal -- it identifies every location that warrants review, but many flagged locations will be safe in context. LOW reflects that the pattern alone is not sufficient to confirm exploitability.
Yes. Add a Code Pathfinder inline suppression comment on the line or add the file to a path exclusion list in your configuration. Document why the suppression is safe -- for example, "SERVER_NAME is configured globally" or "URL is only used for logging, never in responses."
Run: pathfinder ci --ruleset python/flask/PYTHON-FLASK-AUDIT-005 --project . The rule outputs SARIF, JSON, or CSV and can post inline pull request comments on GitHub.
No. The match_name={"_external": True} pattern matches only the literal boolean True. If you pass _external=some_variable, the rule does not flag it. This keeps the rule precise to the most common case where external URL generation is explicitly hardcoded.
This rule audits the URL generation site. A complementary open redirect rule would trace the flow from the generated URL into redirect() -- a dataflow analysis. Together they cover both the source (url_for) and the sink (redirect), giving you defense in depth in your static analysis pipeline.

New feature

Get these findings posted directly on your GitHub pull requests

The Flask url_for with _external=True rule runs in CI and posts inline review comments on the exact lines — no dashboard, no SARIF viewer.

See how it works