Skip to main content
Glama
scanything

scanything

by arielfikru

Scanything

Scan any file for malware, with an AI that explains false positives.

Scanything dissects a .zip, .exe, .dll or a GitHub repository without ever running it, tells you whether it looks malicious, explains whether an antivirus alert is probably a false positive, and lets you chat about the result. It works inside Claude Code and Codex as an MCP server, or on its own with your API key.

🇮🇩 Bahasa Indonesia: docs/README.id.md

Quick start

pip install "scanything[pe] @ git+https://github.com/arielfikru/scanything"

Then pick one:

scanything setup                 # use it from Claude Code / Codex (agent mode)
export NETRA_API_KEY=...         # or OPENROUTER_API_KEY / OPENAI_API_KEY
scanything ~/Downloads/file.zip  # standalone AI analyst (orchestrator mode)

No key and no agent? scanything file.zip --no-llm still gives the full static report.

Checking a repository before you clone it? scanything https://github.com/OWNER/REPO downloads the archive pinned to one commit and scans it in memory. Nothing is cloned, built or run.

Related MCP server: Malware-Sandbox-mcp

Two modes, one engine

🤖 Agent mode (MCP)

🧠 Orchestrator mode

The AI is

your coding agent: Claude Code, Codex, any MCP client

Scanything's built-in analyst

You need

the agent you already use

an API key: Netra, OpenRouter, OpenAI, or any OpenAI-compatible server

Start with

scanything setup, then ask your agent

scanything file.zip

Good for

working in your editor or terminal agent

standalone use, scripts, CI

Both modes use the same tools: scan_file, scan_github, get_report_section, search_strings, list_files, vt_lookup.

🤖 Agent mode: Claude Code & Codex

scanything setup              # both agents
scanything setup claude       # only Claude Code
scanything setup --dry-run    # show what would happen

Agent

MCP server

Skill

Claude Code

claude mcp add --scope user scanything -- <python> -m scanything mcp

~/.claude/skills/scanything/SKILL.md

Codex

codex mcp add scanything -- <python> -m scanything mcp

~/.codex/skills/scanything/SKILL.md

No codex CLI on PATH? Add this to ~/.codex/config.toml:

[mcp_servers.scanything]
command = "/path/to/python"
args = ["-m", "scanything", "mcp"]

Restart the agent and ask:

> is ~/Downloads/Game_Setup.zip a virus? Defender says Wacatac

The agent calls scan_file, digs with search_strings and get_report_section, and answers with a verdict, the false-positive reasoning, the scan_id, and a next step.

🧠 Orchestrator mode: your own API key

Put one key in ~/.config/scanything/.env (template: .env.example) or export it, then:

$ scanything Game_Setup.zip --vt

== scanything: Game_Setup.zip ==
scan_id scn_9f2c41d0b7e3a8c5d1e6f402  ·  file_id (sha256) 5d41…c2a9  ·  ruleset rs_ace23f4945c8
Heuristic verdict: SUSPICIOUS  (score 11, 3 file(s), static only)

  #  type         size  score  name
  0  zip        4.1 MB      4  Game_Setup.zip
  1  pe         9.8 MB      7    └ Setup.exe
  2  text        312 B      0    └ readme.txt

[*] Asking deepseek/deepseek-v4-flash-0731 via netra for a verdict...
   ↳ get_report_section(files[1].pe.suspicious_imports)
   ↳ search_strings({"file_index": 1, "pattern": "webhook|telegram"})

## Verdict: LIKELY FALSE POSITIVE (confidence 75%)
...
verdict_id vrd_3b8e…  ·  scan_id scn_9f2c41d0b7e3a8c5d1e6f402
you> why does Defender call it Wacatac?

Providers

--provider

Key

Default model

netra ⚡ recommended

NETRA_API_KEY

deepseek/deepseek-v4-flash-0731

openrouter

OPENROUTER_API_KEY

anthropic/claude-haiku-5.5

openai

