Skip to content

Bare except in move() silently masks connection and auth errors #30

Description

@FernandoCelmer

Description

The MailBox.move() method catches all exceptions when attempting UID MOVE, then silently falls back to COPY + DELETE + EXPUNGE. This means real errors — network failures, authentication issues, quota exceeded, permission denied — are swallowed, and the user has no idea the operation didn't succeed as expected.

Location

email_profile/clients/imap/mailbox.py — lines 189–196

Current Behavior

def move(self, target: MessageLike, destination: str) -> None:
    _state(self._client.select(_quote(self.name)))
    uid = _uid_of(target)
    try:
        _state(self._client.uid("MOVE", uid, destination))
    except Exception:  # ← catches EVERYTHING
        _state(self._client.uid("COPY", uid, destination))
        _state(self._client.uid("STORE", uid, "+FLAGS", "\\Deleted"))
        _state(self._client.expunge())

Example of masked failure

with Email("user@gmail.com", "pw") as app:
    # Server is rate-limiting, but move() catches the RateLimited
    # exception and tries COPY — which also fails silently
    app.inbox.move(uid, "Archive")
    # No error raised, but the message was never moved

Another scenario

# Network drops mid-operation: MOVE fails, COPY fails too,
# but the exception from COPY is raised — confusing traceback
# points to COPY instead of the real issue (network)

Expected Behavior

Only catch the specific exception that indicates the server doesn't support MOVE (RFC 6851). Let all other errors propagate normally.

Suggested Fix

import imaplib

def move(self, target: MessageLike, destination: str) -> None:
    _state(self._client.select(_quote(self.name)))
    uid = _uid_of(target)
    try:
        _state(self._client.uid("MOVE", uid, destination))
    except imaplib.IMAP4.error:
        # Server doesn't support MOVE — fall back to COPY + DELETE
        _state(self._client.uid("COPY", uid, destination))
        _state(self._client.uid("STORE", uid, "+FLAGS", "\\Deleted"))
        _state(self._client.expunge())

Impact

Who: Any user calling move(), MailBox.move(), or any code path that triggers a move operation.

Severity: Critical — silent data loss. Users believe the message was moved, but it may still be in the original mailbox (or worse, deleted from source without being copied).

Priority

Critical

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingcriticalCritical severity — runtime failures, data loss, security

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions