Skip to main content

NestJS Case Study

Verified baseline scan — CVE Lite CLI v1.25.0 · 2026-06-24 (remediation remeasurement on revision cee51af)

NestJS logo

Summary

  • Project: NestJS — production-grade Node.js framework used across thousands of enterprise applications
  • Revision: cee51af9118b68511e77e059f0578a3f0a3bcf0d
  • Lockfile: package-lock.json (npm monorepo root lockfile at pinned revision)
  • Baseline findings: 51 unique vulnerable packages (4 critical · 18 high · 25 medium · 4 low)
  • Direct vs transitive: 8 direct / 43 transitive
  • Time to first actionable fix command: under 30 seconds
  • Validated fix commands generated: 9 command groups at baseline (specific versioned targets, not generic npm audit fix)
  • After two measured remediation passes: reduced from 51 → 50 → 47 findings
  • Usage-aware triage (--only-used): 51 → 10 findings on the same revision (41 toolchain/transitive packages not statically imported)

What this case study demonstrates

NestJS represents the harder class of dependency remediation: a mature monorepo where most findings are transitive, OSV advisory coverage has expanded since earlier scans, and there is no single batch upgrade that clears the list.

CVE Lite CLI's direct/transitive split makes this immediately visible. In this remeasurement, 43 of 51 findings are transitive — meaning npm audit fix would have little effect and npm audit fix --force would be risky. CVE Lite still surfaces confident first-pass work ([email protected] as a direct fix, then [email protected] as a parent-chain move) separately from the deeper structural issues, so a developer knows exactly where to start.

The tool also names the parent chain for transitive findings. form-data (critical) is reached through deprecated request paths. Multiple ws versions appear across framework and dev tooling. That context is absent from npm audit's flat output and is exactly what a developer needs when deciding which parent upgrade is worth attempting first.


Comparison Note: CVE Lite CLI vs npm audit

Both tools were run against the same package-lock.json on the same machine on 2026-06-24.

Metricnpm auditCVE Lite CLI v1.25.0
Total reported findings5651
Critical64
High2218
Moderate / Medium2525
Low34
Direct vs transitive breakdown✓ (8 / 43)
Validated fix targets
Breaking change awareness
Parent chain identified for transitive issues
Specific copy-and-run commands

Why CVE Lite reports fewer findings — and why that is not a coverage gap:

npm audit counts advisories and dependency paths, not unique packages. A single vulnerable package with multiple advisories, or one that appears in several dependency paths, contributes multiple entries to the total. CVE Lite counts each unique vulnerable package once. That is why npm audit reports 56 here and CVE Lite reports 51.

This deduplication is intentional. npm audit's 6-critical output includes overlapping entries for the same underlying packages under different advisory IDs. CVE Lite surfaces 4 critical unique packages in the lockfile. A developer acting on npm audit's raw count would discover partway through that several entries point to the same fix or to paths with no confident automated command.

CVE Lite does not suppress advisories. Every advisory ID that contributed to a finding is recorded in the IDs column of the full table output (--verbose --all). The deduplication is in the presentation layer, not in the detection layer.

npm audit's fix guidance for NestJS:

To address issues that do not require attention, run:
npm audit fix

To address all issues possible (including breaking changes), run:
npm audit fix --force

CVE Lite generates targeted commands at baseline, including:

npm install [email protected]
npm install [email protected]

On a project where 43 of 51 findings are transitive, npm audit fix is nearly useless. npm audit fix --force would attempt to resolve all breakages simultaneously, with no guidance on which upgrades are safe and which introduce API incompatibilities. CVE Lite orders the output — fix the direct issue first, then the parent-chain upgrade, then reason about the remainder.

Usage-aware triage

NestJS is a full monorepo with source available — most lockfile findings live in test runners, build utilities, and legacy toolchain paths that --only-used can filter out.

Measured on revision cee51af with CVE Lite v1.25.0 · 2026-06-24:

Scan modeFindingsCriticalHighMediumLowDirectTransitive
Lockfile baseline (--verbose --all)51418254843
--only-used (actionable subset)10172073

