NestJS Case Study
Tested with CVE Lite CLI v1.6.0
Summary
- Project: NestJS — production-grade Node.js framework used across thousands of enterprise applications
- Revision:
cee51af9118b68511e77e059f0578a3f0a3bcf0d - Lockfile:
package-lock.json(1623 resolved packages, v1.6.0 scan) - Baseline findings: 26 unique vulnerable packages (1 critical · 8 high · 13 medium · 4 low)
- Direct vs transitive: 1 direct / 25 transitive
- Time to first actionable fix command: under 30 seconds
- Validated fix commands generated: 2 (specific versioned targets, not generic
npm audit fix) - After measured pass (remediation study): reduced from 24 → 21 findings
What this case study demonstrates
NestJS represents the harder class of dependency remediation: a mature repository where almost all findings are transitive and there is no large batch of direct upgrades to clear first.
CVE Lite CLI's direct/transitive split makes this immediately visible. In this scan, 25 of 26 findings are transitive — meaning npm audit fix would have little effect and npm audit fix --force would be risky. CVE Lite surfaces the one actionable direct fix ([email protected]) and a single concrete parent upgrade ([email protected]) separately from the deeper structural issues, so a developer knows exactly where to start.
The tool also names the parent chain for every transitive finding. [email protected] (critical) is reached through request. [email protected] is reached through gulp-watch. That context is absent from npm audit's output and is exactly what a developer needs to decide 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.
| Metric | npm audit | CVE Lite CLI v1.6.0 |
|---|---|---|
| Total reported findings | 36 | 26 |
| Critical | 4 | 1 |
| High | 17 | 8 |
| Moderate / Medium | 13 | 13 |
| Low | 2 | 4 |
| Direct vs transitive breakdown | ✗ | ✓ (1 / 25) |
| 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, not 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 36 here and CVE Lite reports 26.
This deduplication is intentional. The 4 critical findings npm audit reports for NestJS include multiple entries for the same underlying package under different advisory IDs. CVE Lite surfaces 1 critical — [email protected] via request — because that is the one unique critical-severity package in the lockfile. A developer acting on npm audit's 4-critical output would discover partway through that several of them point to the same fix.
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:
npm install [email protected]
npm install [email protected]
On a project where 25 of 26 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 one direct issue first, then the one confident parent upgrade, then reason about the rest.
Before vs After
Remediation results from the measured workflow documented in this study (specific revision, v1.5.2 scan):
| Stage | Findings | Critical | High | Medium | Low | Direct | Transitive | Command groups |
|---|---|---|---|---|---|---|---|---|
| Baseline | 24 | 0 | 1 | 4 | 19 | 0 | 24 | 1 |
| After measured pass | 21 | 0 | 1 | 3 | 17 | 0 | 21 | 0 |
The finding count dropped from 24 to 21. The generated command surface dropped from 1 group to 0 — meaning the scanner moved the repository into the deeper transitive-only category where the remaining work belongs to toolchain and parent-chain decisions rather than confident first-pass installs.
Fix Journey
In a mature monorepo or framework like NestJS, the most common mistake is expecting a single upgrade pass to clear the advisory list.
At the study baseline, every finding was transitive. There were no direct packages to upgrade first. The scanner's job was to identify the one parent-chain move that was both safe and likely to reduce risk — [email protected] to clear the [email protected] transitive path — and to surface it separately from the longer tail of structural issues that were not first-pass candidates.
That single recommendation was real and useful, but executing it still required work. 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 24 to 21 findings.
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 the measured pass, the scanner generated no further copy-and-run command groups. That is meaningful: it means the repository moved out of the confident first-pass bucket and into the deeper category where remaining issues are tied to deprecated packages, toolchain dependencies, and replacement-level decisions. The scanner made that transition explicit rather than continuing to suggest commands that would not be actionable.
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 26 vulnerable packages, 25 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 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 36 triggers pipeline gates and developer anxiety without telling anyone what to do. An ordered output with 1 validated direct fix, 1 parent upgrade, and a clear explanation of the transitive remainder gives a team enough to act on before shipping.
Scan command
Run from the NestJS root:
npx cve-lite-cli . --verbose --all
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.
Remaining risk after the measured pass
The post-pass lockfile still contained 21 findings:
0critical1high3medium17low
The remaining high/medium work was dominated by transitive chains with no first-pass fix path:
[email protected]viagulp-diff[email protected][email protected]viarequest[email protected]
The lower-severity remainder included:
[email protected]- three vulnerable
brace-expansionpaths - two
bracespaths - two
micromatchpaths - two
js-yamlpaths [email protected][email protected][email protected]and[email protected][email protected][email protected]@tootallnate/[email protected]
This is a useful stopping point for the public study. The scanner surfaced the one meaningful parent-package move, the move worked once peer-resolution friction was handled, and the remaining work is clearly in the deeper transitive-and-toolchain bucket.
Baseline findings
Full vulnerable package list at scan time:
| Package | Version | Severity | Relationship | Fix hint | Advisory IDs |
|---|---|---|---|---|---|
| form-data | 2.3.3 | critical | transitive | 2.5.4 | GHSA-fjxv-7rqg-78g4 |
| fastify | 5.8.4 | high | direct | 5.8.5 | GHSA-247c-9743-5963 |
| diff | 2.2.3 | high | transitive | 3.5.0 | GHSA-73rr-hh4g-fpgx, GHSA-h6ch-v84p-w6p9 |
| braces | 1.8.5 | high | transitive | 3.0.3 | GHSA-grv7-fg5c-xmjg |
| braces | 2.3.2 | high | transitive | 3.0.3 | GHSA-grv7-fg5c-xmjg |
| lodash.template | 3.6.2 | high | transitive | 4.17.21 | GHSA-35jh-r3h4-6jhm |
| glob | 10.4.5 | high | transitive | 10.5.0 | GHSA-5j98-mcp5-4vw2 |
| serialize-javascript | 6.0.2 | high | transitive | 7.0.3 | GHSA-5c6j-r48x-rmvq, GHSA-qj8w-gfj5-8c6v |
| tar | 6.2.1 | high | transitive | 7.5.3 | GHSA-34x7-hfp2-rc4v, GHSA-83g3-92jg-28c… |
| postcss | 7.0.39 | medium | transitive | 8.4.31 | GHSA-7fh5-64p2-3v2j |
| brace-expansion | 5.0.4 | medium | transitive | 1.1.13 | GHSA-f886-m6hf-6m8v |
| brace-expansion | 1.1.11 | medium | transitive | 1.1.12 | GHSA-f886-m6hf-6m8v, GHSA-v6h2-p8h4-qcjw |
| brace-expansion | 2.0.2 | medium | transitive | 1.1.13 | GHSA-f886-m6hf-6m8v |
| follow-redirects | 1.15.11 | medium | transitive | 1.16.0 | GHSA-r4q5-vmmm-2653 |
| micromatch | 3.1.10 | medium | transitive | 4.0.8 | GHSA-952p-6rrq-rcjv |
| micromatch | 2.3.11 | medium | transitive | 4.0.8 | GHSA-952p-6rrq-rcjv |
| js-yaml | 3.14.1 | medium | transitive | 3.14.2 | GHSA-mh29-5h37-fv8m |
| js-yaml | 4.1.0 | medium | transitive | 3.14.2 | GHSA-mh29-5h37-fv8m |
| request | 2.88.2 | medium | transitive | 3.0.0 | GHSA-p8p7-x288-28g6 |
| qs | 6.5.3 | medium | transitive | 6.14.1 | GHSA-6rw7-vpxm-498p |
| tough-cookie | 2.5.0 | medium | transitive | 4.1.3 | GHSA-72xf-g2v4-qvf3 |
| yaml | 2.8.2 | medium | transitive | 1.10.3 | GHSA-48c2-rrv3-qjmp |
| @tootallnate/once | 1.1.2 | low | transitive | 3.0.1 | GHSA-vpq2-c234-7xj6 |
| diff | 4.0.2 | low | transitive | 3.5.1 | GHSA-73rr-hh4g-fpgx |
| diff | 7.0.0 | low | transitive | 3.5.1 | GHSA-73rr-hh4g-fpgx |
| qs | 6.14.1 | low | transitive | 6.14.2 | GHSA-w7fw-mjwx-w883 |
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.