Rust command-line client for the ScanMalware API.
API docs:
- Submit URL scans, poll for completion, and fetch results/summary/progress.
- Batch mode for URLs or scan IDs (file or stdin).
- Search coverage across all search endpoints exposed by the API.
- Output formats:
json,text,csv,raw. - Quiet and silent modes; ANSI color control for text output.
- Health and module ping checks.
- Cross-compile friendly (Rust + rustls, build script included).
curl -fsSL https://scanmalware.com/install.sh | bash[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; irm https://scanmalware.com/install.ps1 | iexbrew tap scanmalware/tap && brew install scanmalware/tap/scanmalware-cliHomebrew 6 gates third-party taps behind an explicit trust step. If the install is refused with "Refusing to load formula ... from untrusted tap", run:
brew trust scanmalware/tapSupported targets: macOS (x86_64, arm64), Linux (x86_64, arm64, armv7), and Windows (x86_64, arm64).
The install scripts verify the downloaded archive against the sha256sums.txt
published with each release and abort on a mismatch.
Install a specific version or custom location (macOS/Linux):
SCANMALWARE_VERSION=0.1.17 \
SCANMALWARE_INSTALL_DIR="$HOME/.local/bin" \
curl -fsSL https://scanmalware.com/install.sh | bashcargo install --path .Or build a local binary:
cargo build --releaseRun the CLI directly from Docker Hub (multi-arch linux/amd64, linux/arm64, linux/arm/v7):
docker run --rm jonaslejon/scanmalware-cli:latest --help
docker run --rm jonaslejon/scanmalware-cli:latest scan https://example.com --wait
docker run --rm jonaslejon/scanmalware-cli:latest search screenshot-hash phash bc3c3cc1c3c3c3c3 --limit 5The image runs unprivileged (uid 10001) with /work as its working directory.
To write output files into a bind mount, pass your own uid/gid:
docker run --rm --user "$(id -u):$(id -g)" -v "$PWD:/work" \
jonaslejon/scanmalware-cli:latest screenshot get <scan_id> --output /work/shot.pngIf you want to pass environment variables:
docker run --rm \
-e SCANMALWARE_BASE_URL=https://scanmalware.com \
-e SCANMALWARE_TIMEOUT=60 \
jonaslejon/scanmalware-cli:latest healthSCANMALWARE_BASE_URL(default:https://scanmalware.com)SCANMALWARE_TIMEOUT(request timeout in seconds)
Command-line flags override environment variables. No authentication is required.
If scanmalware alone feels too minimal, use the built-in help:
scanmalware --help
scanmalware scan --help
scanmalware search --help
scanmalware technologies --help
scanmalware pcap --help
scanmalware yara --help
scanmalware malware --help
scanmalware tls --help
scanmalware screenshot --helpscanmalware scan https://example.com
scanmalware scan https://example.com --wait --wait-interval 5 --wait-timeout 600
scanmalware scan https://example.com --unlisted
scanmalware result <scan_id>
scanmalware summary <scan_id>
scanmalware progress <scan_id>
scanmalware recent --page 1 --limit 20
scanmalware health
scanmalware pingGlobal flags can be placed before or after the subcommand:
scanmalware --format text --color always stats
scanmalware stats --format text --color alwaysGlobal options:
--base-url <URL> Override API base URL (or SCANMALWARE_BASE_URL)
--timeout-secs <SECS> Request timeout in seconds (or SCANMALWARE_TIMEOUT)
--format <FORMAT> text | json | csv | raw
--color <MODE> auto | always | never
--quiet Suppress banner, batch headers, warnings, and progress output
--silent Suppress all output, including errors
# Scan and wait for completion (text output)
scanmalware scan https://example.com --wait
# JSON output for a result
scanmalware result <scan_id> --format json
# Scan then fetch summary
scanmalware scan https://example.com --wait --format json
scanmalware summary <scan_id>
# OCR text for a scan
scanmalware ocr text <scan_id>
# PCAP metadata + download (decrypted, gzipped)
scanmalware pcap metadata <scan_id>
scanmalware pcap get <scan_id> # writes <scan_id>.pcap.gz
scanmalware pcap get <scan_id> --output scan.pcap.gz
# YARA matches, stats, and recent threats
scanmalware yara scan <scan_id>
scanmalware yara stats
scanmalware yara recent --hours 24 --limit 50
# Antivirus malware results, stats, and recent threats
scanmalware malware scan <scan_id>
scanmalware --timeout-secs 60 malware stats # stats endpoint can be slow
scanmalware malware recent --hours 24 --limit 50
# TLS/SSL certificate analysis
scanmalware tls cert <scan_id> # JSON: subject, issuer, validity, fingerprints
scanmalware tls asn1 <scan_id> # ASN.1 parsed structure + raw dump
scanmalware tls download <scan_id> # writes <scan_id>.pem
scanmalware tls download <scan_id> --output cert.pem
# Screenshots
scanmalware screenshot get <scan_id> # writes <scan_id>.png
scanmalware screenshot get <scan_id> --width 800 --output thumb.png
scanmalware screenshot get <scan_id> --image-format webp --output thumb.webp
scanmalware screenshot stats
scanmalware screenshot duplicates --hash-type phash --max-distance 0 --limit 20
scanmalware screenshot duplicates --hash-type crop_resistant --max-group-members 25
# --limit counts groups, not scans; --max-group-members caps members per group
# (default 10). Each member carries group_size with the true total.
# duplicates is exact-match only (--max-distance 0). For fuzzy matching against
# a single hash use `search screenshot-similar --max-distance 0..10`.
# Search OCR text
scanmalware search ocr "login" --limit 5
# CSV output for a search
scanmalware search scans "example" --limit 10 --format csv
# Search technologies (top-level command)
scanmalware technologies search --query cloudflare --limit 5
# Batch scan (NDJSON output)
scanmalware scan --batch urls.txt --format json > scans.ndjson
# Search by screenshot hash (hex values)
scanmalware search screenshot-hash phash bc3c3cc1c3c3c3c3 --limit 5
scanmalware search screenshot-hash dhash 2026000000000000 --limit 5
scanmalware search screenshot-hash color_hash 87b3c5 --limit 5
# Search similar screenshots (integer hash values)
scanmalware search screenshot-similar 13563782980643832771 --hash-type phash --max-distance 5 --limit 5
scanmalware search screenshot-similar 2316539058328698880 --hash-type dhash --max-distance 5 --limit 5
# TLSH search (JS segments)
scanmalware search js-segments tlsh <tlsh_hash> --max-distance 30 --limit 50 --include-known-libraries trueNotes:
- Use
screenshot_hashes.*_hexforsearch screenshot-hash. - Use
screenshot_hashes.*_intforsearch screenshot-similar. - Color hashes work with or without the leading
#, but#starts a comment in most shells, so quote it:search screenshot-hash color_hash '#87b3c5'. Values are percent-encoded before being placed in the URL, so hashes containing/(ssdeep) or other reserved characters are passed through intact.
Batch mode accepts a file path or - for stdin. Batch JSON output is NDJSON.
scanmalware scan --batch urls.txt
scanmalware result --batch scan_ids.txt
cat urls.txt | scanmalware scan --batch --
text(default): human-readable tree output with optional ANSI color. -
json: pretty-printed JSON for a single response. -
csv: one row per response, one column per top-level key, with nested values encoded as JSON strings. In batch mode this is one row per item over the union of all keys. Add--csv-expandto get one row per item of the response's list field (results,threats,groups, ...) instead, which is usually what you want for a search or a listing:scanmalware --csv recent --limit 20 # 1 row, results as a JSON blob scanmalware --csv --csv-expand recent --limit 20 # 20 rows, one column per field
-
raw: raw response body (useful for binary endpoints).
Use --quiet to suppress the banner, batch headers, warnings, and progress output.
Use --silent to suppress all output, including errors.
Use --color auto|always|never for text output.
SMQL is the unified query language for searching the scan index. Supports field
filters, boolean operators (AND, OR, NOT), negation (-field:value),
wildcards (domain:*bank.com, url:*login*), ranges
(js_risk_score:60..100), relative dates (submitted:last7d), and existence
checks (has:malware).
Note: on domain:, leading-dot wildcards like *.bank.com are not currently
matched server-side β use *bank.com or *bank* instead. The host filters
below do support that form.
smql filters lists every available filter. The host-oriented ones are useful
for mapping a domain's footprint, and unlike domain: they accept
*.example.com:
| Filter | Meaning |
|---|---|
contacted_host |
Host the browser actually resolved and requested. Observed evidence. |
script_host |
Host of a script the browser actually loaded. Observed evidence. |
js_host |
Host called from a URL sink inside script source. Static finding. |
csp_host |
Host named in the page's CSP. Declared intent, not contact. |
ct_domain |
Domain seen in Certificate Transparency logs. |
ct_san |
Certificate Subject Alternative Name. |
These return scans, not hostnames. To turn them into a subdomain list, pull the names out of the per-scan endpoints β certificate SANs give the highest yield:
# every subdomain named in the certificates of scans covering the apex
scanmalware --quiet --json smql query 'ct_san:*.example.com' --limit 25 \
| jq -r '.results[].scan_id' \
| while read -r id; do scanmalware --quiet --json tls cert "$id"; done \
| jq -r '.sans.dns_names[]?' \
| grep -E '(^|\.)example\.com$' | sort -u
# hosts a scan actually talked to
scanmalware --quiet --json smql query 'contacted_host:*.example.com' --limit 25 \
| jq -r '.results[].scan_id' \
| while read -r id; do scanmalware --quiet --json result "$id"; done \
| jq -r '.domains_contacted[]?' \
| grep -E '(^|\.)example\.com$' | sort -uCoverage is passive: it reflects what the index has scanned, so a domain whose hosts present a large multi-SAN certificate yields far more than one behind a wildcard cert.
scripts/subdomains.sh wraps all of this up:
scripts/subdomains.sh example.com # default 25 scans per source
scripts/subdomains.sh example.com 50# Run a query
scanmalware smql query "domain:paypal.com"
scanmalware smql query "(technology:WordPress OR technology:Joomla) AND country:CN" --limit 50
scanmalware smql query "has:malware AND submitted:last7d" --sort oldest
# Index stats
scanmalware smql stats
# All available filters, grouped by category
scanmalware smql filters --format jsonOptions on smql query: --page, --limit (max 100), --sort
(newest|oldest|url|load-time|ip-count).
Negated queries start with -, so place them after --:
scanmalware smql query --limit 10 -- "-domain:google.com login"Search commands are organized under scanmalware search. Use scanmalware search --help
to see the full list.
Core search:
search scanssearch asnsearch ipsearch ip-statssearch jarmsearch faviconsearch favicon-mmh3search fuzzysearch clipboard-suspicioussearch ocrsearch ocr-patternsearch screenshot-hashsearch screenshot-similarsearch semanticsearch similarsearch cpesearch registrarsearch technologies
AI and analyzer search:
search ai classificationsearch ai high-risksearch ai scam-typesearch analyzers high-risk
JavaScript search:
search js-fingerprinter2 code-hashsearch js-fingerprinter2 composite-hashsearch js-fingerprinter2 coveragesearch js-fingerprinter2 healthsearch js-fingerprinter2 js-obfuscationsearch js-fingerprinter2 malware-familiessearch js-fingerprinter2 signaturesearch js-fingerprinter2 similarsearch js-segments hashsearch js-segments normalizedsearch js-segments tlshsearch js-fingerprints bundlersearch js-fingerprints fuzzysearch js-fingerprints librarysearch js-fingerprints library-versionsearch js-fingerprints md5search js-fingerprints normalizedsearch js-fingerprints sha1search js-fingerprints sha256
Use request for arbitrary JSON endpoints and download for binary payloads.
scanmalware request --method get --path /api/v1/search --query q=example --query page=1
scanmalware download --path /api/v1/pcap/<scan_id> --output scan.pcap.gzFor packet captures specifically, prefer the dedicated pcap command:
scanmalware pcap metadata <scan_id> # JSON: file_size_bytes, available, etc.
scanmalware pcap get <scan_id> # download to <scan_id>.pcap.gz
scanmalware pcap get <scan_id> --output my.pcap.gz # custom pathThis project uses reqwest with rustls to reduce OS-specific dependencies. The
scripts/build-release.sh script builds Linux, Windows, and macOS targets.
cargo install cargo-zigbuild
# Install Zig from https://ziglang.org/download/
./scripts/build-release.shNotes:
- Building macOS targets on non-macOS hosts requires a macOS SDK (for example via
osxcross). - You can also build native binaries on each OS with
cargo build --release.
- Negative mmh3 hashes are positional arguments; use
--to avoid flag parsing:scanmalware search favicon --limit 1 -- -1670507450 - Increase
--timeout-secsif requests time out on long-running endpoints.