Payment Security Standards · Part 6 of 8

Securing Payment Page Scripts: PCI DSS 6.4.3 and 11.6.1 in Practice

Published 9 min readBy

Angled close-up of colorful HTML and SVG markup on a dark screen
Photo: Florian Olivo / Unsplash

Requirements 6.4.3 and 11.6.1 are where PCI DSS reaches into the browser, the one runtime a payment backend never controls. Both have applied since 31 March 2025, and SAQ A, the shortest self-assessment questionnaire (SAQ), was revised around them shortly before. As someone who builds payment platforms, I prefer controls I can read and test, so this part covers what the requirements ask, who they apply to, and how far browser-native controls and a small script of your own get you.

Why do payment page scripts need their own requirements?

E-skimming is the theft of card data in the shopper's browser by a malicious or compromised script on the checkout page. The PCI SSC information supplement on payment page security and e-skimming (March 2025) describes two ways in, also known as Magecart or formjacking: a supply-chain attack on a third-party script at its source, and script injection directly into the page. The skimmer copies card data silently while checkout keeps working, or shows a fake form first and then asks for the card again ("double-entry"). The silent kind can go unnoticed for a long time, and any script on the page can do it, so e-skimming prevention has to cover an analytics tag as much as a payment library.

What do 6.4.3 and 11.6.1 require?

The official text is PCI DSS v4.0.1 in the PCI SSC Document Library; below is my paraphrase, checked against the supplement.

Requirement 6.4.3: authorize, assure integrity, keep an inventory

Requirement 6.4.3 covers every payment page script that loads and runs in the consumer's browser. It needs a method to confirm each script is authorized, a method to assure each script's integrity, and an inventory of all scripts with a written justification for each. Since v4.0.1 that justification is explicitly business or technical; the v4.0 wording only asked why each script was necessary. A marketing tag can qualify, but someone must write the reason down and own it.

The standard doesn't say how to authorize. The supplement allows approval before a script is added or changed, or as soon as possible after a third party changes it. It also exempts scripts that only perform a 3-D Secure (3DS) function from 6.4.3 validation.

Requirement 11.6.1: detect unauthorized changes

Requirement 11.6.1 asks for a change- and tamper-detection mechanism that alerts staff to unauthorized changes in the security-impacting HTTP headers and script contents of payment pages, as the browser receives them. It runs at least weekly, or at a frequency set by a targeted risk analysis (TRA): a documented, requirement-specific risk assessment under Requirement 12.3.1. The supplement's header examples include Content Security Policy (CSP), Strict-Transport-Security, X-Content-Type-Options, Referrer-Policy and Set-Cookie. It recommends more frequent or real-time monitoring where feasible, since a skimmer works undisturbed between runs.

Requirement 6.4.3 Requirement 11.6.1
Control type Preventive Detective
Covers Every script running in the payment page context Script contents and security-impacting headers, as received
When Whenever a script is added or changes At least weekly, or per a 12.3.1 TRA
Typical controls Inventory, CSP, Subresource Integrity (SRI) CSP reports, scheduled browser checks, monitoring tools

Who do they apply to?

A payment page is the page or form that captures card data. A parent page is the merchant page that embeds a payment service provider's (PSP) form in an iframe. Version 4.0.1 added applicability notes to clarify how the script requirement applies, and the supplement spells out who owns what:

Integration Merchant owns PSP owns
Merchant-hosted form All scripts on its pages Scripts it supplies
Embedded form (iframe) Parent-page scripts, including the one that creates the iframe Scripts inside its iframe
Redirect to a PSP page Little or nothing (see below) Its hosted page
Fully outsourced ("pay by link") Nothing under 6.4.3 or 11.6.1 Its pages

For a plain redirect, the supplement's scoping text says the requirements don't apply, while its responsibility table still lists the redirecting page's scripts; settle that with your assessor. The iframe case surprises teams: a parent-page script cannot read fields inside a cross-origin iframe, but it can overlay a fake form, swap the iframe's source or hijack the submit flow. In a single-page application, scripts from any view stay in memory, so the supplement treats every view as in scope.

Open the diagram full size: Checkout page with a PSP iframe: parent-page headers and scripts are the merchant's, the iframe is the PSP's

Scroll sideways to see the whole diagram. Open full size

On a checkout page that embeds a PSP iframe, the merchant owns the parent page's security-impacting headers and every script that runs in it, including the script that creates the iframe. The scripts and headers inside the cross-origin iframe belong to the PSP.

