Malicious Packages: The Supply Chain Threat Your CVE Scanner Doesn't See
Your dependency scanner is good at one question: does this package version have a known vulnerability? That covers honest mistakes in code that someone later found and fixed. It does not cover a package that was built to hurt you, or a trusted package that was hijacked last night and republished with a payload.
Those are malicious packages, and they behave very differently from vulnerabilities. They often have no CVE, they never get a "fixed version," and they usually do their damage the moment you install them. This guide explains what makes them different, what the 2025 npm incidents taught us, and the controls that actually reduce the risk.
Vulnerable vs. malicious: two different problems
GitHub's Advisory Database draws the line clearly. A vulnerability is a problem in a project's code that can be exploited. Malware is code intentionally designed for unwanted or harmful functions.
That difference changes everything downstream:
- Identifiers. Vulnerabilities are tracked with CVE IDs. Malicious packages are commonly reported through other channels: the OpenSSF Malicious Packages repository publishes them in OSV format with IDs that begin with
MAL-, and GitHub publishes malware advisories with GHSA identifiers. - Fix. A vulnerability advisory tells you the earliest version where the problem was fixed. GitHub's malware advisories contain no fix version: the remediation is to remove the package and find a safe replacement.
- Timing. A vulnerability is usually exploited against a running application. A malicious package can execute during installation, on a developer laptop or a CI runner, before your application ever starts.
If your software composition analysis (SCA) only matches dependencies against CVE data, it is answering the wrong question for this threat. For a refresher on what SCA does well, see SAST, SCA, secrets and IaC explained.
How malicious packages get into your build
There are four common routes:
1. Typosquatting
An attacker publishes a package whose name is one keystroke away from a popular one. A typo in package.json, requirements.txt or a copy-pasted install command is enough.
2. Dependency confusion
Your company uses an internal package name. An attacker publishes a public package with the same name and a higher version number. A misconfigured package manager that checks the public registry may prefer the attacker's version.
3. Compromised maintainers
This is the most dangerous route because the package name is the real one. In September 2025, a maintainer of widely used npm packages, including chalk and debug, fell for a phishing email disguised as a "2FA reset" from a lookalike npm domain. Eighteen packages with more than 2 billion combined weekly downloads were republished with code that intercepted crypto-wallet activity in website visitors' browsers.
4. Self-spreading malware
Days later, the Shai-Hulud worm hit npm. According to CISA, it compromised more than 500 packages. It searched infected environments for secrets such as GitHub tokens and cloud API keys, exfiltrated them, and used stolen npm access to publish tainted versions of other packages, spreading on its own.
How big is the problem?
Sonatype's 2024 State of the Software Supply Chain report counted more than 704,102 malicious open source packages identified since 2019, with a 156% year-over-year increase. Its Open Source Malware Index then identified 17,954 new malicious packages in the first quarter of 2025 alone, and reported that 56% of them focused on data exfiltration.
The pattern is consistent: malicious packages are not rare edge cases, and the most common goal is stealing data and credentials, not breaking your app.
Controls that actually help
No single control stops every malicious package. Layer these:
Install exactly what you reviewed
Commit lockfiles and install from them in CI. npm's documentation is explicit: npm ci requires an existing package-lock.json and exits with an error if it does not match package.json, instead of silently updating it. That keeps a fresh malicious version from sliding in through a version range.
Don't run install scripts by default
Many malicious packages run their payload from lifecycle scripts during install. Since v10, pnpm does not run dependencies' lifecycle scripts unless you explicitly allow them. With npm, the ignore-scripts setting stops scripts from package.json files from running. Allow-list the few packages that genuinely need a build step.
Add a cooling-off period
A delay gives researchers and registries time to detect and pull a hijacked release before it reaches you. pnpm's minimumReleaseAge setting (added in v10.16.0) sets the minimum number of minutes that must pass after a version is published before it can be installed, and it applies to transitive dependencies too. A one-day delay (1440 minutes) avoids being among the first to install a poisoned release.
Close the dependency-confusion gap
Use scoped names for internal packages and configure your package manager or artifact repository so internal scopes resolve only from your private registry.
Protect the people who publish
The chalk and debug compromise started with phishing. CISA's guidance after Shai-Hulud includes mandating phishing-resistant MFA on developer accounts, especially for GitHub and npm. GitHub also announced it would move npm toward trusted publishing, FIDO-based 2FA and granular publishing tokens with a maximum lifetime of seven days.
Know where every package lives
When a malicious version is announced, you need to answer "are we using it, and where?" in minutes. An up-to-date SBOM makes that a query instead of a project. See our guide to SBOM formats.
If you installed a malicious package
Treat it as a credential incident, not a dependency update:
- Identify affected projects from lockfiles, including nested dependencies, and check cached copies in artifact repositories, as CISA recommends.
- Remove the package and pin to a version published before the compromise.
- Rotate every credential the affected machines and pipelines could reach: developer tokens, CI secrets, cloud keys.
- Review GitHub apps, OAuth applications, webhooks and repository secrets for changes you did not make.
- Monitor for unusual outbound connections from build systems.
Stolen credentials are often used later, not immediately, which is why leaked credential monitoring belongs in the same playbook.
FAQ
Will my SCA tool catch malicious packages?
Only if it consumes malware intelligence, such as OpenSSF MAL- reports or GitHub malware advisories, in addition to CVE data. Ask your vendor directly.
Is a package with no known CVE safe? No. It means no one has reported a vulnerability in it. A freshly hijacked version can be malicious and still have no CVE.
Do these controls slow developers down? Lockfiles and scoped registries are mostly invisible once configured. A release-age delay means waiting a day for brand-new versions, which is a small price compared with rotating every secret in your CI.
How priorIQ.ai helps
CodeIQ combines SCA with reachability, malicious-package detection and SBOM generation in CycloneDX and SPDX, alongside SAST, container and IaC scanning, and secrets scanning across full git history with live-secret validation. Its pipeline gate can stop a risky dependency before merge. Findings flow into PriorIQ Core, which puts them in the same risk model as your endpoints and cloud, so your team works one prioritized queue. Request a demo.