Every script on a web page runs with the full authority of the page, so a single altered third-party file can read card numbers, passwords and session tokens as users type them. WAFs, code review and server-side scanning never see that code execute: it runs in the visitor's browser, often from a CDN, vendor or tag manager container nobody in engineering reviewed. This guide covers the browser-side supply chain, web skimming at a defensive level, the PCI DSS v4.0.1 payment page requirements 6.4.3 and 11.6.1, Subresource Integrity, Content Security Policy reporting, DOM-based XSS, Trusted Types and cross-site leaks, where tokens should live in the browser, extension risk and monitoring. It is for application security engineers, front-end leads and whoever answers to a QSA for payment pages.
Key takeaways
- Inventory what the browser actually loads, not what the repository contains: tag managers and vendor scripts add code at runtime that bypasses your build.
- PCI DSS 6.4.3 and 11.6.1 are now mandatory for payment pages: every script authorised, integrity-assured and justified, and tamper detection on the headers and scripts the browser receives.
- Pin and hash what you can, isolate what you cannot. Subresource Integrity works for static, versioned files; dynamic vendor scripts need a cross-origin iframe, a strict CSP or a server-side alternative.
- Treat DOM sinks as an attack surface. Trusted Types, now Baseline across current browsers, turns string assignments to innerHTML, eval and script src into policy-checked operations.
- Keep long-lived tokens out of JavaScript reach. One malicious script can read everything in localStorage; HttpOnly cookies or a backend-for-frontend remove that prize.
What runs in your users' browsers
Each source of code in the rendered page has a different owner and changes in a different way.
| Source | Who can change it | Typical risk | Primary control |
|---|---|---|---|
| First-party bundles (your build) | Your developers and your CI/CD pipeline | Compromised dependency bundled at build time; poisoned pipeline | Dependency pinning and review, build provenance, code review |
| Open source libraries loaded from a public CDN | The package maintainer, the CDN operator, whoever controls the domain | Unpinned "latest" URLs pick up a malicious release; domain changes hands | Exact version pinning, SRI, or self-hosting |
| Vendor scripts (analytics, chat, payments, fraud, advertising) | The vendor, at any time, often without notice | Vendor server compromise; scripts that load further scripts (fourth parties) | Contract terms, isolation in iframes, CSP, monitoring |
| Tag manager containers | Marketing and analytics users with publish rights | New tags published outside change control; stolen tag manager account | Restricted publish rights, MFA, approval workflow, monitoring |
| Browser extensions | The extension developer, or whoever steals their account | Read and modify pages and cookies | Allowlists for staff; nothing for customers |
OWASP's Third Party JavaScript Management Cheat Sheet names the main threat: "The single greatest risk is a compromise of the third party JavaScript server." Vendor code runs with the privileges of the user's session, so OWASP's starting point is the right one: "Audit the scripts actually loaded before claiming that only first-party code executes." Micro frontends from separate teams share one origin, so one team's third-party addition can read every fragment's DOM and storage: inventory each fragment's pipeline. See the Software Supply Chain Security Guide.
Web skimming and Magecart at a defensive level
Magecart is the umbrella name researchers gave to groups that inject payment skimmers into checkout pages. In formjacking or e-skimming, altered JavaScript copies card fields to an attacker-controlled server while the real payment completes normally. Neither shopper nor processor sees anything unusual, and the stolen data never touches the merchant's servers.
- British Airways (2018): RiskIQ attributed the breach to Magecart. The ICO fined the airline £20 million in October 2020; data for about 429,612 customers and staff was affected, including card numbers and security codes.
- polyfill.io (2024): after the domain was sold, the script served from cdn.polyfill.io redirected visitors to scam sites, affecting more than 100,000 sites according to BleepingComputer, which reported Sansec's finding that the code "only activates on specific mobile devices at specific hours". Conditional payloads are why one-off manual checks miss injected code.
In ATT&CK terms, skimmers sit at T1059.007 JavaScript (the page cites FIN6 using JavaScript "to steal payment card data from e-commerce sites"), delivered through T1195.002 Compromise Software Supply Chain or T1189 Drive-by Compromise. Skimmer code is usually obfuscated, so compare content and behaviour against an approved baseline rather than rely on signatures.
PCI DSS v4.0.1 requirements 6.4.3 and 11.6.1
Both requirements became mandatory on 31 March 2025 and target the payment page as it is received by the consumer browser, not as it sits on your server.
| Requirement | What the standard says | Evidence an assessor will expect |
|---|---|---|
| 6.4.3 | "All payment page scripts that are loaded and executed in the consumer browser must be managed as follows: A method is implemented to confirm that each script is authorized. A method is implemented to assure the integrity of each script. An inventory of all scripts is maintained with written business or technical justification for why each is necessary." | Script inventory with owner and justification; an authorisation record (change ticket or approval) per script; the integrity method (SRI hash, CSP hash or nonce, or monitoring that verifies content) |
| 11.6.1 | "A change- and tamper-detection mechanism is deployed as follows: To alert personnel to unauthorized modification (including indicators of compromise, changes, additions, and deletions) to the security-impacting HTTP headers and the script contents of payment pages as received by the consumer browser." The mechanism must evaluate the received headers and payment page at least weekly, or at a frequency set by a targeted risk analysis under 12.3.1. | Tool or process configuration, alert routing to a named team, alert history and how alerts were handled; the targeted risk analysis if the frequency is not weekly |
- Iframes split the responsibility: per PCI SSC guidance, scripts on the parent page are the merchant's and scripts inside the provider's iframe are the provider's. Where scripts create the iframe, 6.4.3 and 11.6.1 apply to them.
- Full redirects: the requirements do not apply when the shopper is redirected entirely to a compliant provider's page, but the redirecting code is still a target.
- SAQ A merchants: in January 2025 PCI SSC replaced 6.4.3 and 11.6.1 in SAQ A with an eligibility criterion: the merchant must confirm their site "is not susceptible to attacks from scripts that could affect the merchant's e-commerce system(s)". FAQ 1588 allows techniques such as those in 6.4.3 and 11.6.1, or confirmation from a compliant provider whose instructions are followed. The work is much the same.
Scoping, SAQ types and the rest of the standard are covered in the PCI DSS 4.0 Guide.
Controlling what loads: SRI, CSP and isolation
Subresource Integrity
The integrity attribute takes sha256, sha384 or sha512 hashes; if the delivered file does not match, the browser refuses to load it and returns a network error. It works on <script> and on <link> for stylesheets, preload and modulepreload. Cross-origin files need crossorigin="anonymous" and CORS headers, or the check always fails. Generate hashes in the build (openssl dgst -sha384 -binary file.js | openssl base64 -A). The newer Integrity-Policy header, with a report-only variant, blocks scripts loaded without integrity metadata and reports integrity-violation events.
Content Security Policy with reporting
Host allowlists are weak: MDN cites a case where Google Analytics needed 187 Google domains allowlisted. A strict CSP uses per-response nonces or hashes: script-src 'nonce-{random}' 'strict-dynamic'; object-src 'none'; base-uri 'none', plus tight connect-src and form-action to limit where a skimmer can send data. Deploy first as Content-Security-Policy-Report-Only (not allowed in a meta tag) with Reporting-Endpoints and report-to, keeping the deprecated report-uri alongside. Violation reports double as a real-user tamper signal for 11.6.1. See the Security Headers Guide.
Choosing the control
| Control | Stops | Does not stop | Use when |
|---|---|---|---|
| SRI | Any change to a specific static file | Scripts the file loads later; vendors that update in place | Versioned libraries and assets you host or pin |
| Self-hosting | CDN or vendor domain compromise and domain sale | A malicious version you copied in; you own updates | Libraries without a vendor service behind them |
| Strict CSP (nonce or hash) | Injected inline and unapproved external scripts; exfiltration to unlisted hosts if connect-src is tight | Malicious code inside an approved script; 'strict-dynamic' trusts what approved scripts load | Any page you control end to end |
| Cross-origin iframe with sandbox | Vendor code reading the parent DOM, cookies and form fields | Attacks inside the iframe; weak postMessage handling | Payment fields, chat widgets, advertising |
| Server-side tagging or collection | Third-party code in the browser at all | Vendor UI that must render client-side | Analytics and marketing events on sensitive pages |
For iframes, use the sandbox attribute and re-enable only what the frame needs, and on postMessage name the target origin rather than * and match the sender's origin exactly: OWASP calls a substring test such as message.origin.indexOf(".owasp.org")!=-1 "very insecure".
DOM-based XSS, Trusted Types and JavaScript-specific flaws
DOM-based XSS happens when attacker-controlled data reaches a browser API that executes it. MDN groups these injection sinks into HTML sinks (innerHTML, outerHTML, insertAdjacentHTML, document.write(), iframe srcdoc), JavaScript sinks (eval(), the Function() constructor, string arguments to setTimeout()) and script URL sinks (script.src, importScripts(), Worker(), service worker registration).
- Trusted Types: with
require-trusted-types-for 'script'in the CSP, passing a plain string to those sinks throws a TypeError; code must pass a value from a named policy (trustedTypes.createPolicy()) that usually wraps a sanitiser, and thetrusted-typesdirective lists permitted policy names. MDN marks the API Baseline since February 2026. Start in report-only mode and use adefaultpolicy only as a transition aid. - Prototype pollution: recursive merges of user input "without first sanitizing the keys" let
__proto__keys from a URL fragment or parsed JSON add properties to every object. PortSwigger notes that on the client this "commonly leads to DOM XSS". Block__proto__,constructorandprototypekeys in merge utilities and useObject.create(null)orMapfor lookups. - DOM clobbering: HTML elements with
idornameattributes can shadow global variables and document properties that scripts trust. Configure sanitisers to strip those attributes from user content and avoid relying on implicit globals. - Cross-site leaks (XS-Leaks): OWASP describes these as "a browser side-channel attack": another site infers facts about a signed-in user from signals such as frame counts, timing or error events. Defences are headers:
SameSitecookies, Fetch Metadata checks that reject unexpectedSec-Fetch-Site: cross-siterequests to sensitive endpoints,Cross-Origin-Opener-Policy: same-origin,Cross-Origin-Resource-Policy: same-originand CSPframe-ancestors.
See the Web Application Attacks Guide.
Where identity fits: tokens in the browser
Single-page applications often keep OAuth access and refresh tokens in localStorage, which any script on the origin can read. OWASP is direct: "Do not store session identifiers in local storage as the data is always accessible by JavaScript," and "A single Cross Site Scripting can be used to steal all the data in these objects." A stolen refresh token is a long-lived credential that works from the attacker's machine, which maps to ATT&CK T1528 Steal Application Access Token and T1539 Steal Web Session Cookie.
| Storage option | Readable by page scripts | Persistence | Assessment |
|---|---|---|---|
| localStorage | Yes | Until cleared | Avoid for tokens and session identifiers |
| sessionStorage | Yes | Tab or window lifetime | Smaller window, same XSS exposure |
| In-memory JavaScript variable | Yes, while the page runs | Page lifetime | Acceptable for short-lived access tokens with a protected refresh mechanism |
| Cookie with HttpOnly, Secure, SameSite | No | Set by server | Preferred for sessions; add CSRF defences |
| Backend-for-frontend (server holds OAuth tokens, browser holds a session cookie) | No | Server-controlled | Strongest pattern for high-value applications |
Set session cookies with the __Host- prefix, which requires Secure, Path=/ and no Domain attribute, binding it to the exact host. HttpOnly cookies are still sent with fetch() calls, so an injected script can act as the user while the page is open even if it cannot steal the cookie; short token lifetimes and sender-constrained tokens limit the damage. See the Token and Session Security Guide and the OAuth 2.0 and OpenID Connect Guide.
Tag managers and browser extensions
Both change what runs on your pages outside your release process.
Tag managers and consent
A tag manager is a deployment pipeline that bypasses your deployment pipeline. OWASP advises: "Restrict tag-manager publishing access and review changes as application code", and protect the tag manager account with two-factor authentication. Separate editing from publishing, require approval for tags on login or checkout pages, and keep payment pages out of the container where possible. A data layer limits what vendors receive, but OWASP warns "A data layer is not a sandbox".
Tags that fire before consent or collect form contents also create privacy exposure: map each tag to a consent category and test that it stays silent when consent is refused. See the Consent Management Guide: Cookies, CMPs and GPC.
Browser extensions
Extensions with broad host permissions can read and change every page a user visits, outside your CSP. You cannot control customers' extensions, another reason to keep tokens out of script-readable storage. For staff, allowlist extensions through enterprise browser policy (in Chrome, ExtensionInstallBlocklist set to * with ExtensionInstallAllowlist, and ExtensionSettings with runtime_blocked_hosts to keep extensions off payment and admin sites). The Cyberhaven Chrome extension breach shows how OAuth consent phishing against one developer turned a trusted extension into malware in users' browsers (ATT&CK T1176.001 Browser Extensions). See the Enterprise Browser and Browser Security Guide.
Monitoring and detection
Two approaches satisfy 11.6.1; many organisations use both.
| Aspect | Synthetic crawling (agentless) | In-page agent (script on the page) |
|---|---|---|
| How it works | Headless browsers load your pages on a schedule and record scripts, network calls and headers | A monitoring script runs in real users' browsers and reports scripts and behaviour |
| Coverage | Only the paths, geographies and devices you simulate; conditional skimmers can hide | Real sessions, including device-specific and time-specific payloads |
| Trade-off | No user impact; detect only | Adds a script that must itself be inventoried; some can block |
| Best fit | Weekly 11.6.1 evidence, smaller estates | High-volume checkout, heavy third-party use |
Make crawlers reach the payment step, vary user agents and locations, and diff each run against the approved inventory. Send CSP and Integrity-Policy reports to the SIEM; alert on new script origins, new outbound destinations and security-impacting header changes.
Metrics that show it is working:
- Percentage of scripts on payment and login pages with owner, justification and authorisation (target 100).
- Percentage of static third-party scripts protected by SRI or served from your own origin.
- Time from an unauthorised script or header change to alert triage, against your incident response target.
- CSP violation reports per week by directive, trending down after rollout, with each new origin investigated.
- Number of tag manager users with publish rights, and publishes made without approval (target 0 on sensitive pages).
- Number of application flows that still store tokens in localStorage (target 0).
Implementation plan
- Discover: crawl payment, login and account pages with a headless browser, recording every script, origin and header.
- Inventory and justify: record each script's owner and justification; remove anything nobody will own. This is your 6.4.3 inventory.
- Pin and hash: replace "latest" CDN URLs with exact versions, add SRI to static files, and self-host libraries with no service behind them.
- Isolate: move payment fields into the provider's iframe or hosted fields, and move vendor widgets off sensitive pages or into sandboxed iframes.
- CSP in report-only: run a strict policy in report-only mode for two to four weeks, fix legitimate violations, then enforce. Rollback is removing the enforcing header while keeping the report-only one.
- Trusted Types and tokens: run Trusted Types in report-only, fix sinks, then enforce; move tokens out of localStorage.
- Monitor and govern: turn on 11.6.1 change detection, route alerts to a named team with a runbook, and put tag publishing under change control.
A small merchant using a provider's hosted fields can usually meet the requirements with steps 1 to 4 and weekly synthetic monitoring. A large retailer with dozens of vendor scripts should expect step 5 to take several release cycles and consider an in-page agent on checkout.
Framework mapping
| Control area | PCI DSS v4.0.1 | NIST CSF 2.0 | NIST SP 800-53 Rev. 5 | CIS Controls v8.1 | ISO/IEC 27001:2022 | ATT&CK (v20) |
|---|---|---|---|---|---|---|
| Script inventory and authorisation | 6.4.3 | ID.AM-02, GV.SC-07 | CM-7, SA-9, SR-3 | 2.7, 16.4 | A.5.21, A.8.9 | T1195.002 |
| Script integrity and DOM XSS | 6.4.3, 6.2.4 | PR.PS-01, PR.PS-06 | SI-7, SI-10, SC-18 | 16.1 | A.8.26, A.8.28 | T1059.007 |
| Change and tamper detection | 11.6.1 | DE.CM-09 | SI-4, SI-7 | 8.2 | A.8.16 | T1189 |
| Token storage and sessions | n/a | PR.AA-03 | SC-23, IA-5 | 16.1 | A.8.5 | T1528, T1539 |
| Browser extensions | n/a | PR.PS-01 | CM-7, CM-11 | 2.5, 9.4 | A.8.19 | T1176.001 |
Common failure patterns
- Unpinned CDN URLs: loading
@latestor a major-version alias means the next published version runs on your page without review. In the Lottie Player npm compromise, versions 2.0.5 to 2.0.7 were reportedly published with a maintainer's stolen token, and sites loading the unpinned library from a CDN showed visitors a wallet-draining prompt. - Forgotten script domains: a tag pointing at a sold or lapsed domain runs whatever the new owner serves, as with polyfill.io.
- Inventory from the repository, not the browser: tag manager and vendor-loaded scripts are missing, so the 6.4.3 inventory is wrong on day one.
- Monitoring that only checks the home page: skimmers target the checkout step, and conditional payloads fire only for some devices and times.
- CSP left in report-only forever, or enforced with
'unsafe-inline'and wildcard hosts: evidence of a control without the protection. - Refresh tokens in localStorage: one injected script becomes persistent account access that survives closing the browser.
Practitioner checklist
- Crawl payment and login pages and build a script inventory with owner, justification and authorisation for each entry.
- Pin every third-party library to an exact version (never
@latest) and add SRI withcrossorigin="anonymous"to static files. - Put payment fields in the provider's iframe or hosted fields, and keep vendor widgets off payment pages.
- Deploy a strict nonce- or hash-based CSP with tight
connect-srcandform-action, report-only first. - Enable Trusted Types in report-only mode, fix sinks, then enforce
require-trusted-types-for 'script'. - Move session and refresh tokens out of localStorage into
__Host-HttpOnly, Secure, SameSite cookies or a backend-for-frontend. - Restrict tag manager publish rights, require MFA and approval, and map every tag to a consent category.
- Run 11.6.1 change and tamper detection at least weekly, or as set by a targeted risk analysis, with alerts routed to a named team.
Standards and references
- PCI Security Standards Council: Document Library (PCI DSS v4.0.1)
- PCI Security Standards Council: Important Updates Announced for Merchants Validating to Self-Assessment Questionnaire A
- OWASP: Third Party JavaScript Management Cheat Sheet
- OWASP: HTML5 Security Cheat Sheet
- MDN Web Docs: Subresource Integrity
- MDN Web Docs: Content Security Policy (CSP)
- MDN Web Docs: Trusted Types API
- MITRE ATT&CK: Command and Scripting Interpreter: JavaScript (T1059.007)
Related NHI Mgmt Group resources: PCI DSS 4.0 Guide · Token and Session Security Guide · Enterprise Browser and Browser Security Guide · Consent Management Guide: Cookies, CMPs and GPC · Security Headers Guide