Mail Scan

Anti-virus / content-scan facade for inbound + outbound mail. Operators wire b.mail.scan.create({...}) once at boot, then call .scan(messageBytes, opts) from the MX listener (v0.9.45), submission listener (v0.9.47), or any custom pipeline that needs a verdict before delivering / forwarding a message.

## Backends

Two transport shapes are supported out of the box:

- **ICAP** (protocol: "icap" — default) — RFC 3507 Internet Content Adaptation Protocol. The framework speaks to a c-icap (or commercial) daemon over TCP, sends a REQMOD or RESPMOD request with the message body encapsulated, and parses the response through b.safeIcap.parse. The de-facto standard for Sophos / Symantec / Trend Micro / McAfee ICAP integrations.

- **ClamAV INSTREAM** (protocol: "clamav-instream") — the native ClamAV daemon protocol (no ICAP layer). Operator points at a clamd instance; the framework sends zINSTREAM\0, framed 4-byte-length-prefix chunks, then a zero-length terminator, and parses the line-shaped : stream: OK / : stream: FOUND response. See https://docs.clamav.net/manual/Usage/Configuration.html#instream

## Composition

- **b.safeIcap** owns the ICAP wire-protocol bounded parser (CRLF discipline, status-allowlist, body cap). Every ICAP byte routes through it before any field is trusted. - **b.guardArchive** is composed when opts.archiveEntries is supplied — the scanner refuses an archive with hostile entry metadata BEFORE shipping bytes to the AV daemon, so a zip-bomb can't reach the scanner's parser. Recursion-depth cap is the guard's profile-default. - **b.audit** receives every request / verdict / error / timeout via audit.safeEmit (the audit failure is drop-silent per the hot-path rule).

## Threat model

- **ICAP-response-injection (raw bytes → header injection)**: defended by b.safeIcap — bare-CR / bare-LF / NUL refused; status-code allowlist; bounded header / body / count caps. - **Parser-bomb on Encapsulated res-body** (hostile daemon ships arbitrary body length): defended by profile-tunable maxBodyBytes cap on the safeIcap parse path. - **DoS via slow daemon**: per-request wall-clock timeout (default 30s strict / 60s balanced / 120s permissive). After the timeout the scan resolves with { verdict: "error" } and the listener fails the message-handling step (operator's choice: tempfail / reject / accept-with-tag). - **Archive-bomb / zip-slip pre-AV**: defended by optional b.guardArchive.validateEntries composition when the operator enumerates entries before the AV scan.

## Why not "vendor an AV signature engine"?

AV signature databases are operator state, not framework state. ClamAV's signature set changes hourly; commercial scanners refresh their state through their own update channel. The framework's job is the wire-protocol parser + the operator-facing facade — the AV intelligence belongs to whatever daemon the operator deploys.

b.mail.scan.create(opts) #

stable0.9.81
{
  host:          string — required. ICAP / clamd hostname or IP.
  port:          number — required. ICAP port (default 1344) /
                 clamd port (default 3310).
  service:       string — ICAP service name (default "srv_clamav").
  protocol:      "icap" | "clamav-instream" — default "icap".
  timeoutMs:     number — per-request wall clock; default per profile.
  profile:       "strict" | "balanced" | "permissive".
  posture:       "hipaa" | "pci-dss" | "gdpr" | "soc2".
  audit:         b.audit instance (drop-silent on failure).
}

Build a mail-scan handle. Returns { scan(messageBytes, opts), profile, protocol, MailScanError } where .scan resolves to { verdict, icapResponse?, threats?, durationMs }:

- verdict: "clean" | "infected" | "error". - icapResponse: the structured b.safeIcap.parse result on ICAP backend (omitted on clamav-instream). - threats: Array of threat names when infected. - durationMs: round-trip ms (audit / metrics).

var scanner = b.mail.scan.create({
  host:    "av.internal",
  port:    1344,
  service: "srv_clamav",
});
var verdict = await scanner.scan(rawMessage);
if (verdict.verdict === "infected") refuseMessage(verdict.threats);

b.mail.scan.compliancePosture(posture) #

stable0.9.81

Return the effective profile name for a compliance posture, or null for unknown posture names.

b.mail.scan.compliancePosture("hipaa");   // → "strict"

Last updated 2026-08-08T16:39:49.652Z by seeder.