GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — email.policy.default - gotcha with re-using parsed headers with embedded newlines · Issue #121650 · python/cpython

email.policy.default - gotcha with re-using parsed headers with embedded newlines #121650

New issue
Copy link
New issue
Copy link
Closed
Closed
email.policy.default - gotcha with re-using parsed headers with embedded newlines#121650
Copy link
Labels
topic-emailtype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or errortype-securityA security issueA security issue

Description

@jwhitlock
jwhitlock
opened on Jul 12, 2024
Issue body actions

Bug report

Bug description:

I'm not sure if this is a bug, feature request, or user error. I'm happy to re-file once I know which

If a parsed email header contains a correctly quoted newline, setting an email header to that value will include a newline.

from email import message_from_string
from email.policy import default

email_in = """\
To: [email protected]
From: External Sender <[email protected]>
Subject: Here's an =?UTF-8?Q?embedded_newline=0A?=
Content-Type: text/html; charset=UTF-8
Content-Transfer-Encoding: quoted-printable
MIME-Version: 1.0

<html>
<head><title>An embeded newline</title></head>
<body>
  <p>I sent you an embedded newline in the subject. How do you like that?!</p>
</body>
</html>
"""

msg = message_from_string(email_in, policy=default)
msg = message_from_string(email_in, policy=default)
for header, value in msg.items():
    del msg[header]
    msg[header] = value
email_out = str(msg)
print(email_out)

Output is:

To: [email protected]
From: External Sender <[email protected]>
Subject: Here's an embedded newline

Content-Type: text/html; charset="UTF-8"
Content-Transfer-Encoding: quoted-printable
MIME-Version: 1.0

<html>
<head><title>An embeded newline</title></head>
<body>
  <p>I sent you an embedded newline in the subject. How do you like that?!</p>
</body>
</html>

An email parser will interpret the newline as the start of the message. In this case, the Content-Type and other MIME headers will not be processed, and the email treated as plain text. In other cases, required headers like To may not be processed and the email will not be delivered.

I'd expect an error on setting the value, an error on serializing the EmailMessage to a string, the subject to retain the original encoding, or the newline to be quoted in the serialized version.

Now that we know the behavior, we can process the headers (embed or strip trailing newlines). However, you may see this is a bug, a needed feature, or missing documentation.

More info:

subject's type is a email.headerregistry._UniqueUnstructuredHeader. It has a name, so it is assigned without checking (email.policy.EmailPolicy.header_store_parse()).

The _parse_tree, returned by email._header_value_parser.get_unstructured(), is:

UnstructuredTokenList([ValueTerminal("Here's"), WhiteSpaceTerminal(' '), ValueTerminal('an'), WhiteSpaceTerminal(' '), EncodedWord([ValueTerminal('embedded'), WhiteSpaceTerminal(' '), ValueTerminal('newline\n')])])

A user encountered this for our email relaying service https://relay.firefox.com (mozilla/fx-private-relay#4841). An incoming email to a service address is matched to a user. We re-write the email headers and forward the email to the user's "real" address.

A real email has this subject header:

Subject: The All Over Piercings Wishlist of =?UTF-8?Q?John=2E=0A?=

This is from a European website https://www.alloverpiercings.com. You can create a wishlist and send it to an email address. The subject appears correctly encoded to me, to allow for non-ASCII usernames, with the unfortunate embedded newline. When forwarding this email, using something similar to the code above (but with more header modifications and additions), the embedded newline is turned into a real newline. The rest of the email headers are treated as part of the body. Since the Content-Type and other MIME headers are not processed as headers, the email is treated as a plain text email.

CPython versions tested on:

3.11, 3.12

Operating systems tested on:

macOS

