JavaScript Dependency Security: Why npm audit Isn't Enough for Production Sites

Profile
Yves Soete Follow

Apr 26, 2026 · 7 min read

npm audit is a good first line of defense against vulnerable JavaScript dependencies. But it only covers what is in your package.json. Production websites often execute JavaScript that was never in your dependency tree — CDN-loaded jQuery versions, WordPress plugins, marketing tags, and third-party scripts added directly to templates by marketing or ops. None of that appears in npm audit.

The gap between your repo and your production site

Modern production websites load JavaScript from multiple sources:

  • Your application bundle (what npm audit covers)
  • CDN-loaded libraries (cdn.jsdelivr.net/npm/[email protected])
  • Third-party tags loaded via Google Tag Manager (Hotjar, Segment, Facebook Pixel, etc.)
  • CMS plugin scripts (WordPress plugins loading their own jQuery or Bootstrap versions)
  • A/B testing and personalization scripts (Optimizely, Mutiny, Launchdarkly client SDK)
  • Chat widgets (Intercom, Zendesk, Drift) loading external scripts

A marketing team member adds a Heap Analytics tag via GTM. That tag loads a version of the Heap SDK that bundles an old version of lodash with a known prototype pollution vulnerability. Your CI passes, your npm audit is clean, and you have a vulnerable library executing on every page view for six months.

How black-box JS scanning works

Black-box scanning — scanning your live site URL rather than your repository — loads the page in a real browser, intercepts all script requests, and analyzes what actually executes. The process:

  1. Browser loads the page normally (simulating a real user).
  2. All JavaScript network requests are intercepted: both first-party bundles and third-party scripts.
  3. Script content is fingerprinted. Version strings are extracted from minified code using regex patterns, source map references, and known signature databases.
  4. Detected library name + version is matched against CVE/NVD advisories.
  5. Findings are returned with CVE IDs, CVSS scores, affected version ranges, and the recommended safe upgrade target.

Which vulnerabilities actually matter

Not every CVE is exploitable in a browser context. Before escalating:

  • Cross-site scripting (XSS) via vulnerable jQuery versions — genuinely dangerous if any user input is passed through the vulnerable jQuery methods. Affects jQuery before 3.5.0 (CVE-2020-11022, CVE-2020-11023).
  • Prototype pollution — affects lodash before 4.17.21, jQuery before 3.4.0. Exploitable by attackers who can inject JSON data processed by the library.
  • Regular expression denial of service (ReDoS) — requires the library to process attacker-controlled input through a vulnerable regex. Often low practical risk in client-side contexts.
  • Path traversal in bundlers — only relevant during the build process, not in the browser. Treat as a build toolchain issue, not a runtime concern.

jQuery: the perennial offender

Despite being released in 2006, jQuery still runs on an estimated 76% of all websites. The vulnerability distribution is heavily concentrated:

  • jQuery < 1.6.3: Multiple XSS vulnerabilities in selector parsing.
  • jQuery < 3.0.0: jQuery.extend() prototype pollution (CVE-2019-11358).
  • jQuery < 3.4.0: XSS via HTML parsing in append() and similar methods (CVE-2020-11022, CVE-2020-11023).
  • jQuery < 3.5.0: Additional XSS via passing HTML from untrusted sources to $ methods.

The safe minimum is jQuery 3.7.x. Any version below 3.4.0 should be treated as critical in production.

The third-party script supply chain

Third-party scripts loaded by marketing and analytics tags represent a supply chain problem. You do not control when they update. A vendor can push a new version of their script that bundles a vulnerable dependency — and your site starts executing it without any action from your engineering team.

The mitigations:

  • Subresource Integrity (SRI): For scripts loaded from CDNs with a known version, add integrity="sha384-..." and crossorigin="anonymous" to the script tag. The browser refuses to execute the script if its hash does not match. Not applicable for dynamically loaded tags.
  • Content Security Policy: Use CSP to restrict which domains can load scripts. This does not prevent vulnerable versions but limits the blast radius if a supply chain compromise occurs.
  • Regular black-box scanning: Scan your live site weekly. The only way to catch third-party vulnerabilities that were never in your dependency tree.

Putting it together: a layered approach

Layer Tool What it catches
Build time npm audit / yarn audit Direct and transitive deps in package.json
CI pipeline Dependabot / Renovate Automated PR-level dep updates
Live site scan Kuality JS audit CDN libs, GTM tags, CMS plugins, all runtime JS
Network layer CSP + SRI Limits unauthorized script execution
Scan your live site for vulnerable JavaScript →
Version 1.0.65