Skip to content

Commit 421e13f

Browse files
author
Arne Tarara
committed
Merge remote-tracking branch 'upstream/main'
2 parents 77b3562 + cc68c7d commit 421e13f

3 files changed

Lines changed: 124 additions & 39 deletions

File tree

docs/internals/howto-release-django.txt

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,8 @@ There are a lot of details, so please read on.
6363
populates a lot of boilerplate you will need for announcements, CVE
6464
publication, and hashes for commit messages. By using this app for preparing
6565
security issue metadata, your peer releasers can check your entries and
66-
consult them again in the future.
67-
(`Example <https://www.djangoproject.com/checklists/release/5.2.4/>`_)
66+
consult them again in the future. See `example checklist
67+
<https://www.djangoproject.com/checklists/release/5.2.4/>`_.
6868

6969
Prerequisites
7070
=============
@@ -742,7 +742,8 @@ You're almost done! All that's left to do now is:
742742
#. Process the older versions that will reach End-Of-Mainstream and/or
743743
End-Of-Life support when this final release is published:
744744

745-
#. Ensure that the EOL versions are mentioned in the blog post. `Example
745+
#. Ensure that the EOL versions are mentioned in the blog post. See
746+
`example announcement
746747
<https://www.djangoproject.com/weblog/2025/apr/02/django-52-released/>`_.
747748

748749
#. Create a tag for the EOL stable branch and delete the stable branch.
@@ -777,6 +778,13 @@ You're almost done! All that's left to do now is:
777778
#. Forward port the updated source translations to the ``main`` branch
778779
(:commit:`example commit <aed303aff57ac990894b6354af001b0e8ea55f71>`).
779780

781+
#. If this was an ``alpha`` pre-release, coordinate with the maintainer of
782+
:pypi:`asgiref` to determine when to raise the minimum version before the
783+
final release. For the backport, add an upper bound on the next major
784+
version (example commits:
785+
:commit:`main <df35cf578f99522dd1ba864d513be95d47bab7a5>`,
786+
:commit:`backport <e5d2664908164d51d2daa46e375da8b6a93def03>`).
787+
780788
#. If this was an ``rc`` pre-release, call for translations for the upcoming
781789
release in the `Django Forum - Internationalization category
782790
<https://forum.djangoproject.com/c/internals/i18n/14>`_.

docs/ref/settings.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ Each item in the list should be an email address string. Example::
5656

5757
ADMINS = ["john@example.com", '"Ng, Mary" <mary@example.com>']
5858

59+
To safely build an address with a display name from variable content, see
60+
:ref:`formatting-addresses`, which recommends using
61+
:class:`~email.headerregistry.Address` over string formatting, but since this
62+
setting requires a list of strings, wrap each ``Address`` in ``str(...)``.
63+
5964
.. setting:: ALLOWED_HOSTS
6065

6166
``ALLOWED_HOSTS``
@@ -1351,6 +1356,9 @@ any format valid in the chosen email sending protocol.
13511356
This doesn't affect error messages sent to :setting:`ADMINS` and
13521357
:setting:`MANAGERS`. See :setting:`SERVER_EMAIL` for that.
13531358

1359+
To build an address with a display name from variable content, see
1360+
:ref:`formatting-addresses`.
1361+
13541362
.. setting:: DEFAULT_INDEX_TABLESPACE
13551363

13561364
``DEFAULT_INDEX_TABLESPACE``
@@ -2797,6 +2805,9 @@ The email address that error messages come from, such as those sent to
27972805
``From:`` header and can take any format valid in the chosen email sending
27982806
protocol.
27992807

2808+
To build an address with a display name from variable content, see
2809+
:ref:`formatting-addresses`.
2810+
28002811
.. admonition:: Why are my emails sent from a different address?
28012812

28022813
This address is used only for error messages. It is *not* the address that

docs/topics/email.txt

