Attachment Writes Are a Trust Boundary
Attachment writes are a trust boundary
Attachments look boring because they are "just files." That is exactly why they are dangerous. A file write turns parsed remote input into a local artifact with a path, name, permissions, opener behavior, and future meaning to other programs.
In mxr, the attachment path crosses several trust boundaries at once:
- email content provides filenames and bytes;
- providers supply attachment identifiers and MIME metadata;
- local clients can ask the daemon to materialize or save an attachment;
- the operating system decides who can read the resulting file;
- the default opener may hand the file to another app.
So the safety rule is simple: an attachment write is not done when the bytes are written. It is done when the path, name, permissions, size, and opener boundary are all boring.
The checklist
- Strip path separators, NULs, and control characters from filenames.
- Reject or rewrite platform-reserved names, including Windows device names like
CON,AUX,NUL,COM1, andLPT1. - Keep path construction server-side where possible.
- If a client supplies a destination, require an allowlisted root.
- Reject parent-directory components before write time.
- Reject symlink destinations.
- Set private file permissions after writing (
0600on Unix). - Cap remote inline assets before buffering them into memory or writing them.
This is not only about malicious email. It is also about boring accidents: a filename that works on macOS but breaks on Windows, a shared host where 0644 leaks a private PDF, or a client bug that tries to save into ~/.ssh.
What mxr now does
Validated against code on 2026-05-28:
sanitized_attachment_filenamefalls back for blank and Windows-reserved names.safe_attachment_destinationrejects parent traversal, symlink targets, missing filenames, and destinations outside safe roots.- Attachment cache writes, export writes, downloads, data-URI assets, and remote HTML assets set Unix
0600permissions. - Remote HTML assets are capped at 25 MB whether the server sends
Content-Lengthor streams without one. - Tests cover Windows-reserved filename fallback and downloaded attachment permissions.
2026-06-24 follow-up: cache filenames now always include the attachment id. That
closes the quiet collision case where two safe-looking attachments share a name,
and it keeps weird remote names like ., .., or CON.txt boring after
sanitization. The user-visible filename remains metadata; the cache path is a
private local artifact.