Mailstrix › Syntax & synopsis

Syntax & synopsis

Complete reference for the two binaries — the strixd scanning backend and the strix-scan CGO-free client — plus every MAILSTRIX_* environment variable, the HTTP /scan API, the request/response headers and the JSON verdict shape. Anything set on the command line can also be set by environment variable; flags win over the environment, and both are clamped to safe ranges at startup.

strixd — the scanning backend

One binary, several subcommands. With no command it defaults to serve. Run strixd <command> -h for that command's own flags.

strixd <command> [flags]

  serve        run the HTTP backend (/scan, /metrics, /health) — the default
  scan         scan files/dirs/stdin against the rules in-process
  fetch-rules  download an updated compiled rule bundle into the cache
  check-rules  compile the rules, print the count, exit non-zero on failure
  extract      dump what the container extractor carves from a file (no scan)
  info         print build, libyara, and loaded-rules-bundle identity
  health       probe the local /health endpoint (container HEALTHCHECK)
  version      print the version

extract is the debugging window into the moat: it shows every layer Mailstrix carves out of a file — OLE/OOXML parts, decompressed VBA, archive members, carved PE/ELF — without running YARA, so you can see exactly what the matcher will be handed.

strixd serve — flags

Each flag mirrors an environment variable (shown in parentheses); the flag overrides it.

