' through\nthe form and verifies that the outgoing email (using Django's mail.outbox in\ntests) contains '<script>alert(1)</script>' rather than the raw\nscript tag in the html_message. This directly verifies that escaping is applied.\n"}}]}

Django XSS in send_mail html_message Parameter

MEDIUM

User input flows into the html_message parameter of send_mail() without sanitization, enabling HTML injection in emails.

Rule Information

Language
Python
Category
Django
Author
Shivasurya
Shivasurya
Last Updated
2026-03-22
Tags
pythondjangoxsshtml-injectionemailsend-mailhtml-messagetaint-analysisinter-proceduralCWE-79OWASP-A03
CWE References

Interactive Playground

Experiment with the vulnerable code and security rule below. Edit the code to see how the rule detects different vulnerability patterns.

pathfinder scan --ruleset python/PYTHON-DJANGO-SEC-061 --project .
1
2
3
4
5
6
7
8
9
10
11
12
rule.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

About This Rule

Understanding the vulnerability and how it is detected

This rule detects HTML injection vulnerabilities in Django applications where untrusted user input from HTTP request parameters flows into the html_message keyword argument of Django's send_mail() function without proper sanitization.

Django's send_mail() accepts an optional html_message parameter for sending HTML email alongside a plain text alternative. When user-controlled data is incorporated into the html_message string without HTML escaping, attackers can inject HTML tags, attributes, and potentially JavaScript that renders when the email recipient opens it.

This is functionally the same vulnerability as SEC-060 (EmailMessage HTML injection) but targeting the send_mail() API, which is the most commonly used Django email function due to its simpler interface. The send_mail() function is frequently called from Django views that process form submissions, making it a common location for user input to reach HTML email content.

Security Implications

Potential attack scenarios if this vulnerability is exploited

1

Phishing Content Injection into Legitimate Emails

send_mail() is widely used for transactional emails (password resets, welcome emails, order confirmations). An attacker who controls input to any of these flows can inject fake instructions, deceptive links, or fraudulent content into emails that recipients trust because they originate from the legitimate application domain.

2

Contact Form Abuse for Phishing

Contact forms that send submitted content in HTML emails are a particularly common injection vector. An attacker submits HTML content through the contact form, which is then included in the html_message parameter and delivered to the recipient's inbox. The recipient sees what appears to be a normal contact submission but containing attacker-controlled HTML.

3

Password Reset Flow Manipulation

If user-controlled data (such as a submitted username or display name) flows into the html_message of a password reset email, attackers can inject content that makes the email appear to contain different reset instructions, a malicious link, or a fake customer service message.

4

Notification Email Compromise

Applications that send HTML notifications containing user-supplied content (comments, messages, names) without escaping allow attackers to distribute malicious HTML to other users through the application's email system, potentially at scale.

How to Fix

Recommended remediation steps

  • 1Generate html_message content using render_to_string() with a template file; Django templates auto-escape all {{ variable }} values.
  • 2When constructing html_message as a Python string, escape all user-controlled values with django.utils.html.escape() before inclusion.
  • 3Generate the plain text message argument from the HTML by using strip_tags() on the escaped HTML content.
  • 4Validate the email recipient address with Django's EmailValidator before passing it to send_mail().
  • 5For emails triggered by user-submitted forms, consider whether including the raw user content in the HTML body is necessary, or whether a sanitized summary is sufficient.

Detection Scope

How Code Pathfinder analyzes your code for this vulnerability

This rule performs inter-procedural taint analysis with global scope. Sources include calls("request.GET.get"), calls("request.POST.get"), calls("request.GET.__getitem__"), calls("request.POST.__getitem__"), calls("request.body"), and calls("request.read"). The sink is the html_message keyword argument (tracked via .tracks(0) on the named parameter) of calls("send_mail") and calls("django.core.mail.send_mail"). Sanitizers include calls("django.utils.html.escape"), calls("render_to_string"), and calls("bleach.clean"). The rule follows taint across file and module boundaries.

Compliance & Standards

Industry frameworks and regulations that require detection of this vulnerability

CWE Top 25
CWE-79 ranked #2 in 2023 Most Dangerous Software Weaknesses
OWASP Top 10
A03:2021 - Injection
PCI DSS v4.0
Requirement 6.2.4 - protect against injection attacks
NIST SP 800-53
SI-10: Information Input Validation; SI-15: Information Output Filtering
GDPR Article 32
Technical measures to ensure security and integrity of personal data

References

External resources and documentation

Similar Rules

Explore related security rules for Python

Frequently Asked Questions

Common questions about Django XSS in send_mail html_message Parameter

SEC-060 targets Django's lower-level EmailMessage and EmailMultiAlternatives classes. SEC-061 targets the higher-level send_mail() convenience function which is more commonly used in Django views for transactional emails. Both are HTML injection sinks but require different sink patterns to detect. Applications typically use send_mail() for simple cases and EmailMessage for more control.
Yes, if the render_to_string() template properly uses {{ variable }} interpolation for user-controlled values, which auto-escapes them. The finding is eliminated because the sanitizer (render_to_string with auto-escaping) is recognized as a safe transformation of user input before it reaches the html_message argument.
Email header injection (CWE-93) through the subject parameter is a separate concern from HTML injection in the body. Django's send_mail() sanitizes the subject parameter against header injection. However, you should still validate that the subject contains reasonable content and does not include newlines.
Email addresses should be escaped if included in HTML. An email address can contain characters like +, ., and @ that are not HTML-special, but a malformed or crafted email address might contain <, >, or & characters. Apply escape() to any value included in html_message content, including email addresses.
Yes. If you escape the user's submitted message with escape() before including it in html_message, the HTML injection risk is eliminated. The escaped text renders as visible characters rather than as HTML markup. For contact forms, consider whether HTML rendering is necessary at all -- plain text emails avoid the escaping requirement entirely.
Write a test that submits a value containing '<script>alert(1)</script>' through the form and verifies that the outgoing email (using Django's mail.outbox in tests) contains '&lt;script&gt;alert(1)&lt;/script&gt;' rather than the raw script tag in the html_message. This directly verifies that escaping is applied.

New feature

Get these findings posted directly on your GitHub pull requests

The Django XSS in send_mail html_message Parameter rule runs in CI and posts inline review comments on the exact lines — no dashboard, no SARIF viewer.

See how it works