Use three views and merge them: what the browser actually loads (DevTools network panel), what the policy reports (a report-only CSP), and what your repository references. Then record an owner and a purpose for each script. OWASP's advice is simply to audit the scripts actually loaded.
Why one view is never enough
Your templates say what you intended to load. The browser shows what really loaded, which includes scripts that other scripts inject. A tag manager can bring in a dozen more, and some appear only for certain countries, logged in users or A/B test groups.
Method 1: browser network panel
- Open the page in a clean profile with extensions off.
- Open DevTools, then the Network tab, and filter by JS.
- Reload with cache disabled and sort by domain.
- Note every domain that is not yours.
- Repeat for key flows: login, checkout, account settings and any page with a form.
Check the Initiator column. It tells you which script requested another one, so you can see chains.
Method 2: CSP report-only as a sensor
A report-only policy blocks nothing but reports violations. MDN describes the Content-Security-Policy-Report-Only header and says reports go to an endpoint named with report-to. Set a tight policy such as default-src 'self' in report-only mode, and every external script, frame, image and connection arrives as a report. This catches paths you did not browse. The CSP report-only rollout steps post walks through the setup.
Method 3: search your code and build
Search templates, tag manager exports and package manifests for src= URLs, import statements pointing at URLs, and package names that run in the browser. Bundled npm packages will not show as separate network requests, so your lockfile is the inventory for them.
What to record
| Field | Example question |
|---|---|
| URL or package and version | Is it an exact version? |
| Owner | Who answers if it breaks? |
| Purpose | What business reason justifies it? |
| Data reach | Is it on pages with forms or tokens? |
| Integrity | Pinned with a hash, or dynamic? |
| Last reviewed | When did a person last look? |
Anything with no owner is a candidate for removal.
Keep it current
An inventory goes stale in weeks. Re-run the CSP reports as an ongoing feed, and add a review step to any change that adds a new domain. A pull request check that fails when a new external script URL appears is cheap and effective.
Safeguard builds an SBOM and scans the dependencies in your repository, which covers the bundled half of this inventory. You can start with a free scan at gold.safeguard.sh/scan.
Try it yourself
Click through it at your own pace, or open it full screen. Notice how direct and transitive packages are listed together.
Quick check
Will the network panel show bundled npm packages?
No. Bundled code arrives inside your own script file, so use the lockfile or an SBOM for that part.
How often should the list be reviewed?
At least when a new domain appears, and on a regular schedule such as quarterly for high risk pages.
Sources
- OWASP, Third Party JavaScript Management Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Third_Party_Javascript_Management_Cheat_Sheet.html
- MDN, Content Security Policy: https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP
Last reviewed: 19 October 2026.