> ## Documentation Index
> Fetch the complete documentation index at: https://docs.oximail.ch/llms.txt
> Use this file to discover all available pages before exploring further.

# Attachment policy

> What the box checks in the attachments of incoming and outgoing mail before it answers 250: executables by their octets, a list of extensions, macros, the content of archives and Office containers. Modes observe and enforce, rule by rule; what it does not check.

Before it answers `250` to a message, the box reads every attachment and applies a policy on file types (ADR-155). It is a policy, not a malware scanner: there is no signature engine, no database of hashes, and the judgement opens no connection.

The public page [/postmaster?p=AttachmentPolicy](https://oximail.ch/postmaster?p=AttachmentPolicy) is what a sender whose message was refused reads.

## Where it applies

| Door | What happens |
| - | - |
| Port 25, the backup MX, submission (587/465) | The message is refused with `550 5.7.1 attachment policy: <rule> [<name>] queue=<id>` when a rule in `enforce` applies. Nothing is stored and this box generates no bounce. |
| Upload (`/jmap/upload`, `/jmap/upload-tenant`) | The octets are judged once, without a name, and the verdict is kept with the blob. An upload is never refused. |
| Sending from a JMAP client (`Email/set` create) | The attachments are judged on their octets and their names. A refusal is an `invalidProperties` error: `invalid arguments: attachments: attachment policy: <rule> [<name>]`. |
| Chat message (create and edit) | The names are judged with the verdict kept at upload. |
| `Email/import`, IMAP `APPEND` | The message is already the user's: a log line and the counter, never a refusal. |
| The Drive | Refuses nothing, since it is mounted as a network drive. A file whose octets are an executable is recorded and served as `application/octet-stream`, whatever its name. |

## The rules

| Rule | What triggers it |
| - | - |
| `executable-type` | The octets are an executable or a disk image, whatever the file is called (PE, ELF, Mach-O, shortcuts, CHM, CAB, Windows installers, ISO/UDF, VHD/VHDX/DMG/VMDK, OneNote, app packages). This floor is not a list and cannot be removed. |
| `listed-extension` | A name the part declares ends with an extension on the list. |
| `double-extension` | A listed extension hides in a name: `invoice.pdf.exe`. |
| `macro` | An Office document declares macros: a VBA project in an OOXML package, a VBA storage in a `.doc` or `.xls`. |
| `archive-content` | A file inside a container falls under the rules above: zip (read through its central directory, nothing decompressed), tar, gzip, an object packaged in an Office document, an attachment of an Outlook `.msg`, an attachment in a `winmail.dat`. |
| `encrypted-archive` | An archive is password-protected. |
| `name-changed` | A name would change when Windows saves it (NUL, control or direction character, path, trailing dots or spaces). |
| `not-inspectable` | An archive the box cannot open (7z, RAR), an archive inside an archive, a split archive, a container whose structure is broken. |
| `budget`, `depth`, `mime-unverifiable` | The structure could not be read within the bounds: 512 parts per message, 10 000 archive entries per message, a 1 MiB zip directory, a 64 KiB `[Content_Types].xml`. |

Every name a part declares is judged, not only the one a mail client shows.

## Configuration

```toml theme={null}
[attachment_policy]
mode = "observe"                  # off | observe | enforce, for every rule
refuse_encrypted_archives = true  # false turns the encrypted-archive rule off
extensions_add = []               # extensions added to the list (lowercase, no dot)
extensions_remove = []            # extensions removed from it, e.g. "7z"

[attachment_policy.rules]         # one mode per rule, by its name
executable-type = "enforce"
```

A new box starts in **`observe`**: a message a rule would refuse is delivered, and a log line plus the counter `oximail_attachment_verdicts_total{reason, door, action="would_refuse"}` record it. Arm the rules one by one once the counters show what they touch: executables first, archives and macros next, unreadable containers and MIME anomalies last. An unknown rule name or a list entry that is not canonical refuses the boot.

A tenant can override `attachment_policy.mode` and `attachment_policy.refuse_encrypted_archives` with `oximail config set-override`. A message to several tenants follows the strictest of them. There are no exceptions per sender.

## Checking

* `oximail doctor` (check `attachment_policy`) reports the mode, the per-rule modes, the list version and its SHA-256, and the tenant overrides. A backup MX must carry the same list as its primary: compare the two SHA-256.
* `oximail attachment check --file <path> [--name <name>]` or `--eml <path>` reproduces the box's verdict on a file or on a message a sender forwarded, with no server and no network. Exit code 0: nothing found; 2: something the policy refuses (`--json` says whether the box enforces it or only observes); 3: the input could not be read.

## What it does not check

* Malware as such: documents that carry an exploit without macros, and anything that carries a link or a QR code (PDF, HTML, SVG, Office documents).
* Mail encrypted end to end (S/MIME, OpenPGP), which is delivered unchecked.
* A second level of archive, and the content of 7z: both are `not-inspectable`.

Workstations keep their own protection. The trace of a refusal holds the part's SHA-256, size and type, never its content and never the raw name.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.