exav-grep
grep, but it looks inside archives. The flags you already know, with every member of every container, recursively, as the haystack.
It is in each release, as
exav-grep-<tag>-<target>, or:
cargo install exav-grepexav-grep -r "AKIA[0-9A-Z]{16}" ./backups/ # leaked keys inside tarballsexav-grep -F "password" release.zip # nested archives tooexav-grep -l "ProcessBuilder" suspicious.jar # which member, as well as which fileMatches are reported with the full path through the containers. A compressed stream (the gzip around a tar) is a layer of its own, named for its content:
backups/2026-01.tar.gz!gzip-content!db/dump.sql:412:AKIAIOSFODNN7EXAMPLEsuspicious.jar!kingDavid/00.class/!java-class-strings:65:java/lang/ProcessBuilderExit codes
Section titled “Exit codes”| Code | Meaning |
|---|---|
0 |
matches found |
1 |
no matches |
2 |
a usage or I/O error |
3 |
no matches, but some members could not be read: the search was incomplete |
3 is the one a script looking for secrets or indicators has to handle: a clean
result and an incomplete one are different answers.
As a library
Section titled “As a library”cargo add exav-grepuse exav_grep::{Matcher, Options, Searcher};
let matcher = Matcher::fixed("password", false)?;let mut searcher = Searcher::new(matcher, Options::default());// The sink returns `true` to keep going, `false` to stop the search.searcher.search_path(std::path::Path::new("backup.zip"), &mut |ev| { println!("{ev}"); true})?;Why not zgrep and a loop
Section titled “Why not zgrep and a loop”The loop stops at the first layer, and unreadable members disappear. exav-grep
recurses through archives inside archives and reports members it could not read,
so an encrypted or corrupt member does not look like a non-match. --quiet-unreadable
turns those reports off.
The familiar set (-i, -v, -l, -r, -F, -A/-B/-C), counted per
member rather than per file: -m N stops after N matches in each member, and
-c prints one total, the number of matching members. Plus the ones extraction
needs:
| Flag | Purpose |
|---|---|
--passwords <PASSWORD> |
Try on encrypted members (repeatable), before the built-in passwords |
--max-object-bytes <BYTES> |
Cap on what any single member may decompress to, and on the size of an input file (default 256 MiB) |
--max-members <N> |
Cap on members visited inside each input file |
--max-unpack-depth <N> |
Cap on archive-within-archive nesting |
--quiet-unreadable |
Suppress unreadable-member reports |
The limits are the scanner’s decompression-bomb budget, spelled as
exav spells them, so pointing it at a hostile
archive is safe. Each input file is read whole into memory, so one larger than
--max-object-bytes is reported unreadable (exit 3) rather than read; raise
the flag to search it.
What it searches
Section titled “What it searches”Everything exav-unpack opens: zip, rar, 7z, tar, gzip/xz/bzip2/zstd, iso, cab, OLE2 documents, PDF, MIME email, disk images and the filesystems inside them. Derived views are searched too, so a Java class is searchable as its string constants rather than as opaque bytecode.