51 → 10 — an 80% reduction in finding count. The --usage pass marked 41 of 51 findings as not statically imported (gulp toolchain, deprecated request/form-data chains, test-only paths). The --only-used subset surfaces runtime-facing packages like fastify, @grpc/grpc-js, @fastify/middie, and multiple ws paths still referenced from framework code.

Honest limits: --usage uses static import analysis only. It can miss packages loaded dynamically, through build scripts, or via string-based requires. Treat --only-used as a triage accelerator — not proof that filtered findings are unreachable at runtime. See CLI reference.


Before vs After

The first two rows show the lockfile baseline and its --only-used subset. The rows below document two measured remediation passes applied locally on the pinned revision (CVE Lite v1.25.0 · 2026-06-24):

StageFindingsCriticalHighMediumLowDirectTransitiveCommand groups
Lockfile baseline514182548439
--only-used filter (same revision)101720733
After first pass ([email protected])504172547439
After second pass ([email protected])474152537409

The finding count dropped from 51 to 47 across two passes. The first pass cleared the one direct fastify finding. The second pass cleared three transitive high/low findings tied to the test toolchain (glob, serialize-javascript, and one diff path). Critical findings did not move — they require deeper parent-chain or replacement decisions, not a single copy-and-run install.


Fix Journey

Upgrading once is rarely enough in a mature JavaScript monorepo.

Pass 1 — direct fix ([email protected])

At baseline, CVE Lite flagged fastify as a direct high-severity finding with a validated non-vulnerable target. That is the correct first move: it is in packages NestJS controls directly and does not depend on guessing a parent chain.

npm install --ignore-scripts --legacy-peer-deps [email protected]

Peer dependency friction is common in large workspaces. The install succeeded with --legacy-peer-deps. The scan dropped from 51 → 50 findings. The only change was clearing the direct fastify entry. Every critical finding and the bulk of transitive toolchain noise remained — as expected.

Pass 2 — parent-chain upgrade ([email protected])

With the direct fix applied, the next productive move was a parent upgrade on the test runner chain. CVE Lite surfaced [email protected] to address transitive diff paths pulled in through the existing Mocha dependency tree.

The first install attempt:

npm install --ignore-scripts [email protected]

failed because of peer dependency conflicts in the NestJS workspace. Retrying with legacy peer resolution:

npm install --ignore-scripts --legacy-peer-deps [email protected]

succeeded, and the scan dropped from 50 → 47 findings.

What cleared in pass 2:

  • glob (high, transitive)
  • serialize-javascript (high, transitive)
  • one diff (low, transitive) path

What remained after pass 2:

  • diff (high) and another diff (low) path still present through other parents
  • all four critical findings (@fastify/middie, form-data, protobufjs, shell-quote)
  • fifteen high-severity findings including direct @grpc/grpc-js, multer, and ws

This is a common pattern in large JavaScript projects: the right upgrade is identifiable, but executing it runs into install-policy friction before the graph changes. Knowing what to upgrade is only half the problem. The other half is knowing that the install friction is incidental — not a signal that the upgrade was wrong.

After two passes, the scanner still generated nine command groups. That is meaningful: the repository remains in the deeper transitive-and-toolchain bucket where remaining work belongs to parent-chain decisions and replacement-level calls (request, legacy gulp tooling), not a zero-CVE framing.


Why this matters

NestJS is not a neglected project. It has active maintainers, frequent releases, and a large ecosystem. Yet a lockfile scan still surfaced 51 vulnerable packages, 43 of them transitive.

That is the real-world state of dependency graphs in large JavaScript frameworks: most of the risk is not in packages the project controls directly. It lives in the toolchain, in test runners, in build utilities, and in packages that have not had a breaking-change-free upgrade path for years.

For a developer running a pre-release check, the operationally relevant question is not "how many advisories are there?" It is "what do I do right now, and what do I park?" CVE Lite answers that question in under 30 seconds: one direct upgrade, one parent-chain upgrade worth attempting, and an explicit separation of the structural remainder from the confident first-pass work.

