JavaScript security: the client-side threat model
A threat model for code running in a browser you do not control, on a page assembled from several companies' code, with no privilege boundary between any of it.
TL;DR
JavaScript security
THE CORE PROBLEM
Every script on a page has the same access to that page. There is no privilege boundary between your checkout code and a fourth-party pixel.
THE UNDERWEIGHTED RISK
Most JavaScript security guidance covers code you wrote. Most incidents on payment pages involve code you did not.
THE CONTROLS
CSP for origin, SRI for file contents, monitoring for behaviour, and removing scripts you cannot justify, which beats all three.
Short on time? You can write flawless JavaScript and still have a skimmer on your checkout.
What is JavaScript security?
JavaScript security covers the risks that arise from code executing in a visitor's browser, including flaws in your own code, the behaviour of third-party scripts you load, and the absence of any privilege separation between the two. The discipline splits into two halves that get very unequal attention. Writing secure JavaScript, avoiding injection flaws, handling input carefully, using framework defaults, is well covered and genuinely important. Controlling the JavaScript you did not write but do load is where most payment-page incidents actually originate, and it gets far less coverage because it is not really a coding problem.

CSP is an origin allowlist. It decides where a script may load from, never what the script does once it is allowed. Both controls are useful, and they are not the same control.
The absence of a privilege boundary
This is the fact everything else follows from. When a browser loads a page, every script that executes in that page's context has identical access: the full DOM, every form input, cookies not marked HttpOnly, local and session storage, and the ability to make network requests.
Your checkout code can read the card field. So can the analytics tag. So can the script that analytics tag loaded. The browser has no mechanism for saying 'this script may read this input and that one may not'. Operating systems solved this decades ago with process isolation and permissions; browsers have no equivalent for third-party scripts on a page.
That gap is why PCI DSS 6.4.3 exists as an administrative control asking you to inventory and justify each script. The standard is compensating for something the platform does not provide.
The JavaScript security vulnerabilities that actually cause incidents
Failure
Cause
What addresses it
Cross-site scripting
Your application renders input as executable code
Output encoding, framework defaults, strict CSP with nonces
Compromised third party
A vendor you load is breached and serves altered code
Payload hashing, behavioural monitoring. A CSP does not help here
Tag manager abuse
Container access used to inject script
MFA, least privilege, change review, monitoring what the container publishes
Dependency compromise
A build-time package is backdoored
Lockfiles, provenance, review, plus production payload monitoring
Data leakage
A legitimate script sends more than it should
Behavioural monitoring and destination control
Clickjacking and overlay
Your page framed, or a form drawn over yours
frame-ancestors, and structural monitoring of the payment form
Most of the risk is in code you did not write but do load.
Which control does what
Ranked by what they actually cover rather than by how much attention they get:
Remove what you cannot justify. The only control that reduces the attack surface rather than watching it. Cheapest and most effective, and the least exciting.
Content Security Policy. Browser-enforced origin allowlist. Strong against XSS and unlisted sources. Silent on the behaviour of allowed scripts.
Subresource Integrity. Pins file contents. Excellent for static assets, impractical for vendor tags that update.
Script monitoring. Records what executed and what it did, with integrity by payload hash. Covers the compromised-vendor case the others miss.
Secure coding. Necessary, well documented, and addresses only the code you wrote.
Why payment pages are different
On most pages a leaking script is a privacy problem. On a payment page it is card data, which has immediate resale value and a regulatory regime attached. That is why the standard singles out payment pages, and why the sensible starting point for any programme is the checkout rather than the whole site.
Sources and methodology
PCI DSS requirement text is quoted from PCI DSS v4.0.1 (June 2024), published by the PCI Security Standards Council, which is the current version at the time of writing. Requirements 6.4.3 and 11.6.1 became mandatory on 31 March 2025. Where we describe a competitor, we describe publicly available information and say so when something should be verified with the vendor. We publish this product and that is a bias worth knowing about while you read.
ASK AN AI
Don’t just take our word for it
Ask your assistant what to look for in a tool for PCI DSS 6.4.3 and 11.6.1.
Frequently asked questions
No. The language is not the problem. The issue is the execution model: a page runs code from several sources with no privilege separation between them, so the least trustworthy script has the same access as the most trustworthy.
A compromised third-party script, by some distance. It requires no flaw in your own code, arrives through a channel you deliberately opened, and passes both your CSP and your allowlist.
Partly. React, Vue and Angular escape output by default, which removes most classic XSS. They do nothing about third-party scripts you load deliberately, because those run exactly as intended.
Partly, and it is harder than it sounds. Techniques exist, including iframes with restricted permissions and various proxying approaches, and vendor scripts frequently break under them. Some products are built entirely around doing this well.
No, not as a security control. Minification is a performance measure. Obfuscation raises the effort for a casual reader and does not prevent determined analysis, because the code has to execute in an environment the attacker controls.
Two mechanisms: Subresource Integrity if the file is versioned and stable, and payload monitoring if it is a vendor tag that updates. Pin the contents one way or the other.
No. HTTPS protects data in transit between browser and server. A skimmer operates inside the browser after decryption, and it will happily send stolen data over HTTPS as well.
Yes, as the build-time half of the same problem. A backdoored package ends up in your bundle and runs with full page access. Lockfiles and provenance help at build time; payload monitoring catches what reaches production.
It is unglamorous: remove scripts from the payment page that nobody can justify. It is the only control that shrinks the attack surface instead of observing it, and 6.4.3 makes you do the analysis anyway.
With the checkout, not the whole site. Inventory what executes there, delete what has no owner, add a CSP in report-only, then enforce. That sequence gets most of the benefit in a few days.
Related articles
Technical
Content Security Policy: a complete guide
What a CSP does, how to deploy one without breaking your site, and the gap it leaves open.
Technical
CSP vs script monitoring: two controls, different jobs
One governs where scripts come from. The other governs what they do. You want both.
Technical
Subresource Integrity: what SRI solves and what breaks
Why SRI is excellent for static assets and almost unusable for third-party tags.
Technical
JavaScript obfuscation and de-obfuscation
Why malicious scripts are obfuscated, the common techniques, and how they get unpacked.
See every script on your own checkout.
Install the tag, mark your payment pages, and watch the inventory fill in. Free forever on 1,000 page views a month, no credit card.
Free forever plan. No credit card.