Skip to content

Scanning

exav prints clamscan’s output format. The flags are exav’s own, and there is a fourth exit code. This guide covers everyday scanning; see the CLI reference for every flag.

Terminal window
exav file.bin # scan one file
exav /var/www # scan a directory, recursively
exav --no-recursive /var/www # just the directory's own files
exav --quiet /data # only print errors and detections
cat file.zip | exav - # scan stdin (constant memory, any size)
exav https://host/obj.zip # scan over HTTP range requests (needs an `http-scan` build)
exav -d ./mydb /data # use a signature database (file or dir)

A named directory recurses by default. Naming one and getting some of it is the kind of surprise that reads as a clean result: the files that were never opened are indistinguishable, in the output, from files that were and were fine.

All examples assume signatures are loaded — either via -d / --sig-dir, or the default --sig-dir directory. See Signatures.

exav has two input modes, and the difference matters for what gets analysed:

  • Stream (a forward-only pipe, exav -): runs the constant-memory pattern + hash core. Unlimited size. Structural unpacking of archives requires seeking, so a pure pipe does pattern+hash only.
  • Seekable (local files, or http(s):// URLs): additionally drives archive extraction and full structural analysis.

Scanning over HTTP costs bandwidth, not memory

Section titled “Scanning over HTTP costs bandwidth, not memory”

A seekable source may be read more than once. Some checks need a whole object at a time — overlapping ZIP records, overlapping MBR partitions, broken media and executable headers — while extraction walks members one at a time, so the object is read again to run them.

That is deliberate. exav optimises for memory, not bandwidth. The bound it holds to is the working set: a 4 GB archive is scanned without a 4 GB allocation, whatever the transfer costs. Over http(s):// this means a range source is fetched a small, constant number of times rather than once, and an object under the deep-analysis limit is likely fetched in full.

If you would rather pay in memory or disk than in transfer — metered egress, a slow link, an object store that bills per request — download the object and hand exav the file:

Terminal window
curl -sO https://host/obj.zip && exav obj.zip

An object too large to hold is never held: it is scanned member-by-member, and the whole-object checks are reported as skipped rather than quietly dropped.

The human-readable output is one line per file:

Terminal window
/path/to/file: Win.Trojan.Agent-1234 FOUND
/path/to/clean: OK
/path/to/huge.bin: file size 9663676416 exceeds max-scan-size 4294967296; scanned first 4294967296 bytes only LIMITS-EXCEEDED PARTIAL
/path/to/secret.zip: encrypted archive member PASSWORD-PROTECTED PARTIAL

A line that could not be fully examined ends with two words, not one: the category saying which condition stopped the scan, and the status PARTIAL that every category shares. The reason comes first.

followed by a clamscan-style summary. --quiet is the single output dial — it suppresses the OK lines and the summary together, with -v at the other end of the same dial.

For tooling and pipelines, --json emits newline-delimited JSON — one object per scanned input, plus a final summary object:

Terminal window
exav --json /data | jq 'select(.status=="FOUND")'

status is the word the human line ends with — OK, FOUND, ERROR, PARTIAL — and it is on every result object. category is not: it sub-classifies a PARTIAL (LIMITS-EXCEEDED, UNSCANNABLE, PASSWORD-PROTECTED) and is absent everywhere else, so filtering a pipeline on it returns nothing at all rather than failing. Select on status:

Terminal window
exav --json /data | jq 'select(.status=="PARTIAL")' # not fully examined
exav --json /data | jq 'select(.status|test("FOUND|PARTIAL"))' # everything unclean

-v / --verbose prints informational findings (detected file type, entropy, imphash, ML score) alongside the verdict.

By default exav stops at the first matching signature per file. --all-matches reports every match (what clamscan spells --allmatch):

Terminal window
exav --all-matches /data

Include/exclude by path regex — repeatable, matching clamscan’s semantics:

Terminal window
exav /data --exclude '\.log$' --exclude-dir 'node_modules' --include '\.exe$'

--exclude-dir matches a directory’s own path, which has no trailing separator — so node_modules skips the directory, while /node_modules/ skips only what is nested below it and lets files sitting directly inside through. The looser pattern is the one to write, or anchor it explicitly as /node_modules($|/).

Each code is a status word, and the status is the last word on the result line:

Code Status Meaning
0 OK Clean — everything fully scanned, no matches.
1 FOUND At least one detection.
2 ERROR exav could not do its job — an unreadable path, a database that would not load.
3 PARTIAL A file couldn’t be fully examined: LIMITS-EXCEEDED / UNSCANNABLE / PASSWORD-PROTECTED.

3 is the never-silent-clean invariant made visible: a file exav couldn’t finish examining never counts as a pass, and it is kept apart from 2 so “the scanner broke” and “this object needs a decision” are not one signal. --partial-as folds 3 into any of the others. See Verdicts & exit codes for the full table.

An encrypted member is reported PASSWORD-PROTECTED, never a silent clean. Supply passwords to decrypt and scan inside:

Terminal window
exav --passwords secret --passwords hunter2 /data # try a pool, in order

Passwords also load from a ClamAV .pwdb database in the signature directory. ZIP (ZipCrypto + WinZip AES), encrypted DMG, 7z AES-256, PDF and Office — legacy XLS (RC4-basic, RC4-CryptoAPI, XOR) and OOXML (AES standard and agile) — are all decrypted. Office additionally auto-tries Excel’s default VelvetSweatshop and the empty password, so no --passwords is needed for the common malware case. RAR AES and PKWARE Strong Encryption are detected but not decrypted.

exav runs a small FP-safe, ClamAV-parity heuristic subset by default (imphash matching and the PDF obfuscated-name check). --detect heuristics turns on the exav-exclusive superset (PE entropy, packer detection, TLSH fuzzy matching, a static ML baseline scorer) — higher FP risk, so it’s opt-in:

Terminal window
exav --detect heuristics -v sample.exe

--detect is the one dial for every heuristic detector: all, none, or a comma-separated list of heuristics, macros, broken, broken-media, partition-intersection, phishing, packed, pua.