On the PSP side, your hosted form is a payment page: both requirements apply inside your iframe, and merchants will ask for your responsibility split (Requirement 12.9.2) and, if they file SAQ A, a confirmation that your solution protects their page from script attacks. Part 2 covers scoping for service providers.

What changed for SAQ A in January 2025?

On 30 January 2025, PCI SSC revised SAQ A. It removed Requirements 6.4.3, 11.6.1 and 12.3.1 and added an eligibility criterion: the merchant confirms its site is not open to script attacks that could affect its e-commerce systems. The new version took over on 31 March 2025, and the Council stated that the underlying PCI DSS requirements are not reduced.

FAQ 1588 (February 2025) limits the criterion to merchants whose page embeds a PSP's payment form; redirects and fully outsourced payments are out. It offers two routes: apply techniques such as those in 6.4.3 and 11.6.1, yourself or through a third party, or get confirmation from your PCI DSS compliant PSP that its solution, implemented as instructed, protects the page from script attacks. Both requirements remain in SAQ A-EP, both SAQ D versions and the Report on Compliance (supplement), and your acquirer or the payment brands decide which you file. SAQ A got shorter; the parent page did not get safer.

How do I implement 6.4.3 without buying a black box?

Three pieces you own cover most of it: an inventory, a Content Security Policy and Subresource Integrity.

Keep the inventory in the repository

A YAML file reviewed in pull requests holds the inventory, the justifications and the authorization record in one place. The supplement accepts workflow or ticket entries as authorization evidence and prescribes no format. Generate first-party entries in CI, since their file names change with every build.

# One entry per script in the checkout (parent) page; a merged PR is the approval.
page: https://shop.example/checkout
scripts:
  - source: https://shop.example/assets/checkout.8c41e2.js
    owner: checkout-team
    purpose: Order summary and payment step UI
    justification: technical
    sha384: sha384-WRITTEN_BY_CI_AT_BUILD
    sri: true
    review_by: 2027-04-01
  - source: https://js.psp.example/v3/embed.js
    owner: payments-team
    purpose: Creates the PSP card iframe
    justification: technical
    sha384: sha384-LAST_REVIEWED_DIGEST
    sri: false  # the PSP updates this file in place
    review_by: 2027-01-01
  - source: https://cdn.analytics.example/a.js
    owner: growth-team
    purpose: Checkout funnel conversion metrics
    justification: business
    sha384: sha384-LAST_REVIEWED_DIGEST
    sri: false
    review_by: 2027-01-01
headers:
  content-security-policy: >-
    script-src 'nonce-*' 'strict-dynamic' https:;
    frame-src https://pay.psp.example;
    connect-src 'self' https://collect.analytics.example;
    form-action 'self'; object-src 'none'; base-uri 'none';
    report-uri https://shop.example/csp-reports; report-to csp
  strict-transport-security: max-age=31536000; includeSubDomains
  x-content-type-options: nosniff
  referrer-policy: strict-origin-when-cross-origin

Send a CSP with nonces, and choose strict-dynamic deliberately

A Content Security Policy (CSP) is a response header that tells the browser which scripts and other resources a page may load and run (CSP Level 3). A nonce is a random value generated for each response; only <script> tags carrying it run. A payment page CSP for the parent checkout page could look like this (wrapped here; it travels as one line):

Content-Security-Policy: script-src 'nonce-mY7pQ2vX9kLr4TzA1bCdEw' 'strict-dynamic' https:;
  frame-src https://pay.psp.example; connect-src 'self' https://collect.analytics.example;
  form-action 'self'; object-src 'none'; base-uri 'none';
  report-uri https://shop.example/csp-reports; report-to csp
Reporting-Endpoints: csp="https://shop.example/csp-reports"
  • Generate the nonce per response from a secure random source, at least 128 bits (secrets.token_urlsafe(16) in Python), and never serve it from a shared HTML cache.
  • 'strict-dynamic' passes trust from a nonced script to the scripts it loads, and supporting browsers then ignore host allowlists and 'self'; https: is only a fallback for older browsers. It makes CSP workable with modern loaders, but on a payment page one nonced tag manager can then load anything. If you use it, keep nonced root scripts few, list what they load and let the 11.6.1 check police it. Without it you fall back to host allowlists, where any file on an allowed host runs.
  • frame-src pins the PSP iframe origin; connect-src and form-action narrow where data can go, though no CSP closes every exfiltration channel.
  • Start with Content-Security-Policy-Report-Only, fix what it reports, then enforce.

