Autoescape in Jinja2 templates

What is it and why?

Ben Alpert's FIXIT 2 email explains it well:
Currently we write things like {{ text|escape }} everywhere in our jinja2 code, but there's no reason that being safe against evildoers should require such contortions.
We like to be secure by default. Enabling autoescape means that all template variables will be XML/HTML escaped, and you'll have to explicitly make the decision to include raw variable output in a template.

Usage

... To do this, pipe your template variable through the |safe filter:

<div id="post-content">
    {{ post.body_html|safe }}
</div>

We try to use _html as a suffix on all template variables which contain HTML, so if you see something like {{ post.body|safe }}, consider investigating and changing the template variable name if appropriate.

If there is, say, a script tag in an HTML file for which every template variable within it needs to be unescaped (marked as safe), you can use the autoescape extension:

<script>
    {% autoescape false %}
    // Autoescape is disabled here; variables will not be escaped by default
    var data_json = {
        gorilla: {{ gorilla_json }},
        giraffe: {{ giraffe_json }}
    };
    {% endautoescape %}
</script>

Note that autoescape is enabled only for HTML and XML files, so you don't have to unescape, for example, .txt and .json files.

Jinja2 is also smart enough to not escape macro output, so something like this is OK:

{% import 'macros/dashboard.html' as dashboard %}
<div id="graph-of-monkeys">
    {{ dashboard.daily_graph(graph.kind_name) }}
</div>

Most of our custom template functions (in templatetags.py) also don't need to be unescaped:

<div class="dashboard-nav">
    {{ templatetags.topic_browser("browse") }}
</div>

This is done by wrapping the return value with jinja2.Markup():

def topic_browser(browser_id, version_number=None):
    # ...
    return jinja2.Markup(shared_jinja.get().render_template(
        "topic_browser.html", **template_values))

Additional good escaping practices

  • To insert values in embedded JavaScript, use our jsonify filter:
<script>
    $(function() {
        Login.initLoginPage({
            continueUrl: {{ continue|jsonify|safe }}
        });
    });
</script>

Our jsonify protects against XSS attempts in JSON strings which attempt to terminate the current script block and start a new one, such as </script><script>alert('uh oh.');</script>. Thus, in Python handlers, always prefer jsonify to json.dumps when embedding JSON in templates:

from api.jsonify import jsonify
template_values = {
    'students_json': jsonify(students),
}
self.render_jinja2_template('viewstudentlists.html', template_values)
  • To escape a variable to be inserted as part of a JavaScript string, use |escapejs:
var greeting = 'Hello {{ username|escapejs|safe }}!';

which encodes troublesome characters (such as \, ", >, <, &, =, -, ;) with their backslash-escaped unicode entities. This protects against injection attacks where username is something like '; alert('blah'); //
  • For query strings in HTML attributes, use the |urlencode filter:
<a href="/login?continue={{ continue|urlencode }}">Continue</a>


Comments