That distinction matters especially in CI. A flat advisory count of 56 triggers pipeline gates and developer anxiety without telling anyone what to do. An ordered output with validated direct and parent-chain fixes, plus a clear explanation of the transitive remainder, gives a team enough to act on before shipping.


Scan command

Run from a local NestJS clone checked out at the pinned revision (full source required for --usage / --only-used):

# Full lockfile graph
npx cve-lite-cli . --verbose --all

# Triage: annotate import status per finding
npx cve-lite-cli . --verbose --all --usage

# Actionable subset: statically imported packages only
npx cve-lite-cli . --verbose --all --only-used

Every number in the Before vs After and usage-aware tables comes from live scans of revision cee51af9118b68511e77e059f0578a3f0a3bcf0d on 2026-06-24.

FieldValue
Remediation measurement date2026-06-24
Usage-aware measurement date2026-06-24
CLI versionv1.25.0
Revisioncee51af9118b68511e77e059f0578a3f0a3bcf0d
Lockfile findings (baseline)51
--only-used findings (baseline)10
After pass 1 ([email protected])50
After pass 2 ([email protected])47

Reproduce from a local clone at the pinned revision:

git clone https://github.com/nestjs/nest.git
cd nest
git checkout cee51af9118b68511e77e059f0578a3f0a3bcf0d

# build CVE Lite from the cve-lite-cli repo, then:
node /path/to/cve-lite-cli/dist/index.js . --verbose --all
node /path/to/cve-lite-cli/dist/index.js . --verbose --all --usage
node /path/to/cve-lite-cli/dist/index.js . --verbose --all --only-used

The remediation walkthrough was performed locally against that revision. Dependency changes were applied during the exercise, but they were not committed in the NestJS repository.

Pass 1 and pass 2 installs used --ignore-scripts --legacy-peer-deps where the default npm resolver blocked peer-conflicting upgrades in the monorepo workspace.

Remaining risk after two passes

The post-pass lockfile still contained 47 findings:

  • 4 critical
  • 15 high
  • 25 medium
  • 3 low

Critical and high work that did not clear in two passes:

  • @fastify/middie (critical, direct)
  • form-data (critical, transitive via deprecated request chains)
  • protobufjs and shell-quote (critical, transitive toolchain paths)
  • direct @grpc/grpc-js, multer, and ws still flagged at pass 2
  • diff (high) still present through non-Mocha parents after one diff low path cleared

The medium and low remainder is dominated by legacy gulp/braces/micromatch chains, multiple brace-expansion and js-yaml paths, deprecated request, and toolchain-only packages that --only-used filters out for runtime triage but that still exist in the lockfile graph.

This is a useful stopping point for the public study. The scanner surfaced two meaningful first-pass moves, both worked once peer-resolution friction was handled, and the remaining work is clearly in the deeper transitive-and-toolchain bucket — not a zero-findings endpoint.


Baseline findings

Representative critical and high findings at baseline (full 51-package table from --verbose --all):

PackageSeverityRelationshipNotes
@fastify/middiecriticaldirectFramework middleware surface
form-datacriticaltransitiveDeprecated request chain
protobufjscriticaltransitiveToolchain / codegen path
shell-quotecriticaltransitiveBuild tooling
fastifyhighdirectCleared in pass 1 (5.8.5)
@grpc/grpc-jshighdirectgRPC integration
multerhighdirectUpload middleware
wshighdirect / transitiveMultiple versions in graph
globhightransitiveCleared in pass 2 via Mocha chain
serialize-javascripthightransitiveCleared in pass 2 via Mocha chain
diffhigh / lowtransitivePartially cleared in pass 2

Run npx cve-lite-cli . --verbose --all against the pinned revision for the complete deduplicated package list with fix hints and advisory IDs.

Want your project reviewed?

If you maintain an interesting JavaScript or TypeScript project and want CVE Lite CLI considered for a public case study, open an issue in the CVE Lite CLI repository.

Please include:

  • the repository link
  • why the project would make a useful case study
  • whether the dependency graph is publicly reproducible

Not every project will be selected. Preference will go to projects that are publicly useful, technically interesting, and strong examples of realistic dependency remediation workflows.