No. Flask configures Jinja to escape values automatically, which rules out most XSS caused in templates, but the Flask docs list gaps: HTML built without Jinja, Markup called on user data, unquoted attributes, javascript: links in href, and HTML served from uploaded files. Autoescape is a strong default, not a guarantee.
What autoescape does
Jinja converts characters such as angle brackets and quotes into harmless entities when it renders a value, so a name like a script tag appears as text instead of running. The Flask templating docs say autoescaping is enabled for templates ending in .html, .htm, .xml and .xhtml, as well as .svg with render_template(), and for all strings when using render_template_string().
That covers the common case of showing user content inside element text.
The gaps listed in Flask's security page
Flask's security considerations page says autoescape should rule out XSS problems caused in templates, but names other places to be careful:
| Gap | Why it bites |
|---|---|
| Generating HTML without Jinja | String concatenation never gets escaped |
| Calling Markup on user submitted data | Markup tells Jinja the text is already safe |
| HTML from uploaded files | Served as a page, it runs in your origin |
| Text files from uploads | Some browsers guess content type from the first bytes |
| Unquoted attributes | Escaping cannot stop attribute injection |
| javascript: URIs in href | Escaped text is still a valid link |
Quote your attributes
The docs give an example of why. In an input tag with the value written without quotes, an attacker can inject an event handler such as onmouseover, which runs script when the user moves the mouse over the element. The rule is simple: always put double or single quotes around attributes that contain Jinja expressions.
Safe:
<input value="{{ value }}">
The href problem
An anchor with a user supplied href can contain a javascript: URI that the browser executes on click. Autoescape does not change that, because the text is a valid URL as far as escaping is concerned. The Flask docs say the way to prevent this is to set a Content Security Policy response header. You should also validate that user supplied links start with http or https before rendering them.
Serving uploaded files safely
Flask advises never sending HTML from uploaded files, and to use the Content-Disposition attachment header so the browser downloads rather than renders. For text files, the content type guessing risk means you should set a correct content type and consider the nosniff header.
A short review list
- Grep templates for the safe filter and code for Markup(). Each use needs a reason.
- Check every attribute that holds a template expression is quoted.
- Check every href or src built from user data for allowed schemes.
- Add a Content Security Policy header.
- Make sure uploads are served as attachments from a separate origin where possible.
Try it yourself
Click through it at your own pace, or open it full screen. See how a running page is tested for script injection and missing headers.
Quick check
Does render_template_string escape values?
Per the Flask docs, autoescaping is enabled for all strings with render_template_string. That does not make it safe to build the template text from user input, only to pass values into it.
What does the safe filter do?
It marks a value as already safe, so Jinja does not escape it. Use it only for content you generated or sanitized yourself.
Safeguard's SAST and DAST scanning can help find unsafe template patterns and missing headers. You can try a free scan.
Sources
Last reviewed: 19 October 2026.