harden(mail): address PR #935 round-4 review

No blockers raised; hardening + clarity:
- client/mail.py: URL-encode the caller-supplied attachment_id
  (quote(..., safe="")) — defense-in-depth against path traversal.
- server/mail.py: measure attachment content in UTF-8 bytes (not characters)
  for the size cap and the sentinel message.
- scanner.py: bound _mail_cap_logged (insertion-ordered dict + oldest-first
  eviction at 50k, mirroring _consent_backstop_done) so the cap-log dedup set
  can't leak in a long-running multi-tenant process; reword the cap log to not
  imply MAIL_SCAN_MAX_PER_MAILBOX is operator-tunable (it's the Mail OCS max).
- models/mail.py: comment why GetAttachmentResponse doesn't nest MailAttachment
  (different OCS endpoint shape).
- mail_content.py: document format_mail_addresses' empty-entry skip contract.

_potentially_deleted doc_type-in-key remains tracked as Deck #376 (pre-existing
cross-cutting; reviewer confirmed deferral).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Chris Coutinho
2026-06-20 13:12:15 +02:00
co-authored by Claude Opus 4.8
parent 891d07db12
commit 0856d59956
5 changed files with 54 additions and 17 deletions
+7 -1
View File
@@ -19,7 +19,13 @@ MAIL_SCAN_MAX_PER_MAILBOX = 100
def format_mail_addresses(addrs: list[dict[str, Any]] | None) -> str:
"""Render a list of {label, email} address objects as a display string."""
"""Render a list of {label, email} address objects as a display string.
``None`` and address objects with neither ``label`` nor ``email`` are
skipped (yielding ``""`` for an all-empty list) — IMAP envelope addresses
effectively always carry at least an email, so this only drops malformed
entries rather than losing real recipients.
"""
parts: list[str] = []
for addr in addrs or []:
label = addr.get("label")