fix(email): harden adapter against malformed IMAP responses

Salvage of #2794 by @CharmingGroot, ported to the relocated
plugins/platforms/email/adapter.py:

- Guard raw_email = msg_data[0][1] against IndexError/TypeError and
  non-bytes payloads. UIDs are added to _seen_uids before fetch, so an
  exception mid-batch permanently skipped every remaining message in
  the batch — now the bad message is logged and skipped instead.
- Message-ID domain generation falls back to 'localhost' when
  EMAIL_ADDRESS lacks '@' (now via a shared _message_id_domain() helper
  covering all 3 send paths; the PR fixed 2 of 3).
This commit is contained in:
CharmingGroot 2026-07-02 02:35:32 -07:00 committed by Teknium
parent c43aa6301d
commit 88bd1c01e1
3 changed files with 142 additions and 4 deletions

View file

@ -673,7 +673,25 @@ class EmailAdapter(BasePlatformAdapter):
if status != "OK":
continue
raw_email = msg_data[0][1]
# IMAP fetch can return unexpected structures (e.g. a
# single bytes item instead of a list of tuples). Guard
# against IndexError / TypeError so one malformed response
# doesn't abort the batch — the UID is already in
# _seen_uids, so an abort would permanently skip the
# remaining messages in this batch.
try:
raw_email = msg_data[0][1]
except (IndexError, TypeError):
logger.warning(
"[Email] Unexpected IMAP response structure for UID %s, skipping",
uid,
)
continue
if not isinstance(raw_email, (bytes, bytearray)):
logger.warning(
"[Email] Non-bytes IMAP payload for UID %s, skipping", uid
)
continue
msg = email_lib.message_from_bytes(raw_email)
sender_raw = msg.get("From", "")
@ -890,6 +908,16 @@ class EmailAdapter(BasePlatformAdapter):
logger.error("[Email] Send failed to %s: %s", chat_id, e)
return SendResult(success=False, error=str(e))
def _message_id_domain(self) -> str:
"""Domain part for generated Message-IDs.
EMAIL_ADDRESS may lack an ``@`` (misconfiguration); fall back to
``localhost`` instead of crashing send with an IndexError.
"""
if "@" in self._address:
return self._address.rsplit("@", 1)[-1] or "localhost"
return "localhost"
def _send_email(
self,
to_addr: str,
@ -915,7 +943,7 @@ class EmailAdapter(BasePlatformAdapter):
msg["References"] = original_msg_id
msg["Date"] = formatdate(localtime=True)
msg_id = f"<hermes-{uuid.uuid4().hex[:12]}@{self._address.split('@')[1]}>"
msg_id = f"<hermes-{uuid.uuid4().hex[:12]}@{self._message_id_domain()}>"
msg["Message-ID"] = msg_id
msg.attach(MIMEText(body, "plain", "utf-8"))
@ -1028,7 +1056,7 @@ class EmailAdapter(BasePlatformAdapter):
msg["References"] = original_msg_id
msg["Date"] = formatdate(localtime=True)
msg_id = f"<hermes-{uuid.uuid4().hex[:12]}@{self._address.split('@')[1]}>"
msg_id = f"<hermes-{uuid.uuid4().hex[:12]}@{self._message_id_domain()}>"
msg["Message-ID"] = msg_id
if body:
@ -1108,7 +1136,7 @@ class EmailAdapter(BasePlatformAdapter):
msg["References"] = original_msg_id
msg["Date"] = formatdate(localtime=True)
msg_id = f"<hermes-{uuid.uuid4().hex[:12]}@{self._address.split('@')[1]}>"
msg_id = f"<hermes-{uuid.uuid4().hex[:12]}@{self._message_id_domain()}>"
msg["Message-ID"] = msg_id
if body: