File Extensions
File Extensions
Admin path: Content Checks > File Extensions
(view_file_extensions.cfm,
inc/get_file_extensions.cfm,
inc/update_amavis_config_files.cfm).
This page maintains the catalogue of attachment file extensions
that Amavis can match on. Each entry is a single extension such as
.exe, .docm, or .iso paired with a description and a sensitivity
flag (Standard vs. High Risk). The page itself does not block anything
— it only registers extension candidates. The block / allow decision
is taken by a File Rule that bundles extensions into
a named ruleset, which is then applied to recipients via an SVF
policy on Anti-Spam Settings. File Extensions
is the building-block page; File Rules and SVF Policies are where the
ruleset is composed and bound to traffic.
The extension catalogue ships with a system-managed list of common
high-risk types (.exe, .scr, .pif, .com, .bat, .vbs,
.js, .jar, .ps1, and dozens more) that cannot be deleted from
the UI. Operators add custom extensions on top — typically Office
macro-enabled types in environments that don't allow macros, archive
formats they want to surface separately, or new attack-surface file
types as they appear in the wild.
Where File Extensions sits
+---------------------------------------+
File Extensions | files table |
(this page) -----> | id, file ("exe"), description, |
| type ("EXT" | "EXT-HIGH"), |
| system ("YES"/"NO"), |
| allow ("[qr'.\.(exe)$'i => 0]"), |
| ban ("[qr'.\.(exe)$'i => 1]") |
+---------------+-----------------------+
|
v
+---------------------------------------+
| File Rules |
| bundle extensions into named |
| rulesets with per-extension |
| allow / ban / priority |
+---------------+-----------------------+
|
v
+---------------------------------------+
| Anti-Spam Settings (SVF Policies) |
| bind a File Rule to recipient(s) |
+---------------+-----------------------+
|
v
+---------------------------------------+
| Amavis 50-user.HERMES |
| @banned_filename_re emitted per |
| rule on every save chain |
+---------------------------------------+
Amavis enforces the resulting @banned_filename_re regex sets at
content-filter time inside hermes_mail_filter. A matched extension
triggers Amavis's final_banned_destiny action (D_BOUNCE,
D_DISCARD, or D_PASS — set globally on
Anti-Spam Settings).
What "matched" means in Amavis
The stored allow / ban snippets are case-insensitive regexes anchored to the end of the filename:
[qr'.\.(exe)$'i => 1] (ban; case-insensitive)
[qr'.\.(exe)$'x => 0] (allow; case-sensitive)
This means:
invoice.exematches.exeInvoice.EXEmatches.exe(because theimodifier is set by default on Add)invoice.pdf.exematches.exe(the trailing extension is the one Amavis tests)invoice.exe.pdfdoes not match.exe— it matches.pdf, and the trailing-extension rule is the only one that fires
The double-extension confusion case (invoice.pdf.exe) is the
historic reason this list exists. Amavis sees the real trailing
extension; the user sees only the displayed-name prefix and a
familiar icon.
The page
A page guide callout, an Add Extensions card with a bulk textarea, a Custom File Extensions DataTable (editable / deletable), and a Read-Only System File Extensions DataTable (the shipped list).
Add File Extensions card
| Field | Stored as | Notes |
|---|---|---|
| File Extensions | files.file + files.description |
One per line; format .ext description. The leading dot is stripped on save (so the row stores exe, not .exe); the description is auto-prefixed with (.ext) so the DataTable shows (.docm) Microsoft Word Macro-Enabled Document regardless of how the operator typed it |
| Extension Type | files.type |
EXT (Standard) or EXT-HIGH (High Risk). Purely a classification tag for the UI badges — Amavis treats both the same |
| Case Sensitivity | drives which template is rendered into files.allow / files.ban |
Insensitive (default, recommended) uses _insense templates with the i regex modifier; sensitive uses _sense templates with x only — for environments where you want .EXE to differ from .exe |
The handler line-splits the textarea on either LF or CRLF, strips whitespace, validates each entry, and inserts the valid ones. Per entry it checks:
- The extension starts with
. - The extension matches
^[.][a-zA-Z0-9\-\.\_]+$(alphanumeric, dash, period, underscore — nothing else) - The description is non-blank (required)
- No row with the same
filealready exists in theEXT/EXT-HIGHtype space (a.docmcannot exist as both Standard and High Risk)
Each rejected line is collected into a per-row error list that
surfaces in the partial-success alert; the valid entries still
insert. The (.ext) prefix on the description is auto-prepended so
the catalogue stays self-describing regardless of how the operator
typed the row.
Custom File Extensions DataTable
| Column | Source |
|---|---|
| (checkbox) | Selection for bulk Delete Selected |
| Extension | .<files.file> (the leading dot is displayed in the UI even though it isn't stored) |
| Description | files.description |
| Actions | Per-row Delete button (single-row confirm) |
The DataTable shows only rows with system = 'NO' and excludes
type = 'CUSTOM-EXPRESSION' rows (those belong to
File Expressions, which uses the same files
table with a different type discriminator).
System File Extensions DataTable (read-only)
The shipped catalogue — every row from files where system = 'YES'
and type IN ('EXT', 'EXT-HIGH'). These rows are filtered out of
every DELETE path on this page (AND system = 'NO' is part of every
DELETE query). The UI gives them no checkbox and no Delete button;
attempting a forged POST that targets a system row surfaces alert
m = 11 and is rejected.
Standard rows get an "Info" badge, High Risk rows get a "Danger" badge. The badge is cosmetic — Amavis treats both the same as banned-extension candidates once they're wired into a File Rule.
Foreign-key guard on delete
A custom extension cannot be deleted while it is referenced by any File Rule. The single-row Delete handler runs:
SELECT COUNT(*) AS cnt FROM file_rule_components
WHERE file_id = :id
If cnt > 0, the delete is refused with alert m = 10 and the
DataTable shows the offending rule name(s) ("This file extension is
used in the following File Rule(s): HighRisk-block"). The
operator's path is to open File Rules, remove the extension from
the rule, then come back here and delete it.
Bulk Delete applies the same guard per-id and accumulates partial results — the success alert reports "N deleted, M skipped" with the skipped rows' rule names attached so the operator knows exactly what to unwire first.
Save and apply flow
1. View page submits action="add_entries" | "delete" | "bulk_delete"
2. For each valid entry:
a. Read the case-sensitive/insensitive allow + ban templates
from /opt/hermes/scripts/file_allow_{sense|insense} and
file_deny_{sense|insense}
b. Substitute THE-EXTENSION placeholder with the (dot-stripped)
extension name
c. INSERT INTO files (file, description, type, system, allow, ban)
3. If at least one row was added or deleted:
a. update_amavis_config_files.cfm:
- Read /opt/hermes/conf_files/50-user.HERMES (template)
- Substitute SERVER-NAME, SERVER-DOMAIN, sa-spam-subject-tag,
final-virus-destiny, final-banned-destiny, final-spam-destiny,
final-bad-header-destiny, enable-dkim-verification,
enable-dkim-signing placeholders from spam_settings
- Render every File Rule's components into an
@banned_filename_re block (per-rule, in priority order,
using the allow/ban regex stored on each files row)
- Substitute HERMES-USERNAME / HERMES-PASSWORD from
/opt/hermes/creds/ for the Amavis MySQL lookup
- Back up /etc/amavis/conf.d/50-user -> 50-user.HERMES,
move rendered file into place
b. docker exec hermes_mail_filter /etc/init.d/amavis force-reload
(30-second timeout)
4. session.m = 1 (add) | 2 (single delete) | 12 (bulk delete)
Amavis is reloaded with force-reload rather than restarted —
the daemon re-reads 50-user without dropping connections, and
mail in flight is not interrupted. The full container restart that
Anti-Spam Settings and
Score Overrides trigger is not needed here
because no SpamAssassin state is being touched.
The reload step is wrapped in cftry/cfcatch with comment "Log
but don't block — extensions were added" — if the reload itself
fails, the DB rows are already in place and the next save (or
manual force-reload) will re-render. The page does not roll back
on reload failure.
Failure semantics
| Alert | Trigger |
|---|---|
m = 1 |
Add Extensions completed (with entries_added / entries_skipped / entry_errors set on session for the per-row breakdown alert) |
m = 2 |
Single Delete succeeded; Amavis reloaded |
m = 10 |
Single Delete refused — the extension is wired into at least one File Rule (rule names surfaced in the alert) |
m = 11 |
Attempt to delete a system row (system = 'YES') — refused at the DB query |
m = 12 |
Bulk Delete completed (with bulk_deleted / bulk_skipped / bulk_errors set on session) |
m = 30 |
Add submitted with an empty textarea |
The per-row error list is HTML-rendered into the alert body so the operator sees every rejection at once ("Must start with dot: foo", "Invalid characters: .x@y", "Description required: .docm", "Duplicate: .exe"). No row is silently dropped without an explanation in the alert.
Files and containers touched
| Path | Owner | Role |
|---|---|---|
config/hermes/var/www/html/admin/2/view_file_extensions.cfm |
hermes_commandbox |
The page (validation + bulk add + DataTables + Amavis reload) |
config/hermes/var/www/html/admin/2/inc/get_file_extensions.cfm |
hermes_commandbox |
Loads custom + system rows for the two DataTables |
config/hermes/var/www/html/admin/2/inc/update_amavis_config_files.cfm |
hermes_commandbox |
Renders 50-user from template + File Rules (called on every change) |
config/hermes/opt/hermes/scripts/file_allow_insense / file_allow_sense |
hermes_commandbox |
Allow-regex templates with THE-EXTENSION placeholder |
config/hermes/opt/hermes/scripts/file_deny_insense / file_deny_sense |
hermes_commandbox |
Ban-regex templates with THE-EXTENSION placeholder |
config/hermes/opt/hermes/conf_files/50-user.HERMES |
hermes_commandbox (read) -> hermes_mail_filter (live /etc/amavis/conf.d/50-user) |
Canonical Amavis template; receives the rendered @banned_filename_re blocks |
/etc/amavis/conf.d/50-user |
hermes_mail_filter |
Live Amavis config; reloaded with force-reload on every save |
files table, type IN ('EXT','EXT-HIGH') |
hermes_db_server (hermes DB) |
Source of truth for the catalogue (system + custom) |
file_rule_components table |
hermes_db_server (hermes DB) |
Cross-reference checked by the delete guard |
hermes_mail_filter container |
— | Hosts Amavis; receives force-reload (not restart) on every change |
Related
- File Expressions — sibling page for full
regex patterns against any filename (not just extension); rows
live in the same
filestable undertype = 'CUSTOM-EXPRESSION' - File Rules — bundles extensions and expressions into named, prioritised rulesets; the consumer of every row this page creates
- Message Rules — content-level SpamAssassin rules (header / body / regex) — the body / header equivalent of what File Extensions does for attachment names
- Anti-Spam Settings — defines
final_banned_destiny(what Amavis does with a banned-extension match) and binds File Rules to recipients via SVF Policies - Antivirus Settings — ClamAV runs in the same Amavis pass; a virus verdict on the same attachment overrides the banned-extension result
- Score Overrides — sibling Amavis tuning page; both write into Amavis configuration but extension blocks are categorical (matched -> banned) where SA rules are weighted
- Perimeter Checks — none of this matters for connections that never make it past the SMTP-time perimeter
- Message History — a banned-extension
rejection appears with Type
Bannedand the matched extension surfaced in the detail view - System Logs — Amavis logs the
matched regex as
Blocked BANNED (.exe,.bat,...)on theamavis[...]:line