Skip to main content

OWASP Juice Shop Case Study

Tested with CVE Lite CLI v1.6.0

OWASP Juice Shop logo

Summary

  • Project: OWASP Juice Shop — intentionally vulnerable Node.js e-commerce application
  • Lockfile: package-lock.json (1601 resolved packages)
  • Revision: 7ae7184dbf84baae9ee1d85be39f793b777ae996
  • Baseline findings: 19 unique vulnerable packages (3 critical · 10 high · 4 medium · 2 low)
  • Direct vs transitive: 4 direct / 15 transitive
  • Time to first actionable fix command: under 30 seconds
  • Validated fix commands generated: 2 (specific versioned targets, not generic npm audit fix)
  • After two remediation passes: reduced from 39 → 18 findings across an earlier study revision

What this case study demonstrates

CVE Lite CLI separates what you control from what you do not. The direct/transitive split (4 direct, 15 transitive in this scan) immediately tells a developer where to start and which problems require parent-chain decisions rather than a simple npm install.

Unlike tools that output a flat advisory list, CVE Lite validates each fix target against published version ranges before recommending it. The commands it generates are confirmed non-vulnerable, not just "the next version." For jsonwebtoken, it flags the 8.5.1 → 9.0.0 upgrade as a breaking change so the developer knows before running the command.

On transitive findings, it names the parent chain — for example, crypto-js is reached through the crypto-js direct dependency, and [email protected] is flagged with a parent path — so there is no guesswork about where to start.


Comparison Note: CVE Lite CLI vs npm audit

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

Metricnpm auditCVE Lite CLI v1.6.0
Total reported findings5519
Critical73
High3110
Moderate / Medium114
Low62
Direct vs transitive breakdown✓ (4 / 15)
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 three advisories across two dependency paths appears as six entries. CVE Lite counts each unique vulnerable package once, regardless of how many advisories affect it or how many times it appears in the dependency graph. That is why the totals differ: 55 vs 19.

This deduplication is intentional. A developer who sees 55 findings cannot tell at a glance that many of them refer to the same handful of packages. CVE Lite's 19 is a more accurate representation of the actual exposure surface — 19 distinct packages that need attention, not 55 individually actionable tasks.

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

npm audit's fix suggestions are:

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]

Each command is a validated non-vulnerable target. npm audit fix --force is a blunt instrument that can silently introduce breaking changes across multiple packages. CVE Lite flags the one breaking upgrade explicitly and keeps the others clean.

npm audit does not distinguish direct from transitive findings. On a project with 15 transitive issues, that means a developer sees 55 entries without knowing which ones they can act on immediately and which require parent-chain decisions.


Before vs After

Remediation results from the measured workflow documented in this study (earlier revision, v1.5.2):

StageFindingsCriticalHighMediumLowDirectTransitiveCommand groups
Baseline3931112310296
After first direct pass271010164233
After second pass18105123151

The finding count dropped from 39 to 18. Critical findings dropped from 3 to 1. The single high-severity finding was cleared. The command surface dropped from 6 groups to 1 — meaning the scanner moved the project through the first two actionable passes and made the remaining blockers explicit rather than mixing them with fixable noise.


Fix Journey

Upgrading once is rarely enough in a mature JavaScript application.

At baseline, the scanner identified 10 directly fixable packages. Applying those upgrades moved the project from 39 findings to 27 — a real improvement, but not done. The reason: upgrading a direct dependency changes what the root project controls, but it does not always clear the transitive copies that other packages are pulling in.

jsonwebtoken is a clear example. After upgrading the root project to 9.0.0, the scanner still showed [email protected] present through the express-jwt dependency chain. From npm audit's perspective, both versions appear as high-severity findings in the same flat list. CVE Lite separated them: one was a direct upgrade path, one was a parent-chain problem.

The second pass targeted parent packages — mocha, socket.io-client, sqlite3 — which cleared the transitive copies their upgrade chains were pulling in. That dropped the count from 27 to 18 and narrowed the remaining set to structural blockers: packages with no published safe version (marsdb, notevil) and deep transitive paths not worth chasing in a single pass.

