Engines
malware-detector runs established detection engines and merges their output into one report. Four engines are supported:
| Engine | Used by | How it's installed | What it does |
|---|---|---|---|
| Semgrep | scan | Included (pip dependency, pinned 1.121.0) | Runs the malware rule pack on code structure and data flow |
| Heuristics (built in) | scan | Part of malware-detector | Flags packed or obfuscated lines that have no useful structure to parse (obf-packed-line) |
| GuardDog | deps | Included (pip dependency, 3.x) | Supply-chain checks on an npm or PyPI package: malicious behaviours and suspicious metadata |
| malcontent | scan --with-malcontent | Optional: brew install malcontent | YARA signatures for binaries, archives and heavily obfuscated webshells |
Which engines run when
| Command | Engines |
|---|---|
scan | Semgrep + heuristics |
scan --with-malcontent | Semgrep + heuristics + malcontent |
scan --staged / --changed | Semgrep + heuristics (malcontent is not used, so hooks stay fast) |
deps | GuardDog |
The last line of every report names the engines that ran:
Scanned 7 files (Python 3 · JavaScript 2 · PHP 1 · TypeScript 1) with semgrep + heuristics in 1.3s.Semgrep
Semgrep parses JavaScript, TypeScript, Python and PHP and follows data through the code. That lets the rules tell eval(atob(payload)) apart from eval("1 + 1"). Its version is pinned exactly: rule matching can change between Semgrep releases, and every version bump is re-measured against the benchmark.
Files Semgrep can't parse are reported, not skipped. If part of a file can't be parsed, you get an engine-partial-parse finding for those lines. If the whole file fails, for example because of a timeout, you get engine-scan-failed. Malware can hide in code a scanner can't read, so these findings are never dropped silently. Details →
Semgrep only parses files that could match a rule, so a file with nothing suspicious in it won't produce a parse error even if it contains invalid code.
Heuristics
Some malware is a single very long line of encoded data and a decoder. Structural rules have little to work with there, so a built-in check flags lines that are both unusually long and contain several obfuscation markers (fromCharCode, atob(, _0x, \x escapes…). It runs alongside Semgrep with no extra install.
GuardDog
GuardDog, by Datadog, looks at a package as a whole. It checks for known malicious behaviours and for warning signs in the metadata, such as typosquatted names. Its results are mapped onto the shared severity scale:
| GuardDog rule | Severity |
|---|---|
threat-* | CRITICAL |
| metadata rules | HIGH |
capability-* | MEDIUM |
malcontent
malcontent, by Chainguard, matches YARA signatures. It finds things code parsers miss, such as compiled binaries, archives and heavily obfuscated PHP. On the benchmark, adding it raises recall from 73% to 91%.
It is a standalone Go binary and is not installed with malware-detector. Install it with Homebrew; go install usually fails because it needs native YARA libraries:
brew install malcontentString-based signatures can't see code structure, so malcontent findings on source files are capped at MEDIUM. They help catch more malware but can't fail a HIGH gate on their own. Findings on binaries and archives keep their original severity.
Engines are pinned to your install
malware-detector runs the Semgrep and GuardDog that were installed with it, even when it is installed with uv tool or pipx. A different Semgrep on your PATH won't change your results.
Not supported
- Opengrep, the open-source fork of Semgrep, uses the same command line and output format but can't be selected from the CLI yet.
- Known-vulnerability scanning (CVE checks with osv-scanner or grype) isn't available. malware-detector finds malicious code. Use
npm audit,pip-auditor Dependabot alongside it for known vulnerabilities.