You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
smtplib.quoteaddr() returns malformed angle-bracket address for input '<' #145552
smtplib.quoteaddr() formats addresses for SMTP MAIL FROM: and RCPT TO: commands per RFC 821, which requires angle-bracket format (<addr>). It first delegates to email.utils.parseaddr(); when parsing succeeds, the result is correctly wrapped in <...>.
When parseaddr fails — which happens for inputs that don't resemble any recognizable email address format, like '<', '< ', or '@' — a fallback path kicks in. The fallback checks if the input starts with < and returns it verbatim, without verifying it also ends with >. This produces structurally invalid output (e.g. MAIL FROM:< instead of MAIL FROM:<>).
The existing code intentionally chooses to be lenient with unparseable input rather than raising an exception (the comment reads "use it as is and hope for the best"). Given that design choice, the output should at least be structurally valid — a half-open < with no > is neither rejecting bad input nor producing well-formed protocol output.
What should happen to a string like <<foo@? Do you know how typical smtp servers respond to such malformed addresses? Out of curiosity, did you encounter this in a "real" situation, or is this a "code inspection" or fuzzing type discovery?
Looking at the code, I think the current logic is: if it starts with a '<' then assume it is a valid address. If not, assume we must quote it with '<' (and hope for the best). I wonder if the better response to there not being a trailing '>' would be to either do the '<' quoting anyway or raise an error. Raising an error if parseaddr can't handle it might have been the best choice to begin with, but that ship sailed long ago.
After a little history checking, I see I am the one that added that '<' check...the older version of the code would just have wrapped the unparseable address in <> unconditionally. Therefore I think we should return to that behavior, as it looks like I introduced a bug with that commit.
Turns out unconditional wrapping doesn't quite work — parseaddr('<>') returns ('', ''), so the null sender <> would get double-wrapped to <<>>, producing MAIL FROM:<<>> instead of MAIL FROM:<>. This breaks testSendNullSender in CI.
Updated the fix to keep the startswith('<') check but add an endswith('>') guard. Already-bracketed input like <> passes through, half-open input like < falls through to wrapping.
Activity
bitdancer commentedon Mar 6, 2026
What should happen to a string like
<<foo@? Do you know how typical smtp servers respond to such malformed addresses? Out of curiosity, did you encounter this in a "real" situation, or is this a "code inspection" or fuzzing type discovery?Looking at the code, I think the current logic is: if it starts with a '<' then assume it is a valid address. If not, assume we must quote it with '<' (and hope for the best). I wonder if the better response to there not being a trailing '>' would be to either do the '<' quoting anyway or raise an error. Raising an error if parseaddr can't handle it might have been the best choice to begin with, but that ship sailed long ago.
After a little history checking, I see I am the one that added that '<' check...the older version of the code would just have wrapped the unparseable address in
<>unconditionally. Therefore I think we should return to that behavior, as it looks like I introduced a bug with that commit.stefanzetzsche commentedon Mar 10, 2026
Not a real-world scenario — found via fuzzing. Happy to restore the original unconditional wrapping. I'll update the PR.
stefanzetzsche commentedon Mar 10, 2026
Turns out unconditional wrapping doesn't quite work —
parseaddr('<>')returns('', ''), so the null sender<>would get double-wrapped to<<>>, producingMAIL FROM:<<>>instead ofMAIL FROM:<>. This breakstestSendNullSenderin CI.Updated the fix to keep the
startswith('<')check but add anendswith('>')guard. Already-bracketed input like<>passes through, half-open input like<falls through to wrapping.khalidsaidi commentedon Mar 25, 2026
Export wave-2 takeover patches (zipfile pythongh-121639, smtplib pyth…