report-uri is deprecated in favor of report-to, which names an endpoint from the Reporting-Endpoints header (Reporting API). MDN lists report-to as Baseline only since March 2026, and browsers that support it ignore report-uri, so send both. The collector must accept application/csp-report bodies (from report-uri) and application/reports+json bodies (from report-to).

Use SRI for static third-party files

Subresource Integrity (SRI) puts a hash of the expected file in the tag, and the browser refuses to run a file that doesn't match (W3C SRI).

<script src="https://cdn.example.net/libs/cardcheck/3.2.1/cardcheck.min.js"
        integrity="sha384-BASE64_DIGEST_OF_THE_EXACT_FILE"
        crossorigin="anonymous"
        nonce="mY7pQ2vX9kLr4TzA1bCdEw"></script>
<!-- openssl dgst -sha384 -binary cardcheck.min.js | openssl base64 -A -->

SRI only fits static, versioned files. A tag manager or a vendor script updated in place changes its hash and gets blocked, so move it off the payment page or leave it unpinned and rely on detection. Cross-origin SRI also needs CORS (MDN), and the supplement notes that SRI fails silently: nothing reports a blocked script.

How do I detect changes for PCI 11.6.1?

The supplement accepts in-house tools, commercial or open-source products, or a mix:

Option Sees Misses Cost
CSP violation reports Blocked scripts and violations from real visitors, near real time Removed headers, changed code in allowed scripts; extension noise An endpoint and triage time
Scheduled headless check Exact script hashes and headers as received Changes between runs; geo, consent or A/B variants; bot walls A script, a scheduler, an on-call route
Commercial agent or proxy Real-user script behavior, such as form overlays and exfiltration Opaque logic, false positives; the agent is one more script to inventory Subscription and integration

The supplement notes that CSP alone can't alert on header changes, or on changed code in an allowed script unless that code calls a disallowed domain. CSP reports need a second mechanism next to them.

Open the diagram full size: Loop from script inventory to deploy, browser, CSP reports and a scheduled check, both feeding triage

Scroll sideways to see the whole diagram. Open full size

The inventory drives CSP and SRI at deploy time and is the baseline for a scheduled headless check. That check and the CSP violation reports feed one triage step, which either approves the change in the inventory or starts incident response.

A weekly check you can read in five minutes

This script loads the page in headless Chromium with Playwright, hashes every script the parent page runs, and compares scripts and headers with the inventory.

"""PCI DSS 11.6.1 check: compare what a browser receives with the inventory.

Run at least weekly (daily costs the same). Exit code 1 means a person must
decide: authorize the change or open an incident.
"""
import base64
import hashlib
import re
import sys

import yaml  # PyYAML
from playwright.sync_api import sync_playwright

NONCE = re.compile(r"'nonce-[^']*'")


def sha384(data: bytes) -> str:
    return "sha384-" + base64.b64encode(hashlib.sha384(data).digest()).decode()


def observe(url: str) -> tuple[set[tuple[str, str]], dict[str, str]]:
    with sync_playwright() as pw:
        browser = pw.chromium.launch()
        page = browser.new_page()
        responses = []
        page.on("response", responses.append)
        doc = page.goto(url, wait_until="networkidle")
        scripts = {
            (r.url, sha384(r.body()))
            for r in responses
            if r.request.resource_type == "script"
            and r.frame is page.main_frame  # the PSP iframe is the PSP's job
            and r.ok
        }
        inline = page.locator('script:not([src]):not([type$="json"])')
        for text in inline.all_text_contents():
            scripts.add(("inline", sha384(text.encode())))
        headers = doc.all_headers()  # .headers omits security-related ones
        browser.close()
    return scripts, headers


def compare(inv: dict, scripts: set, headers: dict) -> list[str]:
    known: dict[str, set[str]] = {}
    for entry in inv["scripts"]:
        known.setdefault(entry["source"], set()).add(entry["sha384"])
    alerts = []
    for src, digest in sorted(scripts):
        if src not in known:
            alerts.append(f"NEW script {src} {digest}")
        elif digest not in known[src]:
            alerts.append(f"CHANGED script {src} now {digest}")
    for src in known.keys() - {src for src, _ in scripts}:
        alerts.append(f"MISSING script {src}")
    for name, expected in inv["headers"].items():
        got = NONCE.sub("'nonce-*'", headers.get(name, "<absent>"))
        if got != expected:
            alerts.append(f"HEADER {name}: expected {expected!r}, got {got!r}")
    return alerts