OPENAI_API_KEY

gpt-6-luna

custom

LLM_API_KEY + LLM_BASE_URL

LLM_MODEL

  • Without --provider, Scanything uses LLM_PROVIDER, else the first key it finds (Netra → OpenRouter → OpenAI).

  • --model or LLM_MODEL picks any other model. scanything models lists what your provider serves right now.

  • Models without tool calling still work: Scanything falls back to plain chat, or pass --no-tools.

Options

scanything setup.exe --vt                   # VirusTotal hash lookup (never uploads)
scanything locked.zip --password infected   # encrypted archives
scanything file.exe --no-llm --json-out file.report.json   # offline, no AI
scanything file.exe --no-chat --md-out verdict.md          # CI / batch
scanything file.exe --provider openrouter --model anthropic/claude-sonnet-5.5 --lang en
scanything https://github.com/OWNER/REPO --no-llm             # a repository, pinned to its latest commit
scanything github:OWNER/REPO@v1.2.0                          # a branch, tag or commit

Flag

Meaning

--vt, --vt-max N

VirusTotal lookup of up to N hashes (needs VT_API_KEY)

--clamav

also run clamscan

--yara PATH

YARA rule file or folder

--password

zip password (infected, malware, virus are always tried)

--no-llm, --no-chat, --no-tools

static only, verdict only, model without function calling

--provider, --model, --base-url, --lang

override .env

--json-out, --md-out

save the full report, save the AI transcript

GITHUB_TOKEN (optional) raises GitHub's rate limit and gives access to private repositories you can read.

In chat: /save [file.md], /exit. Config is read from ./.env, then ~/.config/scanything/.env.

What it checks

Layer

What Scanything looks at

Archive

nested zips, zip-slip paths, zip bombs, password-protected executables, decoy names (invoice.pdf.exe, RLO tricks), scripts, shortcuts and disk images inside

PE (.exe, .dll)

sections and entropy, W+X sections, entry point, imports grouped by behaviour (injection, keylogging, download, anti-debug…), Authenticode presence and certificate names, version info, overlay including anti-scan padding, embedded PEs

Toolchain DB

a Detect It Easy style database of about 40 signatures: installers (NSIS, Inno, WiX, 7-Zip SFX…), runtimes (PyInstaller, Nuitka, AutoIt, Go, Rust, .NET, Electron…), compilers (MSVC Rich header, MinGW, Delphi, VB6), packers (UPX, ASPack, MPRESS…), protectors (VMProtect, Themida, Enigma…) and .NET obfuscators, each with its false-positive note

Strings & IOCs

URLs, IPs, registry keys, PowerShell cradles, LOLBins, shadow-copy deletion, Defender tampering, stealer paths, Discord/Telegram exfil, miners, ransom notes, EICAR, prompt-injection text

Hidden strings

FLOSS-style recovery without emulation: x86 stack strings, single-byte XOR, base64 blobs and embedded PEs, PowerShell -EncodedCommand. Decoded text goes through the IOC rules and search_strings (prefixed [decoded])

Source code

in repositories and any archive: npm install hooks, setup.py that execs or downloads, exec(base64…), eval(atob…), VS Code tasks that run on folder open, MSBuild build events, workflows that leak secrets or run fork PRs with secrets, curl … | sh, code hidden far right, Trojan Source bidi characters

Repository

stars against owner-account age (star farming), new repository, fork, archived. Metadata never changes scan_id; the code is pinned by commit

Optional engines

VirusTotal hash lookup, ClamAV, YARA, osslsigncode signature verification

MITRE ATT&CK