The practical lesson: a developer following the scanner's ordered output would have two productive passes in one sitting rather than one undifferentiated dump to reason about from scratch.


Why this matters

Dependency remediation slows down close to release — not because the fixes are technically hard, but because the signal-to-noise ratio in advisory output is poor.

A raw dump of 55 findings (as npm audit produces here) does not tell a developer which 4 are in packages they control directly and can fix in a single command, vs which 15 require parent-chain investigation, vs which ones have no published fix at all. That distinction matters enormously at sprint end, when a team is deciding whether to ship or hold.

CVE Lite's output answers the operational question: what do I do right now, and what do I defer? Two validated fix commands, a breaking change flag on the one that matters, and a structured plan for the transitive work. That is the gap between knowing a problem exists and knowing what to do about it.


Project context

Baseline scan command from the Juice Shop root:

npx cve-lite-cli . --verbose --all

One practical detail mattered during this run: Juice Shop has package-lock=false in .npmrc. That means a normal npm install can update package.json and local installs without updating the lockfile snapshot the scanner reads. To keep the case study honest, the lockfile was refreshed after each install batch with:

npm install --package-lock-only --package-lock true --ignore-scripts

That is not a CVE Lite CLI quirk. It is a real-world workflow detail that developers can easily miss when validating remediation against lockfile state.

Remaining risk after two passes

The second pass left 18 findings in the lockfile:

  • 1 critical
  • 5 medium
  • 12 low

Direct unresolved cases:

Transitive or structural follow-ups:

  • [email protected] via i18n
  • multiple minimatch findings via grunt, replace, and another root path
  • [email protected] via node-pre-gyp
  • lower-severity transitive packages such as serialize-javascript, vm2, lodash, lodash.set, micromatch, got, braces, and crypto-js

This is the part of remediation where a maintainer stops asking "what can I bump today?" and starts asking "which dependencies are worth replacing, and which upgrades deserve broader regression testing?"


Baseline findings

Full vulnerable package list at scan time:

PackageVersionSeverityRelationshipFix hintAdvisory IDs
crypto-js3.3.0criticaltransitive4.2.0GHSA-xwcq-pm8m-c4vf
marsdb0.6.11criticaldirectGHSA-5mrr-rgp6-x4gr
vm23.9.17criticaltransitive3.9.18GHSA-99p7-6v5w-7xg8, GHSA-cchq-frgv-rjh…
braces2.3.2hightransitive3.0.3GHSA-grv7-fg5c-xmjg
jsonwebtoken8.5.1highdirect9.0.0GHSA-8cf7-32gw-wr33, GHSA-hjrf-2m68-595…
lodash4.17.23hightransitive4.18.0GHSA-f23m-r3pf-42rh, GHSA-r5fr-rjxr-66jc
minimatch3.0.8hightransitive3.1.3GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m2…
http-cache-semantics3.8.1hightransitive4.1.1GHSA-rc47-6667-2j5j
lodash.set4.3.2hightransitive4.17.19GHSA-p6mc-m468-83gw
minimatch9.0.3hightransitive3.1.3GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m2…
tar4.4.19hightransitive6.2.1GHSA-34x7-hfp2-rc4v, GHSA-83g3-92jg-28c…
minimatch3.0.5hightransitive3.1.3GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m2…
serialize-javascript6.0.2hightransitive7.0.3GHSA-5c6j-r48x-rmvq, GHSA-qj8w-gfj5-8c6v
got8.3.2mediumtransitive11.8.5GHSA-pfrx-2q88-qq97
micromatch3.1.10mediumtransitive4.0.8GHSA-952p-6rrq-rcjv
notevil1.3.3mediumdirectGHSA-8g4m-cjm2-96wq
sanitize-html2.17.2mediumdirect2.17.3GHSA-9mrh-v2v3-xpfm
@tootallnate/once2.0.0lowtransitive3.0.1GHSA-vpq2-c234-7xj6
messageformat2.3.0lowtransitive3.0.0-beta.0GHSA-xfqm-j7pc-xrfc

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.