Flask XSS Protection Disabled

ID

python.flask_xss_protection_disabled

Severity

high

Remediation Complexity

trivial

Remediation Risk

low

Remediation Effort

low

Resource

Misconfiguration

Language

Python

Tags

ASVS50:v1.3.1, CWE:80, NIST.SP.800-53, OWASP:2025:A02, OWASP:2025:A05, PCI-DSS:6.5.7, flask

Description

Improper configurations or misuse of Flask’s templating system can disable XSS protection, exposing applications to cross-site scripting attacks.

Rationale

Cross-Site Scripting (XSS) is a vulnerability that occurs when an application includes untrusted data in a web page without proper validation or escaping.

Templating systems are designed with built-in protections to prevent cross-site scripting (XSS) vulnerabilities. These protections automatically escape HTML input, ensuring that user-supplied content is not executed as code on the client side.

However, developers may inadvertently disable these protections by marking content as "safe" or bypassing the template engine, increasing the risk of XSS attacks. Some common misconfigurations and practices can lead to such vulnerabilities:

Markup

Using Markup in Flask to handle HTML content requires careful attention to prevent XSS vulnerabilities. Markup is used to mark a string as safe, meaning it should not be escaped when rendered. This is useful for trusted content but can be dangerous with user-generated input.

from flask import Flask, render_template_string, Markup

app = Flask(__name__)

@app.route('/')
def home():
    # Mock potentially dangerous user input
    user_input = "<script>alert('XSS');</script>"

    # Dangerous: Marking untrusted content as safe (do not do this)
    content = Markup(user_input)

    # Rendering a simple template with the unsafe content
    template = """
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Unsafe Example</title>
    </head>
    <body>
        <!-- Directly rendering potentially dangerous content -->
        <div>{{ content }}</div>
    </body>
    </html>
    """
    return render_template_string(template, content=content)

if __name__ == '__main__':
    app.run(debug=True)

What is not reported

Markup over text the author wrote suppresses no escaping decision, so it is not reported. That covers a literal, a translated literal, and any concatenation or format of those:

Markup('<code>100,200,300</code>')                              # not reported
Markup(_('Assigned tagged VLAN IDs, encased with double quotes')) # not reported
Markup(_('Example:') + ' <code>"100,200,300"</code>')            # not reported

It also covers an expression that escapes every value it interpolates — an f-string or a format whose dynamic parts all pass through escape or conditional_escape — except where HTML escaping is not the protection that matters. A value opening an href, src, action or style, or landing inside a <script> element or an event-handler attribute, is reported even when escaped, because escaping leaves javascript:alert(1) untouched:

Markup(f'<p>{escape(comment)}</p>')             # not reported
Markup(f'<a href="{escape(target)}">go</a>')     # reported: target can be a javascript: URI

The test is on the expression, and a local name is never resolved: Markup(msg) is reported even when msg was built from escaped parts, because proving that needs the local data flow and the cost of being wrong is a missed XSS. A module-level constant the file assigns exactly once is resolved, and only that.

This mirrors python.django_xss_protection_disabled, which documents the shapes in full.

render_template With Unescaped File Extension

To render a template without automatically escaping content, you can use custom file extensions or adjust Jinja environment settings to disable auto-escaping. Below is an example using a custom file extension:

from flask import Flask, render_template

app = Flask(__name__)

# Mock potentially dangerous user input
user_input = "<script>alert('XSS');</script>"

@app.route('/')
def home():
    return render_template("unsafe.customext", user_input=user_input)

if __name__ == '__main__':
    app.run(debug=True)

Remediation

Carefully review every issue addressed by this detector to ensure that the unescaped HTML code is safe.

To effectively address XSS vulnerabilities when using Flask enable all the autoescape settings.

References

  • CWE-80 : Improper Neutralization of Script-Related HTML Tags in a Web Page (Basic XSS).

  • Flask XSS

© 2026 Xygeni. All rights reserved