How do you find out if a script on your website has a known CVE?
npm audit only sees what is declared in your repo. The browser also runs scripts injected by tag managers, CMS plugins and vendors. How to inventory what actually executes in production and match it against CVE databases, hash by hash.
A pentest report lands with “outdated jQuery 1.12.4, CVE-2020-11023” flagged on a page nobody remembers adding jQuery to. The finding is annoying. The question underneath it is worse: what else is running on the site that nobody knows about?
To find out whether a script on your website has a known CVE, you need an inventory of every script the browser actually executes in production, with each script identified precisely enough to match against a vulnerability database. The precise identifier is the file’s cryptographic hash. Dependency scanners like npm audit and Dependabot only cover scripts declared in your repository. For everything else, the reliable source is the browser itself, which can report the SHA-256 hash of every script it runs so those hashes can be matched against known library builds and their CVEs.
Why npm audit and Dependabot miss half your scripts
Dependency scanners read manifests. npm audit walks your lockfile, Dependabot watches your repo, Snyk scans what your build produces. All of that is worth doing, and none of it answers the question, because the set of scripts a browser executes on your production pages is larger than the set your repository declares.
The difference comes from everything injected at runtime: tags added through Google Tag Manager by the marketing team, scripts bundled into CMS and e-commerce plugins, the chat widget that loads its own copy of a utility library, vendor scripts that load further scripts from their own CDNs. None of this passes through package.json. Some of it changes without any deploy on your side.
We’ve watched a team run a clean npm audit on Monday and fail a PCI scan on Thursday because a plugin shipped with a four-year-old lodash. Both tools were right. They were just looking at different things.
The gap, stated plainly: what is declared versus what executes. Any vulnerability detection that only reads your repo audits the first set and hopes it equals the second. In our experience it never does.
Identify scripts by hash, not by URL
Once you accept that the browser is the ground truth, the next problem is identification. A URL is weak evidence. /assets/vendor.min.js tells you nothing about what is inside. A versioned CDN path like .../jquery-3.4.1.min.js is better, until a vendor republishes different bytes at the same path (it happens more than vendors admit).
The file’s hash is the identity that cannot lie. Two files with the same SHA-256 hash are the same file, and every published build of every mainstream library has a known, stable hash. If you have the hash of what executed, you can say “this is exactly jQuery 3.4.1 as published” rather than “the URL suggests jQuery-ish”.
CSP gives you a way to collect those hashes at scale. The report-sha256 (and 384/512) mechanism under script directives makes browsers include the integrity hash of each script that loads in their violation reports. No agent in the page, no crawler pretending to be a user. Real visitors, real pages, real hashes, including the scripts that only appear after login or on the payment step, which a crawler never reaches.
Matching hashes against a CVE database
Collecting hashes is the plumbing. The useful part is the lookup. CentralCSP’s Script Inventory captures every script the browser runs, with its origin, URL, SHA-256/384/512 hash and browser context, on every plan. On Scale (€349.99/month), the Technologies view takes it further: it fingerprints each script’s content to identify the library and its exact version, checks that version against CVE databases, and tags it with a lifecycle status (up to date, outdated, dormant, deprecated). You get one row per library version, with severity, the affected range and the script files carrying it, exportable as CSV or through the API. Inline scripts are not analyzed, only script files.
So the outdated-jQuery question stops being a research project. The inventory lists the file, Technologies names the exact release, and CVE-2020-11023 sits next to it with the advisory a click away. Setup is one header and about 5 minutes, and the full mechanics are in how to build a script inventory without an agent.
Finding out when it appears, not at the next pentest
An inventory you check quarterly is a pentest with extra steps. The scripts that hurt are the ones that appear between reviews: the tag someone adds on a Tuesday, the plugin update that swaps in a new bundle.
CentralCSP’s alerting (from Business, €129.99/month) covers the first half with a “new script origin” rule: the first time a script loads from an origin the site has never reported, you get a message on Slack, Microsoft Teams, Google Chat, Telegram or email, or a signed webhook you can point at Splunk, Datadog, PagerDuty or a ticketing queue. On Scale, two Technologies rules cover the second half: “new vulnerability” fires when a CVE lands on a library version you actually run (with a minimum severity you choose), and “outdated or deprecated version” fires when a plugin update swaps in something past its support window. Rules evaluate on ingest rather than on a schedule, and a per-rule cooldown batches repeats into one message instead of dropping them. The window between “vulnerable script goes live” and “someone knows” shrinks from months to minutes.
What this method will not tell you
Two honest limits.
Fingerprinting identifies known releases of public libraries. Your own first-party code matches nothing, we would not count on a webpack bundle that inlines lodash into 900 KB of application code being recognized as lodash, and inline scripts are not analyzed at all. Those scripts still appear in the inventory with their origin and hash, so you know they exist and you notice when they change, but no CVE database will have an opinion about them. Unmatched does not mean safe. It means yours to review.
And a CVE match is evidence, not a verdict. CVE-2020-11023 is exploitable through $.htmlPrefilter with untrusted HTML. If no code path on your page feeds user input into it, the practical risk may be low. The match tells you exactly which advisory to read and which upgrade to schedule. The judgment call is still yours.
Neither limit touches the case that started this article: a known public library, running in production, with a published CVE. That is exactly what fingerprinting catches, and it catches it on every page real visitors reach, tag-manager injections and plugin bundles included.
What the inventory changes is the shape of the problem. Before, the vulnerable-script question was unanswerable because the list of scripts was unknown. After, it is a finite list with hashes, matches and severities, and a feed that tells you when the list grows. That is a problem a team can actually work, and CentralCSP’s Script Inventory gets you there with one header. The list itself comes with every plan, and the 14-day trial on Start (three sites, 250,000 reports a month) is enough to build it for most sites end to end. The CVE matching on top of it is a Scale feature, so budget for that tier if the pentest finding is the reason you are here.
Frequently asked questions
Can npm audit find vulnerable scripts on my website?
Only partially. npm audit reads your lockfile, so it covers the dependencies your build pipeline ships. Scripts added through a tag manager, a CMS plugin or a vendor snippet never appear in package.json, so npm audit cannot see them. Those scripts run in your visitors' browsers all the same, and they are where outdated libraries tend to hide.
How do I know which version of jQuery is actually running on my site?
Identify the file by its hash, not its URL. A URL like /js/jquery.min.js says nothing about the version, and even versioned CDN paths can be republished. The SHA-256 hash of the file identifies the exact build. Browsers can report that hash for every script they execute via CSP's report-sha256 mechanism, and the hash can then be matched against known jQuery releases and their CVEs.
Does a CVE in a third-party script mean my site is exploitable?
No. A CVE means the library contains a known flaw, not that your pages exercise the vulnerable code path with attacker-controlled input. Treat a CVE match as a prioritized lead: check the advisory, check whether the affected function is reachable on your pages, then upgrade or remove. Auditors and attackers will both find the same match, so ignoring it is rarely an option either way.
Do I need to install an agent or run a crawler to scan my scripts?
No. CSP hash reporting turns every visitor's browser into the sensor. You add one response header, and browsers report the origin, URL and integrity hash of each script that executes, including scripts a crawler would never trigger because they only load after consent, login or checkout. Setup is around 5 minutes.