Skip to content

Why malware-detector? ​

Malicious code rarely shows up looking malicious. It arrives as a "telemetry" helper, a postinstall script in a dependency, or one long minified line in a file nobody reviews. By the time it runs, it has already read your .env, your cloud credentials or your users' cookies.

malware-detector looks for that behaviour before the code runs. Point it at a directory or a package and it tells you, in a few seconds, whether anything in it acts like malware.

Who it's for ​

  • Developers who install packages every day and want a quick check before npm install or pip install.
  • Maintainers who review outside contributions and want a second pair of eyes on every pull request.
  • Security and platform teams who need a malware gate in CI that runs on any provider, including air-gapped and self-hosted runners.
  • Agencies and freelancers who take over existing codebases (often PHP) and need to know whether a webshell is hiding in them.

What makes it different ​

It looks for malicious behaviour, not just bugs ​

Most code scanners look for vulnerabilities: mistakes an attacker could exploit. malware-detector looks for intent: code that is already trying to steal, hide or execute something. Its rules track how data moves. A secret flowing into an HTTP request, or a decoded string flowing into eval, is a much stronger signal than the presence of eval on its own.

It combines engines instead of betting on one ​

No single engine catches everything. Each one sees a different part of the problem:

EngineWhat it is good at
Semgrep + our rule packStructure and data flow in source code (taint from secrets to the network)
Built-in heuristicsPacked and obfuscated lines that have no useful structure to parse
GuardDogPackage metadata and known malicious package behaviours on npm and PyPI
malcontent (optional)YARA signatures for binaries, archives and heavily obfuscated webshells

More about each engine →

malware-detector runs them together, merges overlapping findings, ranks everything by severity and gives you one report, one ignore file and one exit code. On a real-world corpus, the combined setup catches more malware than any single engine (see the benchmark).

Your code stays on your machine ​

There is no service behind it. Scans run locally or on your CI runner, with no account, token or upload. The Docker image runs with a read-only mount and needs no network access, so you can safely scan code you don't trust.

It fits the workflow you already have ​

  • A --staged mode that finishes in seconds for pre-commit hooks.
  • A --changed mode for pre-push.
  • SARIF output that shows up in GitHub's Security → Code scanning tab.
  • JSON output for dashboards and scripts.
  • Exit codes that any CI understands: 0 clean, 1 findings, 2 usage error, 3 engine error.

How it compares ​

malware-detectorDependabot / npm auditGeneric SAST linters
Detects new, unreported malware✅❌ (known CVEs only)⚠️ rarely
Scans your own source✅❌✅
Scans third-party packages✅✅❌
Works offline / any CI✅⚠️ GitHub / onlinevaries
JS, TS, Python and PHP in one tool✅per-ecosystemvaries

These tools work well together. npm audit tells you a package has a known vulnerability. malware-detector tells you a package, or your own code, behaves like malware even when nobody has reported it yet.

What it is not ​

Being clear about limits makes the tool easier to trust:

  • It doesn't run your code. It is a static analyser, so it can't observe behaviour that only appears at runtime, such as code downloaded later.
  • Heavily obfuscated PHP webshells are the hardest case. Turn on --with-malcontent if PHP is a priority for you.
  • A finding is a signal, not a verdict. Low-confidence rules report at MEDIUM so you can review them. Use --min-severity HIGH if you only want to see the strongest signals.

Ready? Run your first scan →