XSS Protection in Django
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.
<!-- 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.
<!-- 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.
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. #}
- Using the |safe filter on user-submitted text just to make HTML formatting work, which disables escaping entirely.
- Building HTML by string-concatenating user input in Python instead of letting the template engine escape it.
- Assuming XSS is only a template problem and forgetting that JSON responses can carry unescaped user input too.
- 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.
Chapter Quiz — Complete all 9 topics to unlock
0/9 topics done
Complete these topics first: