Flask Direct Use of Jinja2

MEDIUM

Detects direct use of jinja2.Environment or jinja2.Template, which bypasses Flask's automatic HTML autoescaping and can lead to XSS vulnerabilities.

Rule Information

Language
Python
Category
Flask
Author
Shivasurya
Shivasurya
Last Updated
2026-03-22
Tags
pythonflaskjinja2xsscross-site-scriptingautoescapingtemplateauditCWE-79OWASP-A03
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-XSS-001 --project .
1
2
3
rule.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

About This Rule

Understanding the vulnerability and how it is detected

This rule detects direct instantiation of jinja2.Environment or jinja2.Template in Flask applications. Flask wraps Jinja2 with a custom Environment that has autoescaping enabled for HTML templates (files ending in .html, .htm, .xml, .xhtml). When developers create a raw jinja2.Environment or jinja2.Template directly -- bypassing Flask's rendering pipeline -- they lose this autoescaping protection.

Without autoescaping, any template variable rendered with {{ variable }} is inserted into the HTML output as a raw string. If variable contains HTML metacharacters (<, >, ", ', &) from user input, those characters are rendered as HTML elements and attributes. An attacker who can control the variable value can inject arbitrary JavaScript that executes in the victim's browser -- a stored or reflected XSS vulnerability.

The autoescaping difference is subtle: Flask's render_template() renders {{ name }} as &lt;script&gt; if name is "<script>". A raw jinja2.Environment without autoescaping renders the same {{ name }} as <script>, which the browser executes.

The detection uses Or(calls("Environment"), calls("jinja2.Environment")) to match both the directly imported constructor form and the module-qualified form. This is an audit-grade rule that flags every direct Jinja2 Environment instantiation for manual review, since the presence of a raw Environment is a strong signal that autoescaping may not be configured.

Security Implications

Potential attack scenarios if this vulnerability is exploited

1

Reflected XSS via Unescaped Template Variables

User input rendered through a raw Jinja2 Environment without autoescaping is inserted directly into the HTML response. An attacker can craft a request with a parameter containing <script>alert(document.cookie)</script> and the script will execute in the victim's browser, stealing session cookies, redirecting to phishing pages, or performing actions on behalf of the victim.

2

Stored XSS via Database-Sourced Template Content

If templates are rendered from database content using a raw Jinja2 Environment, stored XSS is possible. An attacker who can write malicious HTML to a database field (through any injection or data import path) can have it rendered and executed in other users' browsers when they view the affected content.

3

Cookie Theft and Session Hijacking

XSS payloads can read document.cookie (for cookies without the HttpOnly flag) and exfiltrate session tokens to attacker-controlled servers. A successful XSS attack in a single unescaped template variable can compromise every active user session.

4

DOM-Based Defacement and Phishing

Injected JavaScript can rewrite page content, insert fake login forms, redirect users to phishing sites, or silently perform authenticated API calls on behalf of the victim.

How to Fix

Recommended remediation steps

  • 1Use Flask's render_template() with .html template files for all HTML responses. Flask's Jinja2 Environment has autoescaping enabled for HTML by default.
  • 2If you need to render templates outside Flask's request context (e.g., for email generation), configure the raw Jinja2 Environment with explicit autoescaping: Environment(autoescape=select_autoescape(['html', 'xml'])).
  • 3Never construct template strings by concatenating user input. Pass user data as context variables to templates, where Jinja2 handles escaping.
  • 4If you need raw HTML output from a trusted source, use Flask's Markup() (see PYTHON-FLASK-XSS-002) explicitly and document why the content is trusted.
  • 5Implement Content-Security-Policy (CSP) headers as defense in depth. Even if XSS occurs, a strict CSP can prevent script execution.

Detection Scope

How Code Pathfinder analyzes your code for this vulnerability

This rule uses Or(calls("Environment"), calls("jinja2.Environment")) to match both the directly imported constructor (from jinja2 import Environment; Environment(...)) and the module-qualified form (jinja2.Environment(...)). This is a broad audit pattern -- every direct Jinja2 Environment instantiation is flagged for review to determine whether autoescaping is configured. Calls that explicitly pass autoescape=True or autoescape=select_autoescape([...]) may still be flagged; reviewers should verify the autoescaping configuration is correct for the use case. The rule operates at the call-site level without cross-file dataflow analysis.

Compliance & Standards

Industry frameworks and regulations that require detection of this vulnerability

OWASP Top 10
A03:2021 - Injection: prevent cross-site scripting through proper output encoding
CWE Top 25
CWE-79 Improper Neutralization of Input During Web Page Generation (XSS)
PCI DSS v4.0
Requirement 6.2.4 -- prevent injection attacks including cross-site scripting
NIST SP 800-53
SI-10: Information Input Validation -- encode output to prevent script injection

References

External resources and documentation

Similar Rules

Explore related security rules for Python

Frequently Asked Questions

Common questions about Flask Direct Use of Jinja2

Legitimate use cases include generating non-HTML content (plain text emails, CSV, Markdown) where HTML autoescaping is irrelevant; using Jinja2 in non-request contexts like management commands or background tasks; or using Jinja2-based templating in libraries that are imported by Flask apps. In all cases, explicitly configure autoescaping for any output that will be rendered as HTML.
Yes. Raw jinja2.Environment() has autoescaping disabled by default -- autoescape=False. Flask's own Jinja2 Environment enables autoescaping for .html, .htm, .xml, and .xhtml file extensions. When you instantiate jinja2.Environment directly, you do not inherit Flask's autoescaping configuration unless you pass autoescape=select_autoescape([...]) explicitly.
from jinja2 import Environment, select_autoescape env = Environment(autoescape=select_autoescape(['html', 'xml'])) This enables autoescaping for templates loaded from files with .html or .xml extensions. For from_string() calls, autoescaping must be enabled per-template: template = env.from_string(source, globals={"autoescape": True})
PYTHON-FLASK-AUDIT-008 flags uses of Flask's render_template_string(), which uses Flask's autoescaping-enabled environment but is SSTI-adjacent. This rule (XSS-001) flags direct instantiation of raw jinja2.Environment, which creates a new environment without Flask's autoescaping. Both patterns are XSS-adjacent but through different mechanisms: SSTI risk vs. missing autoescaping.
Run: pathfinder ci --ruleset python/flask/PYTHON-FLASK-XSS-001 --project . The rule outputs SARIF, JSON, or CSV and can post inline pull request comments on GitHub.
The current rule pattern matches Environment() and jinja2.Environment(). jinja2.Template() is a lower-level API that also bypasses autoescaping. If your codebase uses jinja2.Template() directly, that pattern should be reviewed as well. The Or pattern can be extended to cover Template() in a future revision.

New feature

Get these findings posted directly on your GitHub pull requests

The Flask Direct Use of Jinja2 rule runs in CI and posts inline review comments on the exact lines — no dashboard, no SARIF viewer.

See how it works