Lines changed: 102 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -495,7 +495,8 @@ email backend API :ref:`provides an alternative
495495
* ``body``: The body text. This should be a plain text message.
496496

497497
* ``from_email``: The sender's address. Both ``fred@example.com`` and
498-
``"Fred" <fred@example.com>`` forms are legal. If omitted, the
498+
``"Fred" <fred@example.com>`` forms are supported (see
499+
:ref:`formatting-addresses`). If omitted, the
499500
:setting:`DEFAULT_FROM_EMAIL` setting is used.
500501

501502
* ``to``: A list or tuple of recipient addresses.
@@ -792,47 +793,112 @@ example::
792793
msg.content_subtype = "html" # Main content is now text/html
793794
msg.send()
794795

795-
Preventing header injection
796-
---------------------------
796+
Safely sending email
797+
--------------------
798+
799+
Any public website that can send email will eventually be targeted by attempts
800+
to abuse it for spam, phishing, or other malicious content. While a complete
801+
discussion of vulnerabilities in sending email is beyond the scope of Django's
802+
documentation, there are many references available on the web. Two good
803+
starting points are:
804+
805+
* Princeton University's guidance on `preventing email abuse in web forms`_.
806+
Although this is an internal reference for users of Princeton's Drupal Site
807+
Builder, nearly all of its advice applies equally to sites built with Django
808+
(or any web framework).
809+
810+
* OWASP's `Email Validation and Verification in Identity Systems Cheat Sheet`_.
811+
This primarily covers using email in authentication contexts. While many of
812+
its recommendations are handled by :mod:`django.contrib.auth` and other
813+
Django features, items like rate limiting and securing email change workflows
814+
are the developer's responsibility.
815+
816+
Thinking through how email might be abused (and taking steps to mitigate it) is
817+
especially important if your site can send to unverified addresses. Features
818+
like newsletter sign-up, contact forms that cc or auto-reply to the sender, and
819+
"share this page" can be attractive targets.
820+
821+
.. _preventing email abuse in web forms:
822+
https://sitebuilder.princeton.edu/site-administration/spam
823+
.. _Email Validation and Verification in Identity Systems Cheat Sheet:
824+
https://cheatsheetseries.owasp.org/cheatsheets/Email_Validation_and_Verification_Cheat_Sheet.html
825+
826+
.. _formatting-addresses:
827+
828+
Formatting email addresses
829+
~~~~~~~~~~~~~~~~~~~~~~~~~~
830+
831+
Email addresses allow a "friendly" display name alongside the ``user@domain``
832+
address. For example, you could include your company name in the
833+
:setting:`DEFAULT_FROM_EMAIL` setting::
834+
835+
DEFAULT_FROM_EMAIL = '"Example, Inc." <contact@example.com>'
836+
837+
The double quotes around ``"Example, Inc."`` are needed so the comma isn't read
838+
as separating two different addresses. A fixed address, written out by hand as
839+
in the example above, is safe, but composing one from variable parts
840+
(especially untrusted input) needs more care.
797841

798-
`Header injection`_ is a security exploit in which an attacker inserts extra
799-
email headers to control the "To:" and "From:" in email messages that your
800-
scripts generate.
842+
.. warning::
801843

802-
The Django email functions outlined above all protect against header injection
803-
by forbidding newlines in header values. If any ``subject``, ``from_email`` or
804-
``recipient_list`` contains a newline (in either Unix, Windows or Mac style),
805-
the email function (e.g. :func:`send_mail`) will raise :exc:`ValueError` and,
806-
hence, will not send the email. It's your responsibility to validate all data
807-
before passing it to the email functions.
844+
Never use string formatting to build an email address from variable parts.
845+
For example, ``f'"{name}" <{email}>'`` is **unsafe**.
808846

809-
If a ``message`` contains headers at the start of the string, the headers will
810-
be printed as the first bit of the email message.
847+
Email address headers have complex syntax rules (much like HTML or SQL), so
848+
constructing them by combining strings creates an injection vulnerability. Even
849+
if you've :class:`validated <.EmailValidator>` the format of ``email``, an
850+
attacker could exploit the ``name`` portion to inject additional addresses.
811851

812-
Here's an example view that takes a ``subject``, ``message`` and ``from_email``
813-
from the request's POST data, sends that to ``admin@example.com`` and redirects
814-
to "/contact/thanks/" when it's done::
852+
To avoid this, always use a well-tested library specifically meant to format
853+
email addresses, like Python's :class:`email.headerregistry.Address` class (the
854+
replacement for the legacy :func:`~email.utils.formataddr` function, which does
855+
not support internationalized domain names).
856+
857+
For example, to include a user's full name when sending them email (where
858+
``user`` is an instance of the default :class:`.User` model)::
815859

816860
from django.core.mail import send_mail
817-
from django.http import HttpResponse, HttpResponseRedirect
818-
819-
820-
def send_email(request):
821-
subject = request.POST.get("subject", "")
822-
message = request.POST.get("message", "")
823-
from_email = request.POST.get("from_email", "")
824-
if subject and message and from_email:
825-
try:
826-
send_mail(subject, message, from_email, ["admin@example.com"])
827-
except ValueError:
828-
return HttpResponse("Invalid header found.")
829-
return HttpResponseRedirect("/contact/thanks/")
830-
else:
831-
# In reality we'd use a form class
832-
# to get proper validation errors.
833-
return HttpResponse("Make sure all fields are entered and valid.")
834-
835-
.. _Header injection: http://www.nyphp.org/phundamentals/8_Preventing-Email-Header-Injection.html
861+
from email.headerregistry import Address
862+
863+
864+
def send_mail_to_user(user, subject, body, from_email=None):
865+
# Safely create an email address with the user's name.
866+
# (addr_spec is the technical term for the user@domain address.)
867+
address = Address(
868+
display_name=user.get_full_name(),
869+
addr_spec=user.email,
870+
)
871+
send_mail(subject, body, from_email, [address])
872+
873+
Django's built-in email backends support using
874+
:class:`~email.headerregistry.Address` objects directly in any address field,
875+
as shown here. So do many custom and third-party email backends. But if this
876+
causes a ``TypeError`` or other problem with a particular backend, use
877+
``str(address)`` to convert the object to a safe, properly formatted string.
878+
879+
Preventing header injection
880+
~~~~~~~~~~~~~~~~~~~~~~~~~~~
881+
882+
`Email header injection`_ is a security exploit in which an attacker
883+
manipulates email headers to change the intended sender or recipients, subject,
884+
or potentially even the entire visible message body.
885+
886+
One type of header injection exploits address header syntax. You are
887+
responsible for preventing this when constructing email addresses from
888+
user-supplied input, as described in :ref:`formatting-addresses` above.
889+
890+
Another (perhaps better understood) attack, CRLF injection, uses carriage
891+
return and line feed characters to insert additional headers into the email.
892+
Django prevents this by raising a :exc:`ValueError` if those characters appear
893+
in any header field when trying to send the message.
894+
895+
Django's CRLF protection relies on using Python's modern
896+
:class:`~email.policy.EmailPolicy` in Django's :meth:`EmailMessage.message`.
897+
Custom email backends that don't call that function, or that call it with the
898+
legacy :data:`~email.policy.compat32` policy, are responsible for implementing
899+
their own CRLF injection prevention.
900+
901+
.. _Email header injection: https://en.wikipedia.org/wiki/Email_injection
836902

837903
.. _topics-sending-multiple-emails:
838904

0 commit comments

Comments
 (0)