FlagEnvDefaultPurpose
-hostMAILSTRIX_HOSTall interfacesHTTP bind host. Bind to loopback/private only.
-portMAILSTRIX_PORT8079HTTP bind port.
-backend-timeoutMAILSTRIX_BACKEND_TIMEOUT1sWhole-request backend budget.
-scan-timeoutMAILSTRIX_SCAN_TIMEOUT8sPer-scan libyara timeout.
-max-concurrentMAILSTRIX_MAX_CONCURRENTCPU countMax simultaneous libyara scans (CPU gate).
-max-bodyMAILSTRIX_MAX_BODY8 MiBMax request body; oversized is rejected, never truncated.
-tokenMAILSTRIX_TOKEN[_FILE]noneShared secret for /scan. Prefer the _FILE form (keeps it out of the process list).
-rules-dirMAILSTRIX_RULES_DIRnoneDirectory of *.yar/*.yara to compile at start.
-rulesMAILSTRIX_RULESnonePrecompiled .yac bundle; wins over -rules-dir.
-cache-dirMAILSTRIX_CACHE_DIRnoneWritable dir for the live bundle; seeded from -seed-rules when empty/unreadable.
-seed-rulesMAILSTRIX_SEED_RULESnoneBaked read-only .yac used to (re)seed the cache.
-verboseMAILSTRIX_VERBOSEoffPer-request logging.
-log-stdoutMAILSTRIX_LOG_STDOUToffInfo/access logs to stdout; errors stay on stderr.

strix-scan — the CGO-free client

A tiny client with no libyara dependency, for the Sieve/LDA path and for boxes that can't link a C library. It reads a file argument (or stdin), POSTs it to a running strixd, and signals the verdict through its exit code — 0 = clean/no-match (and on a transport error when -fail-open), non-zero = match or hard error.

strix-scan [flags] [file|-]
FlagDefaultPurpose
-url— (required)Base URL of strixd, e.g. http://127.0.0.1:8079 (MAILSTRIX_URL).
-token-filenoneFile holding the shared secret (preferred — not in the process list). Falls back to MAILSTRIX_TOKEN.
-tokennoneShared secret inline. Optional; omit for a token-less strixd.
-filenamenoneAttachment name, sent as X-MAILSTRIX-Filename so name/extension-keyed rules fire.
-timeout10sHTTP request timeout.
-max-body8 MiBMax bytes read from input; oversized fails open without scanning a truncated prefix.
-fail-opentrueOn a transport/HTTP error treat the message as CLEAN (exit 0) so a scanner outage never blocks delivery. =false surfaces the error (exit 2).
-quietfalsePrint nothing on a match — rely on the exit code only.
-version—Print version and exit.

strix-milter — the Postfix/Sendmail front end

A second CGO-free binary that runs on the MTA host, buffers each message, POSTs it to strixd and stamps the verdict as a header. It always accepts: the MTA's milter_header_checks turns the verdict into policy, so a scanner outage can never block mail.

strix-milter -url http://127.0.0.1:8079 -listen inet:127.0.0.1:8081
FlagDefaultPurpose
-url— (required)Base URL of strixd (MAILSTRIX_URL).
-listeninet:127.0.0.1:8081inet:HOST:PORT or unix:/path.sock (MAILSTRIX_MILTER_LISTEN). Keep it on loopback or a unix socket: anyone who can reach it can have messages scanned.
-token-filenoneFile holding strixd's shared secret, keeping it out of the process list.
-timeout20sHard per-message deadline. On expiry the message is accepted as unknown.
-max-body8 MiBA larger message is accepted unscanned rather than scanned as a truncated prefix, which would be a silent miss.
-max-conns64Max concurrent MTA connections. Bounds memory at roughly max-conns × max-body × 2, so a flood of large messages cannot OOM the filter.
-cape-policystatic-onlyDelivery always follows the current static verdict; the CAPE adapter is report-only. Any other value is rejected at startup.
-log-cleanoffLog clean verdicts too (noisy).

It stamps X-Mailstrix-Status, X-Mailstrix-Rules and X-Mailstrix-Family. Hold anything called infected from /etc/postfix/milter_header_checks.

Environment variables

Every MAILSTRIX_* knob, grouped. Booleans accept any non-empty value as true. Durations like the refresh/TTL/timeout values are seconds unless a serve flag accepts Go duration syntax (500ms, 2s). All values are clamped to safe ranges at startup, so a non-positive value cannot disable a guard.

Server & admission

VariableDefaultMeaning
MAILSTRIX_HOSTall ifacesHTTP bind host.
MAILSTRIX_PORT8079HTTP bind port.
MAILSTRIX_ICAP_ADDRdisabledWhen set, also start an ICAP listener on this address.
MAILSTRIX_MAX_BODY8388608 (8 MiB)Max request body in bytes.
MAILSTRIX_MAX_CONCURRENTCPU countCPU gate — simultaneous libyara scans.
MAILSTRIX_MAX_INFLIGHT0 → 2× concurrentAdmission gate — in-flight buffers held for the whole request.
MAILSTRIX_BACKEND_TIMEOUT1sWhole-request backend budget.
MAILSTRIX_SCAN_TIMEOUT8sPer-scan libyara timeout.
MAILSTRIX_ICAP_MAX_CONNSauto (8× in-flight)Max live ICAP connections, capped at accept(). Over the cap the server answers 503 and hangs up, so a slow-loris pool cannot accumulate goroutines or fds.
MAILSTRIX_CLAMD_UNIX_PATHdisabledAbsolute socket path for the clamd INSTREAM listener.
MAILSTRIX_CLAMD_TCP_ADDRdisabledExplicit host:port for clamd over TCP. A wildcard host needs MAILSTRIX_ALLOW_WILDCARD_BIND=1.
MAILSTRIX_CLAMD_MAX_CONNS64Shared Unix/TCP connection cap, range 1–1024.
MAILSTRIX_ALLOW_WILDCARD_BIND0Permit a wildcard (all-interface) ICAP or clamd TCP bind. These listeners do not check the /scan token, so keep them on a trusted network.
MAILSTRIX_PPROFoffEnable the net/http/pprof endpoints (debug only).

Effort

VariableDefaultMeaning
MAILSTRIX_EFFORTauto / maxExtraction depth: 1 = raw bytes + shallow, higher = deeper recursion, auto derives it from admission-gate pressure. Per-request override via the header.
MAILSTRIX_EFFORT_MAXbuild defaultCeiling the effort level (and the X-MAILSTRIX-Effort header) is clamped to.
MAILSTRIX_ARCHIVE_PW (or _FILE)0 (off)Opt-in. Try to decrypt password-protected archive members (zip ZipCrypto and WinZip-AES, 7z, rar) with candidate passwords scraped from the mail subject, body and filename, plus an optional wordlist, then scan the recovered payload.
MAILSTRIX_CAPE_CONFIG_FILEdisabledAbsolute path to the CAPE adapter's JSON config. With no file, no CAPE listener, state or scheduler is created.
MAILSTRIX_CAPE_POLICYstatic-onlyThe only accepted value. Mail always follows the static verdict, so a pending detonation never holds, quarantines or tempfails a message. Anything else exits 2 at startup.

Authentication

VariableDefaultMeaning
MAILSTRIX_TOKENnoneShared secret for /scan. Empty = token-less (open) backend.
MAILSTRIX_TOKEN_FILEnoneRead the secret from a file (preferred; not in the process list).
MAILSTRIX_TOKEN_NEXTnoneSecond valid token, for zero-downtime rotation — both accepted during the overlap.
MAILSTRIX_METRICS_AUTHoffRequire the token on /metrics too (otherwise metrics are open).

Rules & cache

VariableDefaultMeaning
MAILSTRIX_RULESnonePrecompiled .yac bundle (wins over RULES_DIR).
MAILSTRIX_RULES_DIRnoneDirectory of source rules to compile at start.
MAILSTRIX_RULES_URLnoneBundle URL for fetch-rules (the daily rules-current release).
MAILSTRIX_CACHE_DIRnoneWritable dir for the live bundle; self-heals from the seed.
MAILSTRIX_SEED_RULESnoneBaked read-only .yac used to (re)seed the cache.
MAILSTRIX_RULES_MAX_AGE0 (off)Mark the bundle stale after N seconds; stale → /ready reports 200 but logs a warning.
MAILSTRIX_BIGFILE_RULESnoneSeparate bundle applied only to inputs over the threshold.
MAILSTRIX_BIGFILE_THRESHOLD6291456 (6 MiB)Size above which the big-file bundle is used.
MAILSTRIX_RULE_ALLOWLISTnoneOnly these rules may produce a verdict (tags-only).
MAILSTRIX_RULE_DENYLISTnoneInline denylist of rule names to suppress.
MAILSTRIX_DENYLIST_FILEnoneFile of denylisted rule names; reloaded on SIGHUP.
MAILSTRIX_CANARYoffShadow mode — tag every match mailstrix_canary=1 instead of acting; for safe rollout.
MAILSTRIX_RULES_POLL_INTERVAL86400 (image/deb)Seconds between automatic verified update and reload. Explicit 0 disables it; an enabled interval must be at least 60 seconds.
MAILSTRIX_RULES_FETCH_TIMEOUT300Seconds; shared deadline for the automatic update's network and cache-lock waits.
MAILSTRIX_RULES_EXTRA_SIGNING_KEYS—Comma-separated extra trusted manifest signing public keys (raw 32-byte ed25519, base64). Additive only — the keys built into the binary stay trusted and cannot be removed, and verification cannot be disabled.
MAILSTRIX_RULES_ALLOW_HTTPoffPermit a plain-http rules URL. https is required otherwise, and https-to-http redirects are always refused.
MAILSTRIX_RULES_ALLOW_COUNT_DROPoffInstall a verified bundle carrying zero rules, or under 50% of the current count. Refused by default.
MAILSTRIX_PROFILEmailHow much of the fetched rule breadth is baked. mail drops only rules that can never fire on a mail attachment's bytes.

Verdict cache

VariableDefaultMeaning
MAILSTRIX_CACHE_SIZE65536In-memory L1 verdict entries (LRU+TTL).
MAILSTRIX_CACHE_TTL3600sVerdict cache TTL.
MAILSTRIX_REDIS_URLnoneOptional L2 verdict cache, shared across replicas.
MAILSTRIX_REDIS_PREFIXdefaultKey prefix for the Redis L2 cache.
MAILSTRIX_REDIS_MAC_KEY (or _FILE)—Optional HMAC-SHA256 key (≥ 32 bytes) over Redis L2 values, bound to the Redis key. A missing or bad MAC is a cache miss, so a tampered or replayed value is never trusted. Roll it out on every replica.

Threat-intel feeds (abuse.ch)

All feeds are optional and refresh in the background. Refresh values are seconds.

VariableDefaultMeaning
MAILSTRIX_URLHAUS_KEYnoneURLhaus auth key; enables malware-URL lookups on extracted links.
MAILSTRIX_URLHAUS_MAX_URLS64Max URLs checked per message.
MAILSTRIX_URLHAUS_REFRESH21600sFeed refresh interval.
MAILSTRIX_MBAZAAR_KEYnoneMalwareBazaar key; enables attachment SHA-256 lookups.
MAILSTRIX_MBAZAAR_FEEDnoneMalwareBazaar feed source/path.
MAILSTRIX_MBAZAAR_REFRESH86400sFeed refresh interval.
MAILSTRIX_THREATFOX_KEYnoneThreatFox key; IOC URL lookups.
MAILSTRIX_THREATFOX_MAX_URLS64Max URLs checked per message.
MAILSTRIX_THREATFOX_REFRESH21600sFeed refresh interval.
MAILSTRIX_FEODOoffEnable the Feodo Tracker botnet-C2 feed.
MAILSTRIX_FEODO_REFRESH21600sFeed refresh interval.

Logging

VariableDefaultMeaning
MAILSTRIX_VERBOSEoffPer-request logging.
MAILSTRIX_LOG_STDOUToffInfo/access logs to stdout; errors stay on stderr.

The strix-scan client reads MAILSTRIX_URL and MAILSTRIX_TOKEN from the environment too.

HTTP API

The serve backend exposes five routes. Only /scan (and optionally /metrics) require the token; the health probes are always open so orchestrators can probe without a secret.

MethodPathAuthPurpose
POST/scantokenScan the raw request body; returns the JSON verdict.
GET/metricsoptionalPrometheus exposition — scans, matches, errors, cache hits, feed stats.
GET/healthopenLiveness — the process is up.
GET/readyopenReadiness — rules loaded (warns, not fails, on a stale bundle).
GET/versionopenBuild, libyara and loaded-bundle identity.
# scan a file, token in a Bearer header
curl -sS -X POST http://127.0.0.1:8079/scan \
  -H "Authorization: Bearer $MAILSTRIX_TOKEN" \
  -H "X-MAILSTRIX-Filename: invoice.docm" \
  --data-binary @invoice.docm

Request headers

HeaderPurpose
Authorization: Bearer <token>Shared secret. The X-MAILSTRIX-Token header is accepted as an equivalent.
X-MAILSTRIX-TokenAlternative way to pass the shared secret.
X-MAILSTRIX-FilenameAttachment/message name, mapped to the YARA filename/extension externals so name-keyed THOR/Loki rules fire.
X-MAILSTRIX-EffortPer-request extraction depth, clamped to [1, EFFORT_MAX]. Overrides MAILSTRIX_EFFORT=auto.
X-MAILSTRIX-CachePer-request cache control (e.g. bypass the verdict cache for a re-scan).

JSON verdict

A 200 with a matches array. The array is empty, never null, on a clean message, so a caller can branch on length alone. Each match names the rule and the source ruleset namespace, plus any tags and metadata.

{
  "matches": [
    {
      "rule": "SUSP_VBA_Shell_Exec",
      "namespace": "sigbase-gen_office.yar",
      "tags": ["macro", "exec"],
      "meta": { "author": "Florian Roth", "score": "75" }
    }
  ]
}
FieldMeaning
ruleThe YARA rule name that fired.
namespaceSource ruleset file (which set fired, not just the rule). Empty for synthetic matches such as URLhaus.
tagsRule tags (omitted when none).
metaRule metadata key/value map (omitted when none).

A scan error, timeout or libyara panic returns an empty matches array — Mailstrix fails open by design, so the scanner can never become a mail-delivery outage. The error is still counted in /metrics.

Rules & bundles

Mailstrix doesn't author rules — it packages roughly 10,000 public rules from eight curated upstreams, precompiled to a .yac bundle and rebuilt daily. The full list and per-set licenses are on the front page FAQ.

  • Each rule file compiles into its own libyara namespace named after the file, so the verdict's namespace tells you which ruleset fired.
  • A writable CACHE_DIR holds the live bundle and self-heals from SEED_RULES on a fresh deploy; fetch-rules pulls the daily rules-current release into it.
  • Any upstream can be pinned or disabled at build time with a per-source build arg (e.g. DIDIER=0).
  • Allowlist/denylist narrow the active set without recompiling; the denylist reloads on SIGHUP with no downtime.

Deploy quickstarts

Docker

docker run -d --name mailstrix \
  -p 127.0.0.1:8079:8079 \
  -e MAILSTRIX_TOKEN_FILE=/run/secrets/mailstrix \
  eilandert/mailstrix:latest

strix-scan from a Sieve/LDA pipe

strix-scan -url http://127.0.0.1:8079 \
  -token-file /etc/mailstrix/token \
  -filename "$ATTACHMENT_NAME" message.eml
# exit 0 = clean, non-zero = match

The five integration paths (rspamd, SpamAssassin, Sieve, ICAP, standalone) each have a ready-made config under contrib/; a Debian package, a Helm chart and full deployment docs live in the GitHub repository.