Skip to content

gh-159475: Make parseaddr() reject inline comments in strict mode - #159618

Open
D3e4mlike wants to merge 1 commit into
python:mainfrom
D3e4mlike:gh-159475-parseaddr-inline-comment
Open

D3e4mlike wants to merge 1 commit into
python:mainfrom
D3e4mlike:gh-159475-parseaddr-inline-comment

Conversation

@D3e4mlike

Copy link
Copy Markdown

Supersedes #159477, which was accidentally closed and could not be reopened because the branch was force-pushed afterward (a GitHub restriction). The diff and description are otherwise identical.

Fixes #159475.

Summary

email.utils.parseaddr() and getaddresses(), in strict mode (the default
since the CVE-2023-27043 fix), still silently drop a comment that is embedded
inside a dot-atom and splice the surrounding atoms together:

>>> from email.utils import parseaddr
>>> parseaddr('u(comment)ser@d.com', strict=True)
('comment comment', 'user@d.com')

email.headerregistry.Address('u(comment)ser@d.com') rejects this input
(local-part is not dot-atom / quoted-string / obs-local-part), so a caller that
validates or authorizes with parseaddr and then hands the original string to
a strict component gets a subject mismatch — the same class as CVE-2019-11340
(Matrix Sydent) and the motivation behind CVE-2023-27043.

Change

Add an internal _inline_comment flag to the lenient AddrlistClass parser in
Lib/email/_parseaddr.py. It is set when a comment is consumed immediately
before another atom inside an addr-spec (or its domain) — exactly the position
where the comment would be silently dropped and two atoms spliced. In strict
mode, parseaddr / getaddresses now return ('', '') / [('', '')] when
the flag is set.

  • Leading and trailing comments remain accepted (they are valid CFWS).
  • A comment inside a quoted string remains literal content (not a comment).
  • strict=False keeps the historical lenient behavior unchanged.
  • Existing strict rejections (domain-literal, multiple addresses, unbalanced
    parentheses) are unchanged.

Why it matters

parseaddr / getaddresses is not deprecated and is still widely used in
security-sensitive paths: CPython's own smtplib, Django, Sentry, Odoo (which
explicitly opts into strict=False), and Ansible all call it. "Legacy" here
means a compat API rather than an abandoned one. The minimal ask is that strict
mode fail closed instead of silently inventing an addr-spec that no strict
reader would accept.

Tests

Added test_parseaddr_inline_comment to Lib/test/test_email/test_email.py,
covering local-part and domain splicing, empty and nested comments,
getaddresses parity, and the still-accepted leading / trailing / quoted cases.

email.utils.parseaddr() and getaddresses(), in strict mode (the default),
silently dropped a comment embedded inside a dot-atom and spliced the
surrounding atoms together (e.g. 'u(comment)ser@d.com' -> 'user@d.com').
Strict mode now returns ('', '') for such addresses; strict=False keeps
the historical lenient behavior.

Signed-off-by: ZHOU Zijun <zijunzhou03222@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

email.utils.parseaddr(strict=True) silently splices inline comments in the local-part

1 participant