[high] fix(email): detect an attachment by its filename, not its disposition - #114
Draft
elhoim wants to merge 1 commit into
Draft
[high] fix(email): detect an attachment by its filename, not its disposition#114elhoim wants to merge 1 commit into
elhoim wants to merge 1 commit into
Conversation
_parse_email_message collected a MIME part only when it declared Content-Disposition: attachment. A payload delivered as `inline`, or with no disposition header at all and only a name= parameter on Content-Type, was therefore invisible: absent from `attachments`, and absent from the has_suspicious_attachment check that is the security purpose of the function. An emailed .exe could be reported as a message with no attachments at all. Both shapes are ordinary rather than exotic -- `inline` is what clients emit for anything they may render, and a bare Content-Type with a name parameter is what several older senders produce. Treat any leaf part carrying a filename as an attachment, in addition to any part that declares the attachment disposition. Nested multipart containers are skipped so a multipart/alternative body cannot be counted as one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
BLUF
.exedelivered asContent-Disposition: inline— or with no disposition header at all — was reported as a message with no attachments, andhas_suspicious_attachmentstayedFalse._parse_email_message. It was blind to exactly the delivery shapes an attacker can choose freely.Content-Disposition: attachment.src/mulder/server/tools/email.pyonly. No new abstraction, no change to any other tool.The bug
Content-Dispositionis a hint from the sender, not a property of the data. A part likecarries an executable and is skipped entirely. So is a part with no disposition header at all, which several older senders emit. In both cases
attachmentscomes back[]andhas_suspicious_attachmentcomes backFalse— the message is reported as clean.inlineis not an exotic choice: it is what mail clients emit for anything they might render, so it does not even look anomalous in transit.The fix
The
is_multipart()skip matters: without it amultipart/alternativecontainer could be counted. Body parts carry no filename, so a plain or HTML body is never mistaken for an attachment — pinned by a test.Widening detection does not widen suspicion: an inline
logo.pngis now correctly listed as an attachment and correctly left non-suspicious. That is also pinned by a test, so the fix cannot be mistaken for making every message look dangerous.Deliberately out of scope
fix/email-parsing(closed #78) bundled this with five other independent defects in the same file — the lexicographic date comparison, RFC 2047 header decoding,_parse_recipientssplitting on a comma inside a quoted display name, HTML-only messages having no searchable body, andCcnever being searched. Each is a separate root cause and is being submitted as its own PR.Attachment filenames are left undecoded here: a name sent as
=?utf-8?B?…?=will still be listed encoded. That is the RFC 2047 concern and belongs with the header-decoding fix, not this one.Relationship to open PR #96 (
fix/readpst-exit-code): that one guards the case where readpst failed. This is the success path. Different functions, no overlap; verified conflict-free withgit merge-tree.Verification
uvx pre-commit run --all-files(new test staged first, so the hooks actually see it) → ruff, ruff-format, mypy all pass.email.pyrestored toorigin/mainand the new tests kept, 4 of 7 fail:The second line is the security consequence in one assertion: a real
.exein the message, and the suspicious-attachment flag reportingFalse.The three that pass on both trees are the narrowness tests — a declared attachment still works, a body-only message gains no phantom attachment, and
multipart/alternativebody parts are not counted — so they pin the fix rather than the bug.Context
Recovered from closed PR #78, which bundled six independent defects behind one title. The rejected outcome framework and
classify_tool_exitare not reintroduced — this is a self-contained fix to attachment detection, per the review on #32:Branched fresh from current
main(2e5432c); the closed branch was not revised in place.🤖 Generated with Claude Code