Skip to content

About

Command-line client for the ScanMalware API

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Latest commit

Β 

History

69 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ§ͺ ScanMalware CLI

Rust command-line client for the ScanMalware API.

API docs:

✨ Features

  • 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).

πŸ“¦ Install

Quick install (macOS/Linux)

curl -fsSL https://scanmalware.com/install.sh | bash

Windows (PowerShell)

[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; irm https://scanmalware.com/install.ps1 | iex

Homebrew

brew tap scanmalware/tap && brew install scanmalware/tap/scanmalware-cli

Homebrew 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/tap

Supported 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 | bash

Build from source

cargo install --path .

Or build a local binary:

cargo build --release

🐳 Docker

Run 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 5

The 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.png

If you want to pass environment variables:

docker run --rm \
  -e SCANMALWARE_BASE_URL=https://scanmalware.com \
  -e SCANMALWARE_TIMEOUT=60 \
  jonaslejon/scanmalware-cli:latest health

βš™οΈ Configuration

  • SCANMALWARE_BASE_URL (default: https://scanmalware.com)
  • SCANMALWARE_TIMEOUT (request timeout in seconds)

Command-line flags override environment variables. No authentication is required.

πŸš€ Usage

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 --help
scanmalware 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 ping

Global flags can be placed before or after the subcommand:

scanmalware --format text --color always stats
scanmalware stats --format text --color always

Global 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

πŸ§ͺ Examples

# 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 true

Notes:

  • Use screenshot_hashes.*_hex for search screenshot-hash.
  • Use screenshot_hashes.*_int for search 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

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 -

🧾 Output formats

  • 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-expand to 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 β€” ScanMalware Query Language

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.

Host filters and subdomain discovery

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 -u

Coverage 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 json

Options 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

Search commands are organized under scanmalware search. Use scanmalware search --help to see the full list.

Core search:

  • search scans
  • search asn
  • search ip
  • search ip-stats
  • search jarm
  • search favicon
  • search favicon-mmh3
  • search fuzzy
  • search clipboard-suspicious
  • search ocr
  • search ocr-pattern
  • search screenshot-hash
  • search screenshot-similar
  • search semantic
  • search similar
  • search cpe
  • search registrar
  • search technologies

AI and analyzer search:

  • search ai classification
  • search ai high-risk
  • search ai scam-type
  • search analyzers high-risk

JavaScript search:

  • search js-fingerprinter2 code-hash
  • search js-fingerprinter2 composite-hash
  • search js-fingerprinter2 coverage
  • search js-fingerprinter2 health
  • search js-fingerprinter2 js-obfuscation
  • search js-fingerprinter2 malware-families
  • search js-fingerprinter2 signature
  • search js-fingerprinter2 similar
  • search js-segments hash
  • search js-segments normalized
  • search js-segments tlsh
  • search js-fingerprints bundler
  • search js-fingerprints fuzzy
  • search js-fingerprints library
  • search js-fingerprints library-version
  • search js-fingerprints md5
  • search js-fingerprints normalized
  • search js-fingerprints sha1
  • search js-fingerprints sha256

🧩 Generic request and download

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.gz

For 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 path

🧱 Cross-compiling

This 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.sh

Notes:

  • 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.

πŸ› οΈ Troubleshooting

  • Negative mmh3 hashes are positional arguments; use -- to avoid flag parsing: scanmalware search favicon --limit 1 -- -1670507450
  • Increase --timeout-secs if requests time out on long-running endpoints.

About

Command-line client for the ScanMalware API

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages