Skip to content

Reject NUL, CR and LF in _raw_command arguments before sending - #658

Merged
mjs merged 4 commits into
mjs:masterfrom
HardMax71:fix/reject-control-chars-in-raw-command
Sep 27, 2026
Merged

mjs merged 4 commits into
mjs:masterfrom
HardMax71:fix/reject-control-chars-in-raw-command

Conversation

@HardMax71

Copy link
Copy Markdown
Contributor

Fixes #657

_raw_command now checks every argument before anything is sent: NUL is rejected everywhere, and CR or LF are rejected in any argument that would go on the command line. Values that are sent as literals (8-bit, or wrapped in _literal) may still carry CR and LF, since a literal can hold them. The check runs before the first send on purpose: a bad argument found after a literal was already sent would leave the server waiting inside a half-sent command. The existing "args must be bytes" check moved into the same pass.

This is the same set of characters CPython rejects in imaplib._command (python/cpython#143921); that check does not reach _raw_command, which writes to the socket directly.

Tests in tests/test_imapclient.py: CRLF in a line argument via _raw_command and via search() raises ValueError with _imap.send never called; NUL inside a literal is rejected; CRLF inside a _literal still goes out as a literal. The three reject cases fail on master. Full suite 267 OK; black, isort, flake8, pylint and mypy clean.

CR and LF end the command line on the wire, so an argument carrying
them, for example a search criterion built from a sender-controlled
header, is run by the server as extra commands. _raw_command bypasses
imaplib._command, so CPython's check for the same characters does not
apply here. Check every argument before the first send, so a bad one
never leaves a half-sent command; 8-bit values still go as literals,
which may carry CR and LF. Fixes mjs#657
Copilot AI lite review requested due to automatic review settings September 10, 2026 19:20

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@mjs mjs left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this! It's close, just a few little things.

Comment thread imapclient/imapclient.py Outdated
Comment thread imapclient/imapclient.py Outdated
literal is now exported so callers can wrap a value that carries CR or LF,
as the new error message suggests. _literal stays as an alias for code that
imported the old name. The argument check iterates with itertools.chain
instead of building a new list.
search() quoted any value with a space, so a literal holding CR or LF came
out as a _quoted string and the new check rejected it, even though the
error message says to wrap the value in literal.
Same check as the regex, written like the NUL check next to it.

@mjs mjs left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@mjs
mjs merged commit fe29bad into mjs:master Sep 27, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

search criteria containing CR LF go on the wire inline and become extra IMAP commands

3 participants