refactor: improve email whitelist matching with proper email extraction
- Extract email address from sender string in `_matches_whitelist` - Add `_extract_email_from_sender` helper function to handle various sender formats - Prioritize email in angle brackets to prevent display name spoofing - Support formats: plain email, "Name <email>", and quoted display names - Add regex validation for extracted email addresses - Fallback to original sender string if no valid email found
frdel committed
Mar 17, 2026 at 10:58 UTC
4b6cc7e2e09f83a314459c00723db57c440b153d
1 file changed
+32
-2
plugins/_email_integration/helpers/imap_client.py
+32
-2
@@ -442,8 +442,38 @@ def _is_noreply(sender: str) -> bool:
442
443
444
def _matches_whitelist(sender: str, whitelist: list[str]) -> bool:
445
- sender_lower = sender.lower()
445
+ sender_email = _extract_email_from_sender(sender.lower())
446
for pattern in whitelist:
447
- if fnmatch(sender_lower, pattern.lower()):
447
+ if fnmatch(sender_email, pattern.lower()):
448
return True
449
return False
450
+
451
+
452
+def _extract_email_from_sender(sender: str) -> str:
453
+ """Extract email address from sender string.
454
+
455
+ Handles formats like:
456
+ - "email@example.com"
457
+ - "Name <email@example.com>"
458
+ - "\"Display Name\" <email@example.com>"
459
+
460
+ Uses content inside angle brackets as authoritative to prevent spoofing
461
+ by fake emails in the display name (e.g., "John ceo@company.com <real@email.com>").
462
+ """
463
+ import re
464
+ # Look for email inside angle brackets - this is the authoritative source
465
+ match = re.search(r"<([^>]+)>", sender)
466
+ if match:
467
+ email = match.group(1).strip()
468
+ # Validate it looks like an email
469
+ if re.match(r"^[^\s<>@]+@[^\s<>@]+\.[^\s<>@]+$", email):
470
+ return email
471
+
472
+ # No angle brackets - extract email from the whole string
473
+ # This handles plain "email@example.com" or malformed input
474
+ email_match = re.search(r"[^\s<>]+@[^\s<>]+\.[^\s<>]+", sender)
475
+ if email_match:
476
+ return email_match.group(0)
477
+
478
+ # Fallback: return the whole string (will likely fail pattern match)
479
+ return sender