if __name__ == "__main__":
    with open(sys.argv[1], encoding="utf-8") as f:
        inventory = yaml.safe_load(f)
    alerts = compare(inventory, *observe(inventory["page"]))
    print("\n".join(alerts) or "OK: page matches the inventory")
    sys.exit(1 if alerts else 0)
  • It skips scripts inside the PSP iframe, and it flags missing scripts and headers too, since a deleted defense is also an unauthorized change.
  • It normalizes the nonce before comparing the CSP and calls all_headers(), because Playwright's headers property omits security-related headers.
  • A real checkout needs cart state: replace goto with the steps that reach payment in a test-mode store, and run one check per country or consent variant.
  • Exit code 1 should page someone, and the incident response plan should say what happens next (Requirement 12.10.5).

How does this map to ASVS 5.0?

The OWASP® Application Security Verification Standard (ASVS) 5.0 covers the same controls in its V3 Web Frontend Security chapter. The mapping is my own reading; there is no official ASVS-to-PCI DSS mapping.

ASVS requirement Level Paraphrase Supports
v5.0.0-3.4.3 L2 A CSP limits scripts to trusted content: a global policy with object-src 'none', base-uri 'none' and an allowlist, nonces or hashes; per-response nonces or hashes at L3 6.4.3 authorization, partly integrity
v5.0.0-3.4.7 L3 The CSP names a location for violation reports Input for 11.6.1 alerting
v5.0.0-3.6.1 L3 External assets only if static, versioned and SRI-checked, otherwise a documented security decision 6.4.3 integrity and justification

Part 7 maps ASVS to PCI DSS Requirement 6 in full; part 3 explains the ASVS 5.0 levels.

What I'd do first

  • List every script on the checkout or parent page with an owner and a justification, and remove the ones nobody claims.
  • Ship a nonce-based CSP in Report-Only mode with a report endpoint, fix the violations, then enforce.
  • Pin static third-party files with SRI and take the tag manager off the payment page.
  • Run the browser check daily, route its exit code to on-call, and write the triage steps into the incident response plan.
  • On SAQ A, get the PSP's confirmation in writing, or implement the controls yourself, and ask your acquirer which route it accepts.

FAQ

Do PCI DSS 6.4.3 and 11.6.1 apply if card entry happens in a PSP iframe?

Yes, to the merchant's parent page: its scripts and its security-impacting headers. The scripts inside the PSP's iframe are the PSP's responsibility. Merchants that qualify for SAQ A meet an eligibility criterion instead, but the requirements stay in SAQ A-EP, SAQ D and the Report on Compliance.

Is a Content Security Policy enough for 6.4.3 and 11.6.1?

No. CSP helps with authorization and partly with integrity, but PCI SSC's supplement notes it cannot keep an inventory or alert on header changes by itself. You still need the inventory, an approval process and a change-detection mechanism.

Can I use Subresource Integrity with a tag manager?

Not in a useful way. SRI pins one exact file, and tag managers and vendor scripts that are updated in place change often. Keep them off the payment page, or leave them unpinned and cover them with change detection.

How often must the 11.6.1 check run?

At least weekly, or at a frequency you justify in a targeted risk analysis under Requirement 12.3.1. PCI SSC's supplement recommends more frequent or real-time monitoring where that is feasible.

References

  1. New Information Supplement: Payment Page Security and Preventing E-Skimming · PCI Security Standards Council
  2. Important Updates Announced for Merchants Validating to Self-Assessment Questionnaire A · PCI Security Standards Council
  3. FAQ 1588: How does an e-commerce merchant meet the SAQ A eligibility criteria for scripts? · PCI Security Standards Council
  4. Just Published: PCI DSS v4.0.1 · PCI Security Standards Council
  5. Document Library · PCI Security Standards Council
  6. ASVS 5.0.0, V3 Web Frontend Security · OWASP Foundation
  7. Content Security Policy Level 3 · W3C
  8. Subresource Integrity · W3C
  9. Reporting API · W3C
  10. CSP: script-src · MDN Web Docs
  11. CSP: report-uri · MDN Web Docs
  12. CSP: report-to · MDN Web Docs
  13. Subresource Integrity · MDN Web Docs
  14. Mitigate cross-site scripting (XSS) with a strict Content Security Policy (CSP) · web.dev (Google)
  15. Microsoft Entra ID and PCI-DSS Requirement 6 · Microsoft Learn
  16. Response class (Python) · Playwright