# Antivirus Settings

# Antivirus Settings

Admin path: **Content Checks > Antivirus Settings**
(`view_antivirus_settings.cfm`, `inc/get_antivirus_settings.cfm`,
`inc/antivirus_set_settings.cfm`,
`inc/antivirus_add_whitelists.cfm`,
`inc/antivirus_delete_entry.cfm`,
`inc/generate_antivirus_configuration.cfm`,
`inc/restart_clamav.cfm`).

This page configures the ClamAV antivirus engine that runs inside
`hermes_mail_filter` and is called by Amavis on every message that
clears the SMTP-time perimeter. Two cards: the main settings card
(sixteen toggles that map to `clamd.conf` directives) and a Pro-only
AV Signature Whitelist for suppressing known-bad-signature false
positives. Refreshing third-party signature *feeds* (Sanesecurity,
SecuriteInfo, MalwarePatrol, etc.) is configured separately on
[Malware Feeds](https://docs.deeztek.com/books/administrator-guide/page/malware-feeds); this page configures the engine
itself.

## Where antivirus sits in the flow

```
                  +-----------------------------------+
   inbound msg -->| Perimeter Checks pass             |
                  +---------------+-------------------+
                                  |
                                  v
                  +-----------------------------------+
                  |  Postfix smtpd_proxy_filter       |
                  |    -> hermes_mail_filter:10024    |
                  +---------------+-------------------+
                                  |
                                  v
                  +-----------------------------------+
                  |  Amavis (hermes_mail_filter)      |
                  |   - SpamAssassin scoring          |
                  |   - ClamAV antivirus  <---- this page configures this engine
                  |   - banned-file checks            |
                  +---------------+-------------------+
                                  |
                                  v
                  +-----------------------------------+
                  |  Re-inject -> hermes_postfix_dkim:10026
                  +-----------------------------------+
                                  |
                                  v
                  +-----------------------------------+
                  |  OpenDKIM sign, ARC seal, deliver |
                  +-----------------------------------+
```

Amavis calls ClamAV over the local socket; the verdict
determines whether Amavis quarantines, blocks, or passes the
message. Amavis's own action policy (the `final_*_destiny`
settings — quarantine vs DSN vs discard) lives in
[Antispam Settings](https://docs.deeztek.com/books/administrator-guide/page/antispam-settings) and the per-domain
policy table, not on this page. This page is **engine knobs only**.

## Container and socket placement

| Component | Detail |
| --- | --- |
| Container | `hermes_mail_filter` (IPv4 `.105`) |
| Engine | `clamd` daemon, Unix socket inside the container |
| Daemon config | `/etc/clamav/clamd.conf` (volume-mounted from `./config/mail_filter/etc/clamav/clamd.conf`) |
| Signature dir | `/var/lib/clamav/` (Docker named volume `mail_filter_data_clamav`) |
| Signature whitelist | `/var/lib/clamav/local.ign2` (regenerated from `parameters2 WHERE module='clamav-bypass'` on every save) |
| Third-party feeds | `/etc/fangfrisch/fangfrisch.conf` + `/var/lib/fangfrisch/signatures/` (see [Malware Feeds](https://docs.deeztek.com/books/administrator-guide/page/malware-feeds)) |
| Base signature refresh | `freshclam` (official ClamAV CVD updates, default 1h) |
| Feed refresh | `fangfrisch refresh` on a 10-minute Ofelia job (`hermes-fangfrisch-refresh`) |

The container exposes **no host ports** — Amavis is reached only by
Postfix internally at `hermes_mail_filter:10024` and re-injects to
`hermes_postfix_dkim:10026`.

## ClamAV Antivirus Settings card

Sixteen toggles, each rendered from the `avSettings` array in
`view_antivirus_settings.cfm` with an inline hint and a "Recommended"
label on the safer default. Every toggle writes
`parameters2.value2 = 'true' | 'false'` for `module = 'clamav'`; on
save, `generate_antivirus_configuration.cfm` selects every active row
and emits one `<directive> <value>` line per toggle into a temp file,
substitutes the temp file into the `HERMES_ANTIVIRUS_SETTINGS_GO_HERE`
placeholder of `clamd.conf.HERMES`, backs up the live config to
`clamd.conf.HERMES`, and moves the rendered file into place.

| UI Toggle | `clamd.conf` directive | Recommended | Notes |
| --- | --- | --- | --- |
| Scan Email Attachments | `ScanMail` | Enabled | Master switch for inbound attachment scanning |
| Scan Archives | `ScanArchive` | Enabled | Recurse into ZIP, RAR, 7z, etc. Without this, only the archive wrapper is scanned |
| Mark Encrypted Archives as Viruses | `ArchiveBlockEncrypted` | Disabled | Aggressive; commonly false-positives on legitimate password-protected files |
| Scan Portable Executables | `ScanPE` | Enabled | Windows PE format; required for decompression of UPX / FSG / Petite packers |
| Scan OLE2 Files | `ScanOLE2` | Enabled | MS Office `.doc/.xls/.ppt` and `.msi` |
| Block OLE2 VBA Macros | `OLE2BlockMacros` | Disabled | Blocks ALL macro-enabled documents regardless of intent (detected as `Heuristics.OLE2.ContainsMacros`); useful in strict environments, breaks legitimate macros otherwise |
| Scan PDF Files | `ScanPDF` | Enabled | PDF embedded JS, exploit detection |
| Scan HTML/JavaScript Content | `ScanHTML` | Enabled | HTML normalization + JavaScript/ScriptEncoder decryption; phishing + script-exploit detection |
| Algorithmic Detection | `AlgorithmicDetection` | Enabled | Engine-level heuristics for complex malware and graphic-file exploits |
| Scan ELF Files | `ScanELF` | Enabled | Linux/Unix executable format |
| Phishing Signature Detection | `PhishingSignatures` | Enabled | ClamAV's phishing signature DB |
| Scan Email URLs for Phishing | `PhishingScanURLs` | Enabled | URL extraction + phishing URL DB lookup |
| Block SSL Mismatches in URLs | `PhishingAlwaysBlockSSLMismatch` | Disabled | False-positives on CDN and redirect URLs |
| Block Cloaked URLs | `PhishingAlwaysBlockCloak` | Disabled | False-positives on URL shorteners and marketing-tracker links |
| Detect Potentially Unwanted Applications | `DetectPUA` | Enabled | Adware, dialers, non-malicious-but-unwanted software |
| Heuristic Scan Precedence | `HeuristicScanPrecedence` | Enabled | When on, heuristic hits stop the scan immediately (saves CPU). When off, scanning continues so a signature-based hit can override a heuristic match |

> **Operational consequence — disabling `ScanMail`.** This effectively
> turns off antivirus for inbound mail. Amavis will still consult
> ClamAV for ban-pattern decisions but the engine will skip the
> attachment scan. Leave on except for very short-term diagnostics.
>
> **Operational consequence — `OLE2BlockMacros` = true.** Every
> macro-enabled Office document is blocked as `Heuristics.OLE2.ContainsMacros`,
> including documents from your own users. Most organizations get
> better results with macro-blocking enforced at the endpoint
> (Microsoft 365 Protected View, Group Policy) rather than at the
> gateway. Turn on only after warning users and ensuring you have a
> release workflow.

## AV Signature Whitelist card (Pro)

When ClamAV produces a false positive on a known-safe file, the
admin enters the exact ClamAV signature name (e.g.
`Heuristics.OLE2.ContainsMacros`) and Hermes appends it to
`/var/lib/clamav/local.ign2`. ClamAV reads `local.ign2` at engine
start and suppresses any detection whose signature name matches a
line in the file.

Storage:
`parameters2 WHERE module = 'clamav-bypass'` (one row per signature
name, `parameter` column holds the signature string). On every save
and on every delete, `generate_antivirus_configuration.cfm` rewrites
the whole `local.ign2` from the table, runs `dos2unix` to scrub line
endings, backs up the current file to `local.ign2.HERMES`, and moves
the new file into place. ClamAV is then restarted via
`restart_clamav.cfm` to pick up the change.

### How to find a signature name

The in-card info box gives admins the lookup steps:

1. From **Message History**, find the blocked message (Type column
   shows `Virus` or `Banned`)
2. Grep the mail-filter log for the message ID:
   `docker logs hermes_mail_filter 2>&1 | grep <mail_id>`
3. The log line shows the signature in parentheses, e.g.
   `Blocked INFECTED (Heuristics.OLE2.ContainsMacros)`
4. Or scan a file directly:
   `docker exec hermes_mail_filter clamscan /path/to/file`

> **Operational consequence — whitelisting is by signature name, not
> by file hash.** If you whitelist `Heuristics.OLE2.ContainsMacros`,
> you have effectively turned off macro detection globally. Prefer
> narrow signature names (specific malware family) over heuristic
> families when possible.

## Signature refresh

Two independent refresh loops keep the engine current:

| Source | Mechanism | Cadence | Database |
| --- | --- | --- | --- |
| Official ClamAV (`main.cvd`, `daily.cvd`, `bytecode.cvd`) | `freshclam` daemon inside `hermes_mail_filter` | Default 1h (configurable in `/etc/clamav/freshclam.conf`) | `/var/lib/clamav/` |
| Third-party feeds (Sanesecurity, SecuriteInfo, MalwarePatrol, etc.) | `fangfrisch refresh` via Ofelia job `hermes-fangfrisch-refresh` | Every 10 minutes (only feeds whose own publish cycle has elapsed actually re-download) | `/var/lib/fangfrisch/signatures/` then linked into `/var/lib/clamav/` by `setup-clamav-sigs` |

`fangfrisch` is the small Python tool that handles auth, cadence
control, and integrity verification for third-party feeds; the feed
list and per-feed enable/disable lives on
[Malware Feeds](https://docs.deeztek.com/books/administrator-guide/page/malware-feeds). Enabling premium feeds
(SecuriteInfo paid, MalwarePatrol paid) requires Pro licensing —
the feed list itself is gated on the same page.

## Resource footprint

Loading the full signature database into RAM costs roughly 1.5–2 GB
of memory. If `hermes_mail_filter` is under-provisioned (e.g. shared
host with 4 GB total), `clamd` will fail to start, mail will queue
behind Amavis, and the only sign in the UI is a quiet rise in
deferred queue depth. Plan for at least 4 GB dedicated to the
`hermes_mail_filter` container on systems with all third-party
feeds enabled.

The default ClamAV file-size cap is 25 MB (`MaxFileSize 25M` in
`clamd.conf`). Messages larger than this are passed without scan and
flagged with a `Heuristics.Limits.Exceeded` indicator. Raising the
cap requires editing `clamd.conf.HERMES` directly; the UI does not
expose it because raising it disproportionately increases RAM and
CPU per scan.

## Save flow

```
1. View page submits action="AV Settings" (sixteen booleans),
                       action="Add AV Whitelist" (textarea),
                       action="Delete Entry" (id list)
2. view_antivirus_settings.cfm validates every avFields entry exists and is true|false
   (any failure -> error.cfm + cfabort)
3. antivirus_set_settings.cfm UPDATEs parameters2.value2 for each toggle
   (16 UPDATEs, module='clamav')
4. generate_antivirus_configuration.cfm:
     a. SELECT active='1' rows from parameters2 module='clamav' -> temp avsettings file
     b. dos2unix the temp file
     c. Substitute into clamd.conf.HERMES placeholder HERMES_ANTIVIRUS_SETTINGS_GO_HERE
     d. Back up /etc/clamav/clamd.conf -> clamd.conf.HERMES, move new file into place
     e. Rebuild /var/lib/clamav/local.ign2 from parameters2 module='clamav-bypass'
     f. dos2unix, back up local.ign2 -> local.ign2.HERMES, move new file into place
     g. cfinclude restart_clamav.cfm (docker container restart hermes_mail_filter ClamAV process)
5. session.m = 9 -> green "Antivirus Settings were saved successfully" alert
```

`generate_antivirus_configuration.cfm` also runs on whitelist
add/delete — every change to either card triggers the same full
regen + ClamAV restart cycle. The page does not return until the
restart has completed (timeout per `cfexecute`).

## Failure semantics

| Failure | Behavior |
| --- | --- |
| Toggle form missing a required boolean field | `m = "Antivirus Settings: form.<f> does not exist"`, `error.cfm`, cfabort |
| Toggle value not in `true,false` | `m = "Antivirus Settings: form.<f> is not true or false"`, `error.cfm`, cfabort |
| Delete clicked with no selection | session.m = 11 |
| Add Whitelist with empty textarea | session.m = 13 |
| `dos2unix` failure on the temp avsettings or local.ign2 file | `error.cfm` + cfabort with the failing path in the message |
| `cp /etc/clamav/clamd.conf -> .HERMES` failure | `error.cfm` + cfabort |
| `mv <tmp>_clamd.conf -> /etc/clamav/clamd.conf` failure | `error.cfm` + cfabort |
| `restart_clamav.cfm` failure | Surfaces as cfcatch from the docker restart step |

The save is **not** transactional across the steps — if the SQL
updates succeed but the ClamAV restart fails, the DB state has
already advanced. The next save will re-render and re-apply
because every save regenerates the entire file from the current row
state (no incremental writes).

## Files and containers touched

| Path | Owner | Role |
| --- | --- | --- |
| `config/hermes/var/www/html/admin/2/view_antivirus_settings.cfm` | `hermes_commandbox` | The page |
| `config/hermes/var/www/html/admin/2/inc/antivirus_*.cfm` | `hermes_commandbox` | Validate / save / regenerate / restart |
| `config/hermes/var/www/html/admin/2/inc/get_antivirus_settings.cfm` | `hermes_commandbox` | Loads current `parameters2 module='clamav'` values |
| `config/hermes/opt/hermes/conf_files/clamd.conf.HERMES` | `hermes_commandbox` (read) -> `hermes_mail_filter` (live `/etc/clamav/clamd.conf`) | Canonical template with `HERMES_ANTIVIRUS_SETTINGS_GO_HERE` placeholder |
| `config/mail_filter/etc/clamav/clamd.conf` | `hermes_mail_filter` (live config, bind-mounted) | Read by `clamd` at start |
| `/var/lib/clamav/local.ign2` | `hermes_mail_filter` (Docker named volume `mail_filter_data_clamav`) | Signature whitelist; rewritten on every save |
| `/var/lib/clamav/*.cvd`, `*.cld`, `*.ndb`, etc. | `hermes_mail_filter` | Signature databases (official + third-party) |
| `parameters2` table, `module='clamav'` | `hermes_db_server` (`hermes` DB) | Source of truth for the sixteen toggles |
| `parameters2` table, `module='clamav-bypass'` | `hermes_db_server` (`hermes` DB) | Source of truth for the AV Signature Whitelist |
| `malware_databases` table | `hermes_db_server` (`hermes` DB) | Third-party feed list (configured on [Malware Feeds](https://docs.deeztek.com/books/administrator-guide/page/malware-feeds)) |
| `ofelia_jobs` row `hermes-fangfrisch-refresh` | `hermes_db_server` | 10-minute feed refresh scheduler |
| `hermes_mail_filter` container | — | `clamd`, `freshclam`, `fangfrisch`, Amavis, SpamAssassin |

## Related

- [Malware Feeds](https://docs.deeztek.com/books/administrator-guide/page/malware-feeds) — the third-party signature feed
  configuration (Sanesecurity, SecuriteInfo, MalwarePatrol, etc.)
  that Fangfrisch refreshes every 10 minutes
- [Perimeter Checks](https://docs.deeztek.com/books/administrator-guide/page/perimeter-checks) — every check on this page
  runs only after a connection clears the SMTP-time perimeter
- [Anti-Spam Settings](https://docs.deeztek.com/books/administrator-guide/page/antispam-settings) — runs in the same
  Amavis pass; a virus verdict overrides any spam score
- [Score Overrides](https://docs.deeztek.com/books/administrator-guide/page/score-overrides) — per-rule weight changes
  for SpamAssassin
- [Email Policies > Disclaimers](https://docs.deeztek.com/books/administrator-guide/page/disclaimers) —
  body modification that runs after Amavis re-injection; never
  conflicts with ClamAV because it happens post-scan
- [ARC Settings](https://docs.deeztek.com/books/administrator-guide/page/arc-settings) — seals over the body Amavis
  passed, so a virus verdict naturally pre-empts everything
  downstream
- [DNS Resolver](https://docs.deeztek.com/books/administrator-guide/page/dns-resolver) — URL phishing
  lookups (`PhishingScanURLs`) and signature-feed downloads
  (Fangfrisch) all resolve through `hermes_unbound`
- [Email flow](https://docs.deeztek.com/books/installation-reference/page/hermes-seg-email-flow) — full pipeline diagram