Skip to content
atomdrift-projectPublic

About

0-day malware detection for binaries, source & scripts (that doesn't suck)

Topics

Resources

Stars

222 stars

Watchers

1 watching

Forks

Latest commit

 

History

1,296 Commits

Folders and files

Repository files navigation

Atomdrift Scan

Latest release License

Atomdrift Scan is a modern ML-based malware scanner designed to detect 0-day attacks against the software supply-chain.

It's designed to be deterministic, fast, and flexible, and embeddable in any workflow or security tool you have in mind, and can operate against files, archives, URLs, PURLs, or processes.

As of September 2026, Atomdrift Scan has a 98% 0-day detection rate, +34% ahead of any other scanner: commercial or open.

Atomdrift Scan terminal output

How does Atomdrift get such great results? First, it covers more ground than any other scanner:

  • 100+ supported file formats: from C source to ELF to PDF
  • 125,000+ detection rules covering malware on every platform from AIX to iOS to Windows
  • 15,000,000+ hashes for known good/badware
  • Integrated AST analysis using tree-sitter
  • Automated binary reverse engineering via rizin

Most importantly, rules are constantly refreshed using reinforcement learning against new samples, blogs, and technical articles, resulting in ~1000 updated rules daily.

Install

If you are an a UNIX-flavored host (macOS, Linux, BSD, Solaris, illumos, Android):

curl -fsSL https://install.atomdrift.org/scan.sh | sh

If you are on Windows:

irm https://install.atomdrift.org/scan.ps1 | iex

Or, if you have Rust installed and just want to build it from source:

make install

If deeper binary analysis is required, you should also install 7-Zip, rizin, upx, and innoextract.

On Windows, using winget:

winget install --exact --id 7zip.7zip
winget install --exact --id Rizin.Rizin
winget install --exact --id UPX.UPX
winget install --exact --id dscharrer.innoextract

On Unix-like systems, install upstream 7-Zip (brew install sevenzip, apt install 7zip) rather than p7zip — its 7z cannot read APFS, so .dmg contents go unscanned.

Usage

# Scan a file, directory, or archive recusively:
atomscan ./project
atomscan release.tgz

# Fetch a package from its registry and scan it.
atomscan purl npm/left-pad@1.3.0

# Fetch and scan a URL.
atomscan url https://example.com/download

# EXPERIMENTAL: Scan running process executables, or triage the wider host
atomscan ps
atomscan sys

# JSON output for programmatic access
atomscan -f json ./project

Exit codes make CI integration trivial:

  • 0: all samples are benign
  • 1: hostile sample detected
  • 2: suspicious sample detected
  • 3: analysis error
  • 4: the rule set was incomplete — the scan ran with fewer rules than the trait set defines, so anything short of hostile proves nothing. Re-run it.

How it works

Diagram

  1. stng extracts content, even if obfuscated
  2. cleave unpacks containers and extracts capabilities
  3. The report is converted into a standardized feature vector, standardized across file types.
  4. azoth LightGBM model ensembles score the sample.

Networking and privacy

atomscan will never send telemetry data. It will however reach out to the Internet for 2 reasons:

  • rule updates: every 24h, can be disabled using --no-update or SCAN_NO_UPDATE_CHECK=1
  • following discovered references: to detect if a benign package depends on or downloads a compromised payload. Set --follow=none to analyze only the requested artifact.

Downloaded dependencies are analyzed under the current rules unless a local analysis report or a matching-analyzer corpus verdict can be reused. A recent clean verdict from another analyzer does not replace analysis of downloaded bytes. Package-coordinate lookups may still use that recent clean verdict to avoid downloading an already-known dependency.

Tune the false-positive level

Unlike other malware scanners, Atomdrift allows you to adjust sensitivity in terms of an acceptable false-positive level using the -l flag:

  • -l0: tight, sets the confidence cutoff to a point where no false-positives have been observed.
  • -l25: the default shipping point: 25 false-positives per 100 million files.
  • -l1000: loose, roughly 1 false positive per 100,000 files.

NOTE: For file formats where we don't have 100 million samples, the observed false-positive rate may be up to 5-6X the requested level. YMMV.

Raw --threshold-hostile and --threshold-suspicious overrides are available for users who want to micromanage probability thresholds directly, but these numbers are not guaranteed to be stable, and these flags cannot be combined with -l.

Optional local LLM (interpretation)

For additional interpretation, users can provide access to an LLM via the --llm flag. This flag serves two purposes:

  • Provides a large-language model text interpretation of the results
  • Steer edge cases based on agreement/disagreement with the ML model ( p to 33%)

By default, atomscan sends the interpreted evidence (not the original file) to http://localhost:8000/v1 - to be used with a local service like Ollama or vLLM; but it can also be setup to use a remote service like Claude, ChatGPT, or DeepSeek.

No model is hardcoded: unless you pass --llm-model, atomscan asks the endpoint which models it serves and uses the largest one it lists. We recommend serving Qwen/Qwen3.8-27B.

If the endpoint requires a bearer token, put it in ~/.tok/llm (mode 0600) and atomscan sends it automatically; --llm-key and SCAN_LLM_KEY override that file.

--llm also takes a comma-separated failover chain, tried in order — --llm 'https://llm.isotope13.ai/v1,openrouter' grades on your own endpoint and reaches for the billed one only when it cannot answer.

atomscan --llm ./project
atomscan --llm http://model-host:8000/v1 --llm-model my-model ./project
atomscan --llm openrouter --llm-model qwen/qwen3.8-27b ./project

--llm openrouter talks to https://openrouter.ai/api/v1. The key comes from --llm-key, SCAN_LLM_KEY, or ~/.tok/openrouter (first non-empty line). --llm-model defaults to openrouter/auto (OpenRouter's own auto-router) if omitted — the rest of the catalog is not auto-selected.

Linux make deploy accepts the same names: LLM=openrouter (or LLM_URL=openrouter), and LLM_MODEL=qwen/qwen3.8-27b only to pin a model instead of openrouter/auto. The unit cannot read the operator home (ProtectHome=true), so the deploy script copies ~/.tok/openrouter into the service state dir.

Coverage

The scanner recognizes more than 100 file and container types. Representative coverage includes:

Category Formats
Binaries and bytecode Mach-O, ELF, PE, WebAssembly, Android DEX, Java .class, Python .pyc, BEAM
Source Python, JavaScript, TypeScript, Go, Rust, Java, C, C++, C#, Ruby, PHP, Perl, Lua, Swift, Objective-C, Kotlin, Scala, Groovy, Zig, Elixir, Clojure, Shell, PowerShell, Batch, VBScript, AppleScript, JCL
Build, manifest, and lock files package.json, package-lock.json, Cargo.toml, Cargo.lock, pyproject.toml, requirements.txt, Poetry, Pipenv, Composer, Yarn, pnpm, Go modules, binding.gyp, GitHub Actions, systemd units, Makefile, Dockerfile
Archives and disk images ZIP, TAR, gzip, bzip2, XZ, zstd, 7-Zip, RAR, CAB, ASAR, DMG, ISO
Packages and containers deb, rpm, APK, npm, wheel, egg, sdist, gem, crate, conda, NuGet, IPA, CRX, XPI, VSIX, OCI/Docker images, FreeBSD, Arch, Void, and Gentoo packages
Documents and data OLE2, OOXML, OpenDocument, PDF, RTF, Markdown, HTML, XML, SVG, plist, JPEG, PNG, LNK, CHM, Python pickle

The rule set includes platform-specific behaviors for Linux, macOS, Windows, Android, iOS, the BSDs, AIX, Solaris, QNX, z/OS, ESXi, OpenWrt, VxWorks, RouterOS, FortiOS, PAN-OS, IOS-XE, Junos, NetScaler, and Ivanti appliances.

The default build matrix covers Linux, macOS, FreeBSD, OpenBSD, NetBSD, illumos, Solaris, and Windows across several CPU architectures. Test depth varies by target; see the build workflow for the current matrix.

Documentation

Measure model loading with make bench-model-load MODEL_LOAD_DIR=../azoth. It reports the first load and repeated timings, and fails if any load exceeds MODEL_LOAD_MAX_MS (300 ms by default). Add EAGER=1 to include every specialist; normal startup loads specialists on demand. These timings exclude trait loading, file analysis, and process/thread-pool startup, and repeated runs use the OS file cache. On Linux, make profile-model-load records a CPU profile with perf.

For the complete VSIX throughput check on Linux, use an existing scanner and Rizin build:

sh scripts/bench-vsix.sh /var/tmp/vsix out/vsix-perf/rust-native-pgo/atomscan \
  out/rizin-perf-static-pgo/bin/rizin out/vsix-perf/goal

This requires all five fresh-process scans to finish within 47 seconds, with one output per input and no errors or skips. It disables persistent analysis caches, retains compiled-rule caches and bounded in-process memoization, and writes every result and timing plus a series summary. VSIX_RUNS, VSIX_LIMIT, and VSIX_NATIVE_CPUS override the defaults of 5, 47, and 0,1. Large native helpers use those CPUs; the scanner excludes their SMT siblings. The scheduling settings in the script can each be overridden through the environment.

The preset uses 96 scan workers, 32 workers per large-file pool, a 4 GiB analysis memo, and delayed jemalloc page purging. On the measured 64-core Threadripper host, give the private large-file pools the faster CPU groups:

CLEAVE_LEAF_CPUS=2-7,24-39,56-63,66-71,88-103,120-127 \
  sh scripts/bench-vsix.sh /var/tmp/vsix out/vsix-perf/rust-native-pgo/atomscan \
  out/rizin-perf-static-pgo/bin/rizin out/vsix-perf/goal

That CPU mask is host-specific: omit or replace it on other machines. CLEAVE_LEAF_CPUS is opt-in on Linux and intersects each private worker's inherited affinity. It leaves the general scan pool's placement unchanged.

scripts/build-balanced-pgo.sh CORPUS OUTPUT_DIR [CARGO_CONFIG] builds a scanner for the current CPU, trains it on cache-disabled analysis, and rebuilds with the resulting Rust profile. PGO_TRAIN_SECONDS optionally bounds training; an interrupted training run never counts as a passing benchmark. Set PGO_USE_EXISTING=1 to rebuild from the retained profile, and LLVM_PROFDATA to the tool matching rustc -vV; use a Cargo config override when testing edited sibling dependencies. For Rizin, RIZIN_NATIVE=1 RIZIN_PGO_TRAIN_DIR=DIR sh scripts/build-rizin-perf.sh also enables native compilation and PGO. That training directory should contain extracted native binaries, such as the corpus's ELF and PE Go helpers. Set RIZIN_STATIC=1 to link Rizin's own libraries statically, allowing LTO across their boundaries; system runtime libraries remain dynamic. These build scripts retain their output locally and do not replace installed tools.

Related projects

  • cleave — capability extraction and static analysis
  • azoth — model weights, thresholds, and feature specification
  • hopper — distributed work queue
  • Atomdrift Lab — free sample analysis

License

Atomdrift Scan is available under the Apache License 2.0.

All contributions welcome!

About

0-day malware detection for binaries, source & scripts (that doesn't suck)

Topics

Resources

Stars

222 stars

Watchers

1 watching

Forks

Releases

Contributors

Languages