← Back to Django Course | Chapter 15: Security, Caching & Deployment | Lesson 4 of 9

XSS Protection in Django

XSS protection is like Django double-checking that any text a visitor typed in isn't secretly a sneaky script before showing it back on the page.

Auto-Escaping in Templates

By default, every variable rendered with {{ variable }} in a Django template is HTML-escaped. If a user submits <script>alert(hi)</script> as their bio, Django renders it as visible text, not as an executable script.

Note: Auto-escaping is on by default for the whole project -- you don't need to do anything to get it.

Example: Auto-Escaping in Templates

By default, every variable rendered with {{ variable }} in a Django template is HTML-escaped. If a user submits <script>alert(hi)</script> as their bio, Django renders it as visible text, not as an executable script.

markup
<!-- templates/profile.html -->
<!-- If comment.text = "<script>alert('hi')</script>" -->
<!-- Django renders it as literal text, not a script -->
<p>{{ comment.text }}</p>
{# Django-only code -- models.py/views.py/urls.py/settings.py snippets, or template markup using Django template tags/variables -- can't run standalone via Judge0 or the browser preview, since it needs a real Django project. Only this course's pure-Python examples (example_lang == 'python', no Django imports) are actually runnable, so those still get the button below. #}

The Danger of the safe Filter

The |safe filter tells Django to trust a value completely and render it as raw HTML without escaping. This is only safe when the content is fully controlled by the developer -- never for anything a user typed.

Warning: Applying |safe to user-submitted content re-opens the exact hole auto-escaping was protecting against.

Example: The Danger of the safe Filter

The |safe filter tells Django to trust a value completely and render it as raw HTML without escaping. This is only safe when the content is fully controlled by the developer -- never for anything a user typed.

markup
<!-- Safe: content written by the developer, not a user -->
{{ site_announcement_html|safe }}

<!-- UNSAFE: never do this with user input -->
<!-- {{ comment.text|safe }} -->
{# Django-only code -- models.py/views.py/urls.py/settings.py snippets, or template markup using Django template tags/variables -- can't run standalone via Judge0 or the browser preview, since it needs a real Django project. Only this course's pure-Python examples (example_lang == 'python', no Django imports) are actually runnable, so those still get the button below. #}

mark_safe in Python Code

mark_safe() does the same thing as |safe but from Python. It should only wrap HTML the code itself built from trusted, fixed strings -- never HTML built by concatenating user-submitted text.

Example: mark_safe in Python Code

mark_safe() does the same thing as |safe but from Python. It should only wrap HTML the code itself built from trusted, fixed strings -- never HTML built by concatenating user-submitted text.

markup
from django.utils.safestring import mark_safe

def build_badge(label):
    # label is a fixed, developer-controlled string, not user input
    return mark_safe(f'<span class="badge">{label}</span>')
{# Django-only code -- models.py/views.py/urls.py/settings.py snippets, or template markup using Django template tags/variables -- can't run standalone via Judge0 or the browser preview, since it needs a real Django project. Only this course's pure-Python examples (example_lang == 'python', no Django imports) are actually runnable, so those still get the button below. #}
Common Mistakes
  1. Using the |safe filter on user-submitted text just to make HTML formatting work, which disables escaping entirely.
  2. Building HTML by string-concatenating user input in Python instead of letting the template engine escape it.
  3. Assuming XSS is only a template problem and forgetting that JSON responses can carry unescaped user input too.
Chapter Summary
  • XSS (Cross-Site Scripting) happens when an attacker gets their own script to run in another user's browser via unescaped input.
  • Django's template engine auto-escapes variables by default, turning <script> into harmless text.
  • The |safe filter and mark_safe() turn escaping off, so use them only on trusted, developer-written HTML.
  • Never trust user input enough to render it as raw HTML without a good reason and a sanitizer.

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.