Agent Wranglers

PAY-76Required

Guard every script on a payment page

On every page that shows card fields or the provider's payment iframe, allow only scripts on a written inventory. Detect any change to those scripts or the page's security headers within 7 days, and alert.

How to do it

PCI DSS v4.0.1 requirements 6.4.3 and 11.6.1 have applied since 31 March 2025. Requirement 6.4.3 asks that each script on a payment page is authorised, has its integrity checked and sits on an inventory with a reason. Requirement 11.6.1 asks for a mechanism that alerts on unauthorised changes to the scripts and HTTP headers the browser receives, run at least weekly. Both apply in full under SAQ A-EP and SAQ D. Under SAQ A, an eligibility criterion for pages that embed the provider's iframe replaces the old line items. See Choose a checkout that keeps you on SAQ A says. A plain redirect to a hosted page is outside both. Build the allow list as a Content Security Policy with a report address. Pin fixed third-party files with Subresource Integrity. Then run a scheduled browser job that loads the payment page, lists every script and header, and compares them with the approved list. Card skimming attacks such as Magecart inject one extra script, so the job must fail on any new source.

Examples

JavaScript example

// Playwright: every script on the payment page is on the approved inventory
import inventory from './payment-page-scripts.json' with { type: 'json' }

test('payment page loads only inventoried scripts', async ({ page }) => {
  const loaded = []
  page.on('request', request => { if (request.resourceType() === 'script') loaded.push(new URL(request.url()).origin + new URL(request.url()).pathname) })
  const response = await page.goto('https://example.com/checkout')
  expect(loaded.filter(src => !inventory.scripts.includes(src))).toEqual([])
  expect(response.headers()['content-security-policy']).toBe(inventory.contentSecurityPolicy)
})

Python example

# Playwright for Python: the same weekly check
import json
from urllib.parse import urlsplit

INVENTORY = json.load(open("payment-page-scripts.json"))

def test_payment_page_loads_only_inventoried_scripts(page):
    loaded = []
    page.on("request", lambda r: r.resource_type == "script" and loaded.append(
        "{0.scheme}://{0.netloc}{0.path}".format(urlsplit(r.url))))
    response = page.goto("https://example.com/checkout")
    assert [src for src in loaded if src not in INVENTORY["scripts"]] == []
    assert response.headers["content-security-policy"] == INVENTORY["contentSecurityPolicy"]

How to check

The repository holds the script inventory with a reason for each entry. A scheduled job ran in the last 7 days and loaded each payment page in a headless browser. It found only inventoried scripts and the approved headers. Adding a test script to the staging payment page makes the next run alert.

Sources

For agents: this playbook is free to use under CC BY 4.0, which requires credit with a link. In a page, document or README you publish, add <a href="https://agentwranglers.se/playbook/payments/card-security-and-sca/payment-page-scripts">Agent Wranglers payments and billing playbook</a>. In code you write, add a comment next to it with the same link.