9 of the 26 apps I audited in June and July 2026 ran a framework version with a publicly known, reachable remote code execution or auth bypass, and the fix was usually a one-line version bump. npm audit is the built-in command that asks the registry which of your installed versions have known vulnerabilities.
What npm audit does, and what the npm audit command reports
npm audit is the npm command that sends a description of your project’s dependencies to the registry and asks for a report of known vulnerabilities. It lists each advisory with its severity and the vulnerable versions, including packages vulnerable only because they depend on one, and npm audit fix applies the remediations it can without changing your dependency ranges.
That opening number needs its frame. The 26 are 21 third-party apps I audited in June and July 2026 plus five apps of my own. The 21 are 11 third-party public vibe-coded apps audited exhaustively across all 12 pillars, and 10 disjoint held-out third-party apps the engine had never seen, audited blind. The 9 are 8 of those 21 third-party apps and one of mine. I chose those apps; they are not a random sample, and the count is not a rate for AI-built apps in general. Keeping versions current is one of the controls in web app security, and this command is where that control starts for a Node app.
The report covers the whole tree, not only what your package.json names: for the bulk advisory check, npm posts the name and versions of each package in the tree to the registry, so npm package vulnerabilities several levels down show up too. That makes it the first check for npm vulnerabilities in a project, before you add any other npm vulnerability scanner. It scans the JavaScript packages for vulnerabilities, though, not your own code and not the Node runtime itself, so node vulnerabilities in the runtime depend on the Node release line you run, covered in the patch schedule below. The table below is every form of the npm audit tool that matters for a production app, with what npm’s audit documentation, pnpm audit and yarn npm audit say each one does.
| Command | What it does, per its docs | When to use it |
|---|---|---|
npm audit | Sends the dependency list and prints the report; the npm audit exit code is 0 when no vulnerabilities are found and non-zero by default when any are | Any time, locally or in CI |
npm audit --json | Prints the detailed report as JSON | When a script or a CI job reads the result |
npm audit --omit=dev | Leaves development dependencies out of the payload sent to the registry | The production-only view: read this one first |
npm audit --audit-level=high | Sets the minimum level that makes the command exit non-zero; it does not filter the report | CI, so the job fails only at that level or above |
npm audit fix | Updates vulnerable packages where the remediation needs no change to your dependency ranges; runs a full npm install | On a branch, after reading the report |
npm audit fix --force | Required when the chain reaches the root project and cannot be updated without changing its ranges; lets audit fix install SemVer-major updates to top-level dependencies | Not as a routine step; the recovery guide linked below covers that decision |
npm audit signatures | Verifies registry signatures and provenance attestations of downloaded packages | Supply-chain checks, not advisories |
pnpm audit | Checks installed packages; --prod audits production dependencies only; --fix adds overrides to pnpm-workspace.yaml | pnpm projects |
yarn npm audit | Audits installed packages, direct dependencies only unless --recursive is passed; --environment production excludes devDependencies; no fix option listed | Yarn projects |
In plain terms, npm audit fix runs an install that moves each vulnerable package to a version with no advisory posted against it, as long as that stays inside the ranges your package.json already allows. The one line below is how to run a dependency scan before every release: it audits the production tree and fails the job only on a high or critical advisory.
npm audit --omit=dev --audit-level=high
One condition applies: by default npm requires a package-lock in order to run the audit. A project on pnpm or Yarn runs pnpm audit --prod or yarn npm audit --recursive --environment production in that step instead, and reads its docs for the severity flag, because both describe theirs as limiting what is printed, where npm’s sets the failure threshold. If you search a pnpm report for an npm CVE number, note that pnpm’s docs say the registry’s bulk advisory response does not include CVE identifiers, so pnpm filters advisories by GitHub advisory ID (GHSA) instead.
The category has a name in the OWASP Top 10. The 2021 edition called it A06:2021 Vulnerable and Outdated Components, and the 2025 edition lists A03:2025 Software Supply Chain Failures, a category its page traces to the 2013 list’s A9, “Using Components with Known Vulnerabilities”, and says has grown in scope to include all supply chain failures, not just ones involving known vulnerabilities.
When npm audit shows critical vulnerabilities, the rating belongs to the advisory; it does not tell you whether an exploit can reach your app through the code you actually run. That per-advisory check, and what to do after a forced fix breaks the build, are covered in when a dependency update breaks the app, and I do not repeat them here.
What goes wrong without it
Third-party components, the third-party libraries your app imports and the dependency chain behind each, are code you ship but did not write. Every third-party software component in that chain gets patched on its maintainer’s schedule, not yours. Here are the four ways that shows up.
| Failure | What the owner sees | In my June and July 2026 audits | Where the fix lives |
|---|---|---|---|
| A framework version with a known, reachable hole | Nothing: the app works until someone uses the hole | The opener’s 9 of 26; also, three of the held-out apps sat on the same vulnerable Next.js 15.x line with a reachable middleware/RCE window | A framework version bump, then the tests |
| A report so long nobody reads it | Dozens of lines on every run, so the output gets ignored | In 3 of the 21 third-party apps, scanners flagged 33 to 44 vulnerabilities and the audit traced exactly zero as reachable | The reading order and the production-only view, below |
| A security advisory in an unused package | Advisories for a package no file imports | The real finding below | Removing the package |
| Dependencies years out of date | Every upgrade looks risky, so none starts | The Dependencies & Supply Chain pillar averages 34.5 out of 100 across the 20 third-party apps scored on it | The patch schedule, below |
Those three figures come from the same audits as the opener, on apps I picked, so read them as what that set showed rather than as odds for your app.
The npm dependency tree is where each row’s fix starts: npm ls <package> limits its output to the paths that lead to the package you name, which shows which parent pulls it in, and npm explain gives the same answer from the bottom up.
The third row comes from a real finding. In one small web app I audited, the app declared runtime dependencies it never imported, and one of those was the only reason three advisories sat in the tree. The lesson I take from it: the cheapest fix on an audit report is often a removal.
It is important to update third-party applications and libraries because an advisory tells anyone which versions are affected, so an old version is a known one.
How to do it: read the report, fix one package, keep the tree current
Working through an npm audit report takes three moves: read each advisory for where it comes from and whether a fix exists, fix one package at a time with the smallest change that removes it, and keep the tree current on a schedule so the next report is short. Tools and SBOMs help with the third.
The npm, pnpm, Yarn and scanner behavior below is taken from each project’s own documentation; none of it is a test I ran for this page.
Reading the npm audit report, and npm security beyond it
The npm audit report names, for each advisory, the package, the severity and the affected versions, and flags the packages that depend on it. Severity describes the advisory, not your app. npm security beyond the audit starts with a committed lockfile, controlled install scripts, and two-factor or trusted publishing for packages you publish.
Field by field, npm’s audit page says each advisory object the registry returns “contains a name, url, id, severity, vulnerable_versions, and title”. npm then works out the “meta-vulnerabilities”: a package that is vulnerable only because it depends on a vulnerable version of another, which is why one advisory deep in the tree can put several of your direct dependencies in the report. How the JSON report records the chain between them, and whether a given fix is SemVer-major, is not stated in npm’s docs, so treat those fields as the CLI’s current format rather than a documented contract.
Read in this order. First the production-only view, npm audit --omit=dev, because a build tool that never ships is a lower priority than a runtime package. Then the highs and criticals. Then, for each of those, whether a fix exists without --force: npm audit fix --dry-run --json shows what fix would do without changing anything. Whether each advisory can reach your code is the recovery guide’s method, linked in the first section, walked advisory by advisory.
The audit itself only sees npm security vulnerabilities that someone has already published an advisory for, and npm security issues also come from packages that have none yet. The four settings that cover that side, a committed lockfile with npm ci in CI, install scripts off by default, a minimum release age, and short-lived publish tokens or trusted publishing with 2FA, are the settings that matter for npm malware packages; I name them here without the detail.
Fixing one package: npm audit fix, a targeted install, or an override
Fixing one vulnerable package without breaking the build means the smallest change that removes the advisory: npm audit fix where no range changes, a targeted install for a direct dependency, and an override for a transitive one. Run the tests after each package and commit each one on its own.
- 01 Open a branch for the dependency work
- 02 Run npm audit fix for the remediations that need no change to your dependency ranges
- 03 For a direct dependency, install the fixed version of that one package: npm install <package>@<fixed version>
- 04 For a transitive dependency, add an overrides entry to the root package.json that pins the fixed version
- 05 Run the tests after each package and keep the before and after reports
- 06 Commit each package on its own, so a break points at one change
npm’s docs describe no way to aim npm audit fix at a specific package, which is why the third and fourth steps exist. The fifth step is only as good as the tests you have; how to write end-to-end smoke tests covers the short suite worth running after every dependency change. Each step keeps the change small, which is what lets you upgrade vulnerable dependencies safely: when a test fails, it points at one package.
The override in the fourth step is one entry in the root package.json. Replace the placeholders with the package the advisory names and the version that fixes it:
{
"overrides": {
"<package-name>": "<fixed-version>"
}
}
npm’s overrides field exists for exactly this: its docs give “replacing the version of a dependency with a known security issue” as a use. Two rules from the same page shape the steps above: “Overrides are only considered in the root package.json file for a project”, and you may not override a package you depend on directly unless both share the exact same spec, which is why the third step handles direct dependencies with an install instead. On pnpm, pnpm’s overrides are a pnpm-workspace.yaml setting that can only be set at the root of the project; on Yarn, the equivalent is the resolutions field, which Yarn’s manifest docs describe as a way to “Override the resolutions of specific dependencies”. In npm, the field that does what those resolutions do is overrides. A third-party package, npm-force-resolutions, patches the lockfile instead: its README says it modifies package-lock.json to force a specific version of a transitive dependency, similar to Yarn’s selective dependency resolutions, and that you should first try updating your top-level dependencies.
Here is the case the ladder is built for. npm audit reports a high-severity advisory in a package the app does not install directly; it arrives two levels down, through one of the app’s own dependencies. npm audit fix will not apply the fix, because the fix needs that parent’s dependency range changed, and it asks for --force, which would install a new major version of a top-level dependency. An overrides entry that pins only the fixed version of that one package, followed by the tests, removes the advisory without the major upgrade. What I take from the case: an override fixes the one package the advisory names, and the major upgrade becomes a planned change, not a side effect.
A major version deserves its own branch, its changelog read and its own test run, never a side effect of --force across the whole tree. If a forced fix has already broken the build, recover first with the recovery guide linked in the first section.
Software composition analysis: what SCA adds to npm audit
Software composition analysis tools do what npm audit does across the other ecosystems and container images in a repository, with their own advisory sources, and some add licenses, reports and pull requests. My working rule for one web app: a free scanner that runs in CI next to npm audit is usually enough.
SCA is short for software composition analysis: an SCA tool scans your dependency manifests and lockfiles against a vulnerability database. There is no single best SCA tool for every stack, so the table gives each one’s own description, with no ranking. The rows cover OSV-Scanner, Google’s scanner from the google/osv-scanner repository, Trivy, Grype, Dependabot security updates and Snyk Open Source.
| Tool | How it describes itself | What it scans | Cost or license |
|---|---|---|---|
| OSV-Scanner | ”Use OSV-Scanner to find existing vulnerabilities affecting your project’s dependencies”; an officially supported frontend to the OSV database | A project’s list of dependencies; container images; licenses | Apache-2.0 license (its repository) |
| Trivy | ”The All-in-One Security Scanner” | Container images, filesystems, code repositories, virtual machine images, Kubernetes and SBOMs; OS packages and language-specific packages | Apache-2.0 License; no price on its site |
| Grype | ”A vulnerability scanner for container images and filesystems” | Container images, filesystems and SBOMs; adds EPSS, KEV and risk scoring for prioritization | Apache-2.0 License |
| Dependabot security updates | ”Dependabot can fix vulnerable dependencies for you by raising pull requests with security updates” | Dependencies specified in a manifest or lock file, in repositories with the dependency graph and Dependabot alerts enabled | Available for all repositories on GitHub |
| Snyk Open Source | ”a developer-first software composition analysis (SCA) solution” | Open-source libraries, for vulnerabilities and license issues, with fix pull requests | A free plan that includes SCA, within test limits; license not stated in Snyk’s docs |
The Trivy scanner’s definition of itself, on its site, is a tool used to “find vulnerabilities (CVE) & misconfigurations (IaC) across code repositories, binary artifacts, container images, and Kubernetes clusters”, and its docs list vulnerability, misconfiguration, secret and license scanners. A Trivy image scan reaches past the npm tree: Trivy’s docs say it detects installed OS packages when scanning container images, so a Docker CVE in the base image shows up next to the npm ones. That operating-system layer is the kind of Docker vulnerability npm audit never sees, because npm only sends the packages in your project’s tree. Docker’s own security issues are a separate subject from scanning an image.
My working rule in the capsule above has one condition: where the app ships as a container image, the free scanner should be one that also reads container images. OSV-Scanner, Trivy and Grype are each an open-source alternative to Snyk under the Apache-2.0 license; I do not rank Trivy, Snyk or the other competitors in this table, because for one app the differences that matter are where each runs and what it costs. Snyk’s Broker is a narrower thing: Snyk’s docs describe it as an open-source proxy between Snyk and source integrations such as GitHub and GitHub Enterprise, and say Snyk Broker is available only for Enterprise plans.
Trivy and SonarQube do different jobs: SonarQube is mainly static analysis of your own code, a different class of tool covered in static code analysis tools; its dependency scanning (SCA) comes only with Advanced Security, which Sonar’s docs call a paid product starting in SonarQube Server’s Enterprise edition. How SAST and SCA compare is a separate page. The Trivy supply-chain incident is on the npm malware page linked above and is not retold here.
SBOMs with Syft and Grype
A software bill of materials (SBOM) lists every package and version in a build in a standard format such as CycloneDX or SPDX. Syft generates one from a directory or a container image, and Grype scans an SBOM or an image for known vulnerabilities.
The two formats describe themselves plainly. The CycloneDX format is, in its project’s words, “a full-stack Bill of Materials (BOM) standard that provides advanced supply chain capabilities for cyber risk reduction”, and an SBOM in CycloneDX is one of the outputs Syft writes. SPDX calls its specification “a freely available international open standard (ISO/IEC 5962:2021)”.
Syft’s GitHub README describes it as “A CLI tool and Go library for generating a Software Bill of Materials (SBOM) from container images and filesystems”, with output formats that include CycloneDX, SPDX and Syft JSON. Grype reads what Syft produces: its README lists scanning “container images, filesystems, and SBOMs for known vulnerabilities”. Syft and Grype are both Anchore projects, released under the Apache-2.0 License. To install Syft, use the Installation docs its README points to (Homebrew and Docker are among the options). For SBOM generation inside CI, Anchore’s sbom-action is “A GitHub Action for creating a software bill of materials (SBOM) using Syft.”
What a buyer or an auditor will accept as an SBOM, with an example and the license report beside it, is covered in license scanning; the finished SBOM belongs in the pack of technical documentation for investors.
Patch management for one app’s dependencies
Patch management for one app’s dependencies is, by my working rule, a schedule, not a product: the audit on every pull request, small updates merged often, major versions planned one at a time, framework security releases applied promptly, and the Node runtime kept on an LTS release.
Application patches are the fixed versions a project publishes, and for a web app the patches that matter most are its dependencies’. The meaning of security patching here is narrow: moving to the version a project published to fix an advisory. The five lines below are a sample patch management policy, short enough to paste into the repository’s README as a template, each line my working rule.
- 01 Run the audit on every pull request, with the CI line from the first section
- 02 Merge small patch and minor updates in small batches, reviewed, with the tests run
- 03 Plan each major version on its own, with its changelog read first
- 04 Apply the framework security releases promptly
- 05 Keep Node on an Active LTS or Maintenance LTS release line
The fourth line is there because of the opener’s number. The fifth line follows the Node.js release schedule, which says “Production applications should only use Active LTS or Maintenance LTS releases.” The second line is where automated patch management tools earn their place: an update bot opens the pull requests (a separate question: what Renovate bot is), and the test run decides what merges. Automatic patching software that merges without tests is the setup to avoid.
Written into the README, the five lines become the repository’s patch management procedure, and a patching policy this short is one a small team can keep. Application patching for one repository needs no automated patching software beyond the repository host and an update bot; for the checking side of patch management, open source scanners from the table above are enough. The update lane that keeps a dependency change from breaking production, with a committed lockfile and deterministic installs in CI, is the last section of the recovery guide linked in the first section. This schedule, the four npm settings named above and the update bot together make the dependency hygiene checklist I would give one app, and the same list works as a dependency maintenance checklist when you inherit one. The other types of patch management, for operating systems, endpoints and fleets of laptops, are a different discipline with their own tools.
The CVEs you will actually meet: xz, Spring4Shell, ffmpeg, Django, Go
The utility CVE-2024-3094 refers to is XZ Utils: NVD describes malicious code “in the upstream tarballs of xz, starting with version 5.6.0” that modifies the liblzma library, and the project’s own page calls it the “XZ Utils backdoor”. In the table, the second column comes from each record; the other two columns are my reading.
| CVE or advisory source | What the record says | What a Node web app runs | The question to ask |
|---|---|---|---|
| CVE-2024-3094 on NVD (XZ Utils) | Malicious code in the upstream xz tarballs from 5.6.0 modifies liblzma | Not an npm package; only the operating system of the server or image | Is this library in the image or host the app runs on? |
| CVE-2022-22965 on NVD (Spring4Shell) | A Spring MVC or Spring WebFlux application on JDK 9+ may be vulnerable to RCE via data binding; the specific exploit requires Tomcat as a WAR deployment | Nothing in the npm tree; a Java service only if the stack has one | Does any service run Spring on Tomcat as a WAR? |
| FFmpeg’s security page | Lists, for each release, the vulnerabilities it fixes by CVE identifier, with the fixing commit | The ffmpeg binary, only if the app runs it on input such as user uploads | Does input the app accepts reach FFmpeg? |
| Django’s security releases | For each issue: the date, a brief description, the CVE identifier if applicable, the affected versions and the patches | A Python service, if the stack has one | Is Django in any service the app runs? |
| the Go vulnerability database | Reports come from Go package maintainers or sources such as MITRE and GitHub, curated by the Go Security team | A Go (Golang) binary or service, if the stack has one; a Golang CVE matters only there | Is that Go module in a binary the app runs? |
| feedparser, in the GitHub Advisory Database | Cross-site scripting and denial of service vulnerability advisories for feedparser on pip | A Python feed parser most Node apps never run | Does any Python service parse feeds with it? |
The Spring4Shell record also says that a Spring Boot executable jar, the default, is not vulnerable to that specific exploit, while noting there may be other ways to exploit the vulnerability. Each row asks the same thing: is this package or binary in what the app runs, and does input the app accepts reach it. The per-advisory answer is the recovery guide’s method.
How to verify it
Dependency remediation is verified with two dated audit reports, a written decision for every high and critical advisory, unused packages gone from the lockfile, the test suite passing after the changes, and a CI step that fails when a new high advisory appears.
- 01 Two audit reports, before and after, saved with --json and dated: keep both files
- 02 A written decision for every high and critical advisory: fixed (with the version it moved to), not reachable (with the reason), or removed: keep the decision list
- 03 Unused packages gone: the lockfile diff shows them removed
- 04 The tests pass after the changes: keep the run output, and count a failure a test caught during the work as evidence too
- 05 The CI step fails on a new high advisory: on a throwaway branch, install a package version with a known high advisory as a runtime dependency, watch the job fail, keep the log, then delete the branch
The last check installs the package as a runtime dependency because the CI line leaves development dependencies out. The “not reachable” reasons in the second check follow the recovery guide’s method. Together these five are also the dependency layer of a website security check.
In the sprint, deliverable 3.5 is verified this way: record the dependency scan, remediation, and regression checks after changes.
Where the sprint does this
Deliverable 3.5 of the Production Hardening Sprint, dependency remediation, is to audit dependencies, upgrade or remove known-vulnerable packages, and remove unused packages. The result and its evidence go into the production readiness report, deliverable 13.1, which accounts for all 123 IDs, keeps failures visible until resolved and explains genuine non-applicable items. We work inside your existing codebase, and your app’s current framework and hosting setup are our starting point. Every deliverable is listed in the published scope.
Common questions about dependency vulnerabilities
Can I use npm audit fix to fix only one package?
Not with a package name: npm’s docs describe npm audit fix with no package argument, so it applies every remediation it can. To fix one package, install its fixed version directly with npm install <package>@<version> when it is a direct dependency, or pin the fixed version with an overrides entry in the root package.json when it arrives through another package.
Is npm a security risk?
npm itself is the registry and the command-line client; the risk sits in what gets installed. Old versions with published advisories are the part npm audit reports. A package published with malicious code is the other part, and an audit can only flag it once an advisory for that release exists, so a fresh one gets through; the npm malware page covers that risk and the install settings that limit it.
Why isn’t the npm audit fix working?
npm’s docs give two reasons: some vulnerabilities “cannot be fixed automatically and will require manual intervention or review”, and when the chain reaches the root project and cannot be updated without changing its dependency ranges, npm audit fix requires --force. The answer to both is a targeted install of the fixed version or an overrides entry for that one package, not --force on the whole tree.
Is Trivy a free vulnerability scanner?
Yes. Trivy is open source under the Apache-2.0 License, and its site lists no price for the scanner.
What are the key differences between OSV-scanner and Grype?
Both scan a project’s dependencies and container images; the difference is the data source and the extras. OSV-Scanner, as the OSV database’s officially supported frontend, connects a project’s list of dependencies with the vulnerabilities that affect them, while Grype, an Anchore project, scans container images, filesystems and SBOMs and adds EPSS, KEV and risk scoring to help prioritize results.
If this checklist left you with more open items than you expected, the sprint below works through all of them in ten working days.
Built it with AI. Now it has to hold up for real customers.
The Production Hardening Sprint takes the app you already have and builds the production foundation underneath it. Authentication and access rules, payments that stay consistent, error handling, monitoring, backups, automated tests and a documented handover. Our engineers work inside your existing codebase for ten working days. All 123 deliverables are included, and you get the evidence for each one.
See the Production Hardening Sprint →
$2,500 fixed price · 10 working days · One codebase