Zero-CVE malware: your scanner is looking for the wrong thing
Here is the single most important number in application security this year, and almost nobody's tooling is built for it:
The largest supply-chain campaigns of 2026, hundreds of malicious packages, millions of downloads, had zero CVEs.
Not “a CVE that arrived late.” No CVE at all. The packages weren't vulnerable in the classic sense. They were legitimate packages that turned hostile for a version or two, published by a phished or hijacked maintainer, or a brand-new package that was malicious from line one. There is no advisory to match against, because there is nothing to advise about a package that isn't “vulnerable”, it's just bad.
Most scanners are databases with a UI. They compare your dependency tree against a list of known-vulnerable versions and print the matches. That model has been the backbone of AppSec for a decade. In 2026 it has a structural blind spot the size of the actual threat.
Why the CVE model misses this entirely
Walk through what a CVE-matching scanner does when a package goes hostile:
- A maintainer account is phished. The attacker publishes
[email protected]with apostinstallhook that exfiltrates tokens. - Your Dependabot PR bumps you to
4.5.1, because it's the newest version and there is no advisory saying otherwise. - Your CVE scanner runs. It checks
4.5.1against the advisory database. No match, because no CVE has been filed. Green check. - You merge. The hook fires in CI. The scanner did its job perfectly, and it was worthless.
The scanner isn't broken. It answered the question it was asked, “does this version appear in the vulnerability database?”, correctly. It's just the wrong question for this class of attack. The right question is “does this package do something it has no business doing?” and a version-match database cannot ask it.
What actually catches a hostile package
None of these are silver bullets. Together they close most of the gap a pure CVE scanner leaves wide open.
1. Behavioral and heuristic signals, not just version matches. A package that adds a lifecycle hook it never had, or starts reading environment variables and opening network connections at install time, or ships an obfuscated blob, is suspicious regardless of whether it has a CVE. Reputation, install-time behavior, and sudden maintainer or publish-pattern changes are signals a version database doesn't carry.
2. Reachability, so the real signal isn't buried. The reason teams ignore their scanner is that it cries wolf, fifty transitive-dev-dependency “highs” nobody can act on. We wrote about that in why most ‘high’ CVEs in your scanner are noise. The point compounds here: if your report is 95% noise, the one genuinely anomalous package is invisible in the pile. Cutting the noise is what makes the real thing findable.
3. A reviewer that reasons about context, not a rules engine. This is where we lean on the LLM triage step deliberately, for the exact reasons in why we put an LLM in the triage loop on purpose. A rules engine can tell you a version string matches an advisory. It can't look at a package that just grew a preinstall hook, cross-reference that against what the package is supposed to do, and say “this is a build tool, why is it phoning home at install time?” That judgment, the part a careful engineer does in their head, is exactly where the model earns its place.
4. Full git-history secret scanning, for after the fact. When a hostile package does slip through, the first question is “what did it have access to?” If your secrets ever touched a commit, that's your exposure list. See what real git-history scanning needs to do.
The honest limits
We are not claiming to detect every malicious package the instant it publishes. Nobody can, and anyone who says they can is selling you a feeling.
- A sufficiently clean payload can evade behavioral heuristics. A package that waits, or only activates in production, or hides its logic well, will pass an install-time behavioral check. Heuristics raise the attacker's cost; they don't make it infinite.
- Zero-hour is still zero-hour. In the minutes between a hostile publish and any signal existing anywhere, there is a window. What matters is how fast you re-check once a signal does exist, which is why we run a daily self-scan against everything you already have, not just new PRs.
- This is one layer. Behavioral dependency scanning sits alongside runtime detection, network egress controls, and least privilege, it doesn't replace them.
The takeaway
If your entire supply-chain defense is “we run a scanner and it was green,” you are defended against the 2019 threat model. The 2026 threat model is a legitimate package that turned hostile with no CVE and 2 billion weekly downloads, and a green check that told you nothing. The fix isn't a bigger vulnerability database. It's asking a different question, and having something that can reason about the answer.
You can see the difference on your own repo in one scan. Sign in, verify via GitHub, and run one from the dashboard, or drop the Pwnkemon Scan GitHub Action into a workflow so it runs on every PR. See pricing for plans.