Signals map to the ATT&CK technique they are typical of, like capa does, and the summary lists every technique with its evidence (T1490 Inhibit System Recovery <- #1 shadow_copy_delete). A mapping means "this evidence is typical of the technique", not proof that the file performs it.

How false positives are judged

Every signal carries an fp_note saying when it is harmless, and the AI weighs those against the evidence.

Usually a false positive

Usually real

1–3 VirusTotal engines, all generic/ML names (Wacatac, Generic, ML.Attribute, Artemis)

named malware families across many engines

PyInstaller, Nuitka, AutoIt or Go binaries

injection + network + persistence together

UPX packing, NSIS or Inno Setup installers

stealer paths plus an exfil channel

signed by a known publisher (verified with osslsigncode)

decoy extensions, padded overlays, encrypted archives hiding an exe

Scan IDs

Every result gets IDs, like the hash-based IDs of malware scanning platforms.

ID

Deterministic?

Computed from

file_id

✅ always

SHA-256 of the file (same as VirusTotal)

scan_id

✅ same file + name + Scanything version + ruleset

hash of the static analysis

ruleset

✅

hash of every detection rule table

verdict_id

❌ one per AI answer

hash of scan_id + provider + model + answer

scan_id ignores volatile data (timestamps, local path, VirusTotal/ClamAV results), so anyone scanning the same file with the same version gets the same id. It also seals the report:

from scanything.ids import scan_id_of
assert scan_id_of(report) == report["ids"]["scan_id"]   # fails if the JSON was edited

With --json-out, the report also holds verdict = {verdict_id, scan_id, label, confidence, provider, model, created}.

Try it safely

python scripts/make-test-samples.py   # harmless samples in ./samples (gitignored)
scanything samples/eicar.zip          # EICAR: every AV flags it, it does nothing
scanything samples/stealer_lookalike.exe

The samples are one-instruction PE stubs with malware-like strings, plus the standard EICAR test file. None of them does anything when run.

We highly recommend Netra Runtime for orchestrator mode: fast inference, an OpenAI-compatible API, and DeepSeek V4 Flash 0731 handles the tool calling the analyst relies on. It is the first provider Scanything looks for, so NETRA_API_KEY is all you need. Save a key from your clipboard with scripts/save-netra-key (macOS).

The recommendation appears in this README, as a CLI hint when no key is set, as a one-line footer when Netra produced the verdict, and in the agent instructions only if you ask which provider to use. It never appears inside a verdict or affects how files are judged. SCANYTHING_NO_RECOMMEND=1 hides it.

How it works

file or GitHub repo ──► scanner (recursive, in memory, never executed)
           ├─ static: type, hashes, entropy, strings/IOCs, PE, toolchain DB, decoded strings, code rules
           ├─ archive: entries, passwords, bombs, nested files
           └─ engines (optional): VirusTotal, ClamAV, YARA, osslsigncode
        ──► report: signals + fp_notes + heuristic score + file_id / scan_id
        ──► tools: scan_file · scan_github · get_report_section · search_strings · list_files · vt_lookup
              ├─ MCP server ──► Claude Code / Codex / any MCP client     (agent mode)
              └─ built-in analyst ──► Netra / OpenRouter / OpenAI       (orchestrator mode)

Module

Role

static.py

file type, string and IOC rules, PE parser

signatures.py / deobf.py

toolchain signature DB / hidden-string recovery

repo.py

GitHub download pinned to a commit, source code and metadata rules

attack.py

signal → MITRE ATT&CK mapping

scanner.py

recursive scan, zip handling, YARA, signature verification

engines.py

VirusTotal and ClamAV

api.py

scan_path(), shared by the CLI and the MCP server

report.py / ids.py

report, compact AI view, terminal summary / deterministic ids

agent.py / providers.py

OpenAI-compatible client, tool loop, chat / provider presets

mcp_server.py / setup_agents.py

MCP server (stdio) / scanything setup

skills/SKILL.md

agent skill

Content from a scanned file is untrusted: both the MCP instructions and the analyst prompt say so, and text that tries to steer an AI is flagged as a prompt_injection signal.

Limits

  • Static analysis can never prove a file is safe. When in doubt, use a sandbox (ANY.RUN, Hybrid Analysis, Tria.ge) or the vendor's published hash.

  • No machine-learning score: there is no trained, openly licensed model small enough for a stdlib-only core, and an unexplained number would undercut the false-positive reasoning. VirusTotal results already include vendors' ML engines.

  • Hidden-string recovery is pattern based, not emulation: multi-byte XOR, RC4 and runtime-built strings are not recovered.

  • Repositories: up to 10,000 archive entries and 3,000 analyzed files, 512 MiB archive. Only what is in the repository is scanned, not the dependencies it pulls at install time.

  • 7z, RAR and ISO are detected but not unpacked yet: extract them without running anything, then scan the contents.

  • Authenticode is only verified when osslsigncode is installed; otherwise Scanything reports its presence and certificate names.

  • The AI can be wrong. Treat its verdict as a second opinion with cited evidence.

Development

git clone https://github.com/arielfikru/scanything && cd scanything
python -m venv .venv && .venv/bin/pip install -e ".[dev]"
.venv/bin/pyflakes scanything tests && .venv/bin/pytest -q

See CONTRIBUTING.md, AGENTS.md and SECURITY.md. Never commit live malware.

License

MIT, see LICENSE.

Available Tools

5 tools
get_report_sectionA
Read-only

Return part of the FULL JSON report by path, e.g. 'files[1].pe.imports', 'files[0].archive.entries', 'files[2].iocs', 'engines'. Empty path lists top-level keys.

Inputs: path (required), scan_id (required).

list_filesB
Read-only

List all analyzed files with index, parent, type, size, score and sha256.

Inputs: scan_id (required).

scan_fileA
Read-only

Statically scan a file on disk (zip, exe, dll, script, anything) for malware and false-positive indicators. The file is never executed. Returns a compact report and a deterministic scan_id for follow-up tools.

Inputs: path (required), password, virustotal.

scan_githubA
Read-only

Download a public GitHub repository (zip archive pinned to one commit) and scan it statically: install hooks, obfuscated exec/eval, auto-run VS Code tasks, risky workflows, binaries inside, plus repository metadata. Nothing is cloned, built or run. Returns the same compact report and scan_id as scan_file.

Inputs: repo (required), virustotal.

search_stringsB
Read-only

Case-insensitive regex search over printable strings extracted from one file.

Inputs: pattern (required), scan_id (required), file_index (required), limit.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A3.9/5.0

    Scored across 5 tools

    Disambiguation5/5

    Each tool has a clearly distinct role: scan a local file, scan a GitHub repo, retrieve a report section, search strings in one file, or list analyzed files. The two scan tools differ by input source and the follow-up tools target different inspection needs.

    Naming Consistency5/5

    All tool names follow a consistent snake_case verb_noun pattern: scan_file, scan_github, get_report_section, search_strings, list_files. There are no mixed conventions or vague names.

    Tool Count5/5

    Five tools is well-scoped for a static malware scanning service. There are two entry-point scan tools and three focused follow-up tools, with no obvious redundancy.

    Completeness4/5

    The surface covers scanning local files and GitHub repos, then inspecting results via file listing, report sections, and string search. Minor gaps exist, such as no explicit full-report retrieval or batch scanning, but core workflows are covered.

    Maintenance

    ActivityMaintained
    ResponsivenessOutdated data

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      B
      maintenance
      Integrates 15+ static application security testing tools (Semgrep, Bandit, TruffleHog, etc.) with Claude Code AI, enabling automated vulnerability scanning and security analysis through natural language commands. Supports cross-platform operation with remote execution on dedicated security VMs.
      6
      MIT
    • A
      license
      Not graded
      quality
      C
      maintenance
      Enables scanning of AI agent code for security vulnerabilities such as prompt injection, tool abuse, and data exfiltration, directly from MCP-compatible clients like Claude Code.
      41 PyPI
      3
      LGPL 3.0
    • A
      license
      A
      quality
      D
      maintenance
      Enables scanning of Claude Code skills, plugins, or MCP servers for malware before installation via static analysis.
      1
      14 npm
      MIT