Autoescaping and safe markup

Jinja · Practical · 10 min

Escaping mistakes in templates are a top cause of cross-site scripting vulnerabilities.

With autoescaping on, every {{ value }} is passed through HTML escaping: <, >, &, " and ' become entities. User input like <script> displays as text instead of running.

Escaping is tracked by type. A `Markup` string (from MarkupSafe) is considered already safe and is not escaped again. Ways to produce one:

  • {{ html|safe }} in a template
  • Markup("<b>ok</b>") in Python
  • macros and caller() return Markup, so they nest without double escaping

Use these only for HTML you produced or sanitized yourself. Markup has safe helpers: Markup("<a href='{}'>{}</a>").format(url, name) escapes the arguments and keeps the template part.

Where autoescaping is not enough:

  • inside <script> blocks, HTML escaping is the wrong encoding. Use {{ data|tojson }}, which produces JSON and escapes characters that could close the script tag.
  • in attributes, always quote: href="{{ url }}". An unquoted attribute can be broken out of with a space. Escaping also doesn't stop javascript: URLs; validate URLs.
  • in CSS and URLs, use dedicated encoders such as |urlencode.

When autoescaping is off (text templates, or plain Environment()), {{ x|e }} escapes explicitly. You can switch it locally with {% autoescape true %}...{% endautoescape %}.

Never build HTML by concatenating user strings in Python and then marking the result safe; that defeats the whole system.

Example

from jinja2 import Environment
from markupsafe import Markup

env = Environment(autoescape=True)
t = env.from_string("""<p>{{ comment }}</p>
<p>{{ trusted }}</p>
<a href="{{ url }}">{{ link }}</a>
<script>const user = {{ user|tojson }};</script>""")

print(t.render(
    comment="<img src=x onerror=alert(1)>",
    trusted=Markup("<em>{}</em>").format("<b>escaped arg</b>"),
    url='x" onclick="evil()', link=Markup("<b>Home</b>"),
    user={"name": "</script><script>alert(1)"}))
# <p>&lt;img src=x onerror=alert(1)&gt;</p>
# <p><em>&lt;b&gt;escaped arg&lt;/b&gt;</em></p>
# <a href="x&#34; onclick=&#34;evil()"><b>Home</b></a>
# <script>const user = {"name": "\u003c/script\u003e\u003cscript\u003ealert(1)"};</script>

Remember

  • Autoescaping escapes every {{ }} unless the value is Markup
  • Use |safe or Markup only for HTML you trust
  • Use |tojson for data inside <script>; always quote attributes

Watch out for

  • Applying |safe to user-provided rich text without sanitizing it first.

Check yourself

  1. How should you embed a Python dict as a JavaScript object inside a <script> tag?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: jinja.palletsprojects.com/en/stable/templates/#html-escaping