From c961098be90d585a83c7f2964a7d6a3bb7abd637 Mon Sep 17 00:00:00 2001 From: Guanlan Dai Date: Wed, 13 May 2026 09:29:40 -0700 Subject: [PATCH] fix(email): strip CR/LF in imap_quote to prevent command injection RFC 3501 prohibits CR and LF inside IMAP quoted strings. Previously, user-controlled inputs (from_contains, subject_contains, login credentials) were passed through imap_quote() without removing newline characters, allowing an attacker to inject arbitrary IMAP commands by embedding \r\n in those fields. Fix: sanitize the input by stripping \r and \n before escaping and quoting. Add two unit tests: - test_imap_quote_strips_crlf_injection: verifies newlines are removed - test_imap_quote_escapes_backslash_and_quote: verifies existing escape behaviour is preserved --- src/email.rs | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/email.rs b/src/email.rs index 1427510..4a49000 100644 --- a/src/email.rs +++ b/src/email.rs @@ -660,7 +660,10 @@ fn normalize_search_term(value: &str) -> Option { } fn imap_quote(value: &str) -> String { - let escaped = value.replace('\\', "\\\\").replace('"', "\\\""); + // RFC 3501: quoted strings must not contain CR or LF. + // Strip them before escaping to prevent IMAP command injection. + let sanitized = value.replace('\r', "").replace('\n', ""); + let escaped = sanitized.replace('\\', "\\\\").replace('"', "\\\""); format!("\"{escaped}\"") } @@ -1149,4 +1152,22 @@ mod tests { assert!(!is_gmail_imap_host("imap.mail.yahoo.com")); assert!(!is_gmail_imap_host("imap.example.com")); } + + #[test] + fn test_imap_quote_strips_crlf_injection() { + // A payload that would inject an extra IMAP command if \r\n were not removed. + let malicious = "x\r\nA001 STORE 1 +FLAGS (\\Deleted)"; + let quoted = imap_quote(malicious); + assert!(!quoted.contains('\r'), "CR must be stripped"); + assert!(!quoted.contains('\n'), "LF must be stripped"); + // The output should still be a valid quoted string. + assert!(quoted.starts_with('"')); + assert!(quoted.ends_with('"')); + } + + #[test] + fn test_imap_quote_escapes_backslash_and_quote() { + let quoted = imap_quote(r#"say "hello" \ world"#); + assert_eq!(quoted, r#""say \"hello\" \\ world""#); + } }