Linked PRs

  • gh-121650 : detect newlines in headers #121812
  • gh-121650: Encode newlines in headers, and verify headers are sound #122233
  • [3.13] gh-121650: Encode newlines in headers, and verify headers are sound (GH-122233) #122484
  • [3.12] gh-121650: Encode newlines in headers, and verify headers are sound (GH-122233) #122599
  • [3.11] gh-121650: Encode newlines in headers, and verify headers are sound (GH-122233) #122608
  • [3.10] gh-121650: Encode newlines in headers, and verify headers are sound (GH-122233) #122609
  • [3.9] gh-121650: Encode newlines in headers, and verify headers are sound (GH-122233) #122610
  • [3.8] gh-121650: Encode newlines in headers, and verify headers are sound (GH-122233) #122611

Activity

Next
  1. jwhitlock
    added
    type-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
    on Jul 12, 2024
  2. Eclips4
    added
    topic-email
    on Jul 12, 2024
  3. jwhitlock commented on Jul 12, 2024

    @jwhitlock
    jwhitlock
    on Jul 12, 2024
    Author
    More actions

    On further investigation, a plain string with a trailing newline has this issue:

    email["Subject"] = "string with newlines\n"
    

    So the "re-use parsed header" is not part of the issue. The problem might be the newline detection in header_store_parse:

    cpython/Lib/email/policy.py

    Lines 131 to 148 in dc03ce7

    def header_store_parse(self, name, value):
    """+
    The name is returned unchanged. If the input value has a 'name'
    attribute and it matches the name ignoring case, the value is returned
    unchanged. Otherwise the name and value are passed to header_factory
    method, and the resulting custom header object is returned as the
    value. In this case a ValueError is raised if the input value contains
    CR or LF characters.
    """
    if hasattr(value, 'name') and value.name.lower() == name.lower():
    return (name, value)
    if isinstance(value, str) and len(value.splitlines())>1:
    # XXX this error message isn't quite right when we use splitlines
    # (see issue 22233), but I'm not sure what should happen here.
    raise ValueError("Header values may not contain linefeed "
    "or carriage return characters")
    return (name, self.header_factory(name, value))

    A single element list is returned by "string with newlines\n".splitlines(), so it can't detect a trailing newline.

  4. ZeroIntensity commented on Jul 13, 2024

    @ZeroIntensity
    ZeroIntensity
    on Jul 13, 2024
    Member
    More actions

    This is a bug (I was able to reproduce this on the CPython main branch), and looks like a minor security problem, considering this:

    An email parser will interpret the newline as the start of the message.

    For example, I could see someone developing an app that does something like this:

    def email_notification(name: str):
        msg = EmailMessage()
        msg.set_content("This is an automatic notification blah blah blah...")
        msg["Subject"] = (
            f"{name} sent you a message!"
        )
        smtp_server.send_message(msg)

    If a user set their name to something like "=?UTF-8?Q?=0A?==?UTF-8?Q?=0A?=This comes before the actual body!", then This comes before the actual body! would precedent the rest of the message. (FWIW, I'm not a security researcher nor a cybersecurity expert, this is speculative.)

    Furthermore, you could use this to inject extra message headers.

  5. basbloemsaat commented on Jul 14, 2024

    @basbloemsaat
    basbloemsaat
    on Jul 14, 2024
    Contributor
    More actions

    It seems to be a bug, or two even.

    msg = email.message.EmailMessage(policy=default)
    msg['Subject'] = 'A 💩 subject\nBcc: [email protected]'
    print(str(msg))

    The above throws a ValueError("Header values may not contain linefeed or carriage return characters"), as expected.

    However the following does not, and inserts an extra newline, thus invalidating some headers:

    msg = email.message.EmailMessage(policy=default)
    msg['Subject'] = 'A 💩 subject\n'
    msg.set_content('This is 💩 the body of the message.\n')
    print(str(msg))

    and by using an utf8 encoded newline, it even inserts an extra header

    msg = email.message.EmailMessage(policy=default)
    msg['Subject'] = 'A 💩 subject=?UTF-8?Q?=0A?=Bcc: [email protected]'
    msg.set_content('This is 💩 the body of the message.\n')
    print(str(msg))

    .

    So, I think two things have to be solved:

    1. newlines at the end should either throw a ValueError, like in the middle, or be stripped, as they are not allowed by the rfc
    2. encoded newlines should also throw a ValueError.

    @encukou : I'll try to fix both during (or after) the EuroPython sprint, ok?

  6. jwhitlock commented on Jul 15, 2024

    @jwhitlock
    jwhitlock
    on Jul 15, 2024
    Author
    More actions

    Thanks @basbloemsaat. Feel free to pick a better title for this issue (or suggest one if I need to change it), or re-file for the individual issues.

  7. ZeroIntensity commented on Jul 15, 2024

    @ZeroIntensity
    ZeroIntensity
    on Jul 15, 2024
    Member
    More actions

    I'm pretty sure this is a security problem, as you can inject extra headers. @Eclips4 what do you think, and could you add the security label?

  8. Eclips4 commented on Jul 15, 2024

    @Eclips4
    Eclips4
    on Jul 15, 2024
    Member
    More actions

    I would like to hear @serhiy-storchaka opinion on this.

  9. encukou
    added
    type-securityA security issueA security issue
    on Jul 27, 2024
  10. encukou
    added 2 commits that reference this issue on Jul 30, 2024

    Merge branch 'main' into pythongh-121650-email-newlines-in-headers

    eb43fe4

    gh-121650: Encode newlines in headers, and verify headers are sound (G…

    0976339
  11. miss-islington
    added a commit that references this issue on Jul 30, 2024

    pythongh-121650: Encode newlines in headers, and verify headers are s…

    a590277
  12. encukou
    added a commit that references this issue on Aug 2, 2024

    pythongh-121650: Encode newlines in headers, and verify headers are s…

    e58aec9
  13. ambv
    added 2 commits that reference this issue on Aug 2, 2024

    [3.11] pythongh-121650: Encode newlines in headers, and verify header…

    f9ddc53

    [3.10] pythongh-121650: Encode newlines in headers, and verify header…

    ff3629b
  14. 11 remaining items

  15. hrnciar
    added 2 commits that reference this issue on Aug 16, 2024

    00435: pythongh-121650: Encode newlines in headers, and verify

    1ee66de

    00435: pythongh-121650: Encode newlines in headers, and verify

    f75f869
  16. blhsing
    added a commit that references this issue on Aug 22, 2024

    pythongh-121650: Encode newlines in headers, and verify headers are s…

    0647947
  17. mcepl
    added a commit that references this issue on Aug 29, 2024

    [CVE-2024-6923] Encode newlines in headers, and verify headers are sound

    3be49f5
  18. ambv
    added 4 commits that reference this issue on Sep 4, 2024

    [3.8] gh-121650: Encode newlines in headers, and verify headers are s…

    b158a76

    [3.11] gh-121650: Encode newlines in headers, and verify headers are …

    f7c0f09

    [3.10] gh-121650: Encode newlines in headers, and verify headers are …

    06f28dc

    [3.9] gh-121650: Encode newlines in headers, and verify headers are s…

    f7be505
  19. encukou commented on Sep 9, 2024

    @encukou
    encukou
    on Sep 9, 2024
    Member
    More actions

    Thank you @jwhitlock for the report, and @basbloemsaat for the initial fix!

    Reacted by Bas Bloemsaat and John Whitlock
  20. encukou
    closed this as completedon Sep 9, 2024
  21. hrnciar
    added a commit that references this issue on Apr 23, 2025

    00435: pythongh-121650: Encode newlines in headers, and verify header…

    94d6287
  22. hroncok
    added a commit that references this issue on Jul 4, 2025

    00435: pythongh-121650: Encode newlines in headers, and verify header…

    40f8c12
  23. frenzymadness
    added a commit that references this issue on Aug 12, 2025

    00435: pythongh-121650: Encode newlines in headers, and verify header…

    7b4affe
  24. hroncok
    added a commit that references this issue on Feb 3, 2026

    00435: pythongh-121650: Encode newlines in headers, and verify header…

    5ed53b9
  25. hugovk
    marked EmailMessage objects break when folding malformed header #132105 as a duplicate of this issue on Oct 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    topic-emailtype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or errortype-securityA security issueA security issue

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      You are visitor 01402503 — GeezerVison does not store the pages you look up.
      Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.