30% off Pro plugins with LAUNCH30 See plugins

Payment-page script inventory, authorisation record, weekly tamper check and header baseline: evidence for your PCI DSS 6.4.3 / 11.6.1 assessment and your SAQ A eligibility. No account, no telemetry to Dragon Core; the only requests it makes to anywhere but your own site (scans also fetch your own payment pages) are re-fetches of script URLs your own payment page already references, to keep their content hashes current.

Why this exists

Nearly every WooCommerce store embeds its gateway's card fields in an iframe: Stripe, WooPayments, PayPal or Braintree, Adyen. Since 31 March 2025, SAQ A carries an eligibility criterion that the merchant has confirmed its site is not susceptible to attacks from scripts. PCI SSC FAQ 1588 (February 2025) applies that criterion to merchants embedding a provider's payment form and accepts two confirmations:

  1. techniques such as those in requirements 6.4.3 and 11.6.1, applied by the merchant to its own page, or
  2. written confirmation from the PCI DSS validated provider that its embedded form includes protections against script attacks when implemented to the provider's instructions.

Not every provider issues one, which leaves route 1. The iframe belongs to the provider; the page it sits in is yours, and a script injected into that page is what e-skimming (the Magecart style of attack) uses to read a shopper's details before the iframe ever sees them. Dragon Checkout Guard is the record of what runs on that page: an inventory, a justification per script, a content hash rechecked weekly, and a response-header baseline.

If your own page's scripts can touch card data, you are on SAQ A-EP or SAQ D and 6.4.3 and 11.6.1 apply in full: the same records are what those requirements ask for. If your customer is redirected to the provider's own page to pay, the criterion does not apply to you, though assessors still commonly expect the page that starts the payment to be monitored.

Claim boundary

Dragon Checkout Guard produces evidence supporting your assessment. It never claims your site "is compliant" or "is certified", it never claims a script's integrity "is assured" (only that an integrity observation was recorded, by the method you chose), and it does not determine which SAQ you complete. Your acquirer, QSA or internal assessor settles that; this plugin gives them something concrete to review.

It does not look inside the gateway's iframe, and is not meant to: that document is served by the provider from its own origin and is covered by the provider's own PCI DSS validation.

Requirements

WordPress 6.5+, PHP 8.0+. WooCommerce is optional but required for the built-in checkout / cart / order-pay / add-payment-method page keys; without it, only custom paths are in scope. No account, no third-party API, no telemetry to Dragon Core. The only outbound request it makes to anywhere but your own site (scans also fetch your own payment pages) keeps a script's content hash current: it re-fetches whatever script URL your payment page already references, which may be your own domain or a third party's CDN (Stripe.js, a gateway SDK, Google Tag Manager). That request is a plain GET of a public URL and carries no data about your site, its configuration or its visitors. See "Data and privacy" below.

Getting started

  1. Activate the plugin and open Tools > Checkout Guard.
  2. On the Pages tab, confirm which pages are in scope. Checkout, Order Pay and Add Payment Method are watched by default; Cart is off by default (enable it on the Settings tab). Add custom paths on the Settings tab for anything else, for example a non-Woo payment page.
  3. Populate an initial inventory either way:
    • Scan now loops back over every in-scope page server-side and records what it finds. This is the fastest way to cover every page at once, and what the weekly cron does automatically. The loopback request is always anonymous and cart-less, so on a WooCommerce site that redirects an empty-cart Checkout away, the scan records that page as failed (dragoncheckoutguard_redirected) rather than inventorying it: use Open & capture with an item in your cart for those pages. Order Pay is skipped by every scan for the same reason, since it only exists for a real order. Any page a scan could not cover is called out under the Scan now button on the Inventory tab and on that page's card on the Pages tab, so a failed scan never looks like a clean one.
    • Open & capture (per page, on the Pages tab) mints a single-use token and sends your own browser to that page, so the capture also sees scripts a tag manager injects at runtime, which a server-side loopback cannot observe. Order Pay has no "Open & capture" button: without a specific order id there is nothing to loop back to. Checkout needs an item in your own account's cart first, and the screen says so: WooCommerce redirects an empty-cart checkout page away before your capture ever runs. Add Payment Method has no such precondition; it is a My Account endpoint, not cart-dependent, and just needs you logged in with a gateway that supports saved payment methods.
  4. Review each row on the Inventory tab and authorise the scripts that belong there.
  5. Answer the three questions on the Assessment guide tab, so the printable report carries the context an assessor needs.

The tabs

  • Inventory: every script seen on an in-scope page, filterable by status (new, authorised, ignored, changed) plus unconfirmed browser observations, with the export buttons and Scan now.
  • Pages: every page key, whether it is enabled, its URL, when it was last covered, and the per-page Open & capture button.
  • Headers: the response-header baseline per page and any drift.
  • Assessment guide: the SAQ A route self-check.
  • Settings: scope, capture, collector token, retention, redacted parameters, uninstall behaviour.

Authorising a script

Each script gets:

  • Justification (required, 5+ characters): the evidence a reviewer actually reads.
  • Owner: who inside the business is accountable for it.
  • Provider: the third party serving it, if any.
  • Business purpose: why it is there.
  • Review due: a date to revisit the authorisation.
  • Integrity method: how you are monitoring this script for tampering.
    • Subresource Integrity (SRI): the script tag itself carries an integrity attribute the browser verifies.
    • Hash monitored by this plugin: records that you are relying on Dragon Checkout Guard's own content-hash comparison as this script's integrity control. The hashing is not gated on this setting (every new or authorised remote script is hashed on the same schedule regardless), but selecting this method is your statement, for the assessment, that a hash change is what you are counting on to catch tampering, rather than SRI or a vendor's own controls.
    • Vendor-managed integrity: the provider, for example a payment processor, is contractually responsible for the script's integrity.
    • Not feasible, justified: record why in Justification, for example a script outside your control that cannot be hashed or SRI-pinned.
    • None yet: the default for a newly discovered script, and not a valid end state for your assessment.

Revoking a script's authorisation puts it back to new so it surfaces for re-review.

Inline blocks printed by WordPress are identified by handle and position, so a change to one is recorded as drift on the same row rather than appearing as a new script on every scan. Inline previews are stored with sensitive values redacted.

Recognised providers

When a script matches a known provider, the authorise form offers Use suggested justification, which prefills provider, business purpose, justification and integrity method.

ProviderCategory
Stripe, PayPal, Braintree, Adyen, Klarna, Mollie, Square, Google Pay, Apple Pay, WooPaymentsPayments
Stripe Radar, ThreatMetrixFraud
WooCommerceWordPress
Google Tag ManagerTag manager
Google Analytics, Meta Pixel, HotjarAnalytics
Intercom, Zendesk, LiveChatChat

Anything outside the payment and WordPress categories carries a reviewer note on its row: a tag manager or pixel can pull further scripts onto a payment page, a chat widget is third-party code on a page you are attesting for, and session recording on a payment page records keystrokes unless it is masked or excluded. Unrecognised scripts are listed plainly for you to justify or remove.

The plugin's own collector script is recorded as authorised automatically, so it never sits in your queue as an unexplained script.

Headers tab

Every in-scope page's security-relevant response headers (Content-Security-Policy, X-Frame-Options, Strict-Transport-Security, Referrer-Policy, Permissions-Policy, the Cross-Origin trio, X-Content-Type-Options, Cache-Control and the CSP reporting headers) are captured, compared to an accepted baseline, and flagged when they drift. Accept a new baseline once you have reviewed a change; changes feed Dragon Activity Log if it is active.

Baselines are captured by scans as well as by real pageviews, so a page has a baseline before any shopper arrives. Two details keep that honest:

  • A logged-in pageview is never used for headers. WordPress sends logged-in responses no-store, which is not what shoppers receive, so observing one would record drift no customer ever saw.
  • When a scan or an Open & capture made the baseline, the plugin's own capture path can influence cache-control and referrer-policy. Those two headers are therefore adopted from the first ordinary shopper view rather than reported as drift.

If a full-page cache serves your payment pages, the headers being compared may be the cache's own rather than your origin server's. Bypass the cache, or exclude payment URLs from full-page caching, for a meaningful headers comparison.

Browser collector and page caches

The optional browser collector (Settings tab, on by default) reports scripts that only exist once a real shopper's page is running: the ones a tag manager injects at runtime, which never appear in the server-rendered HTML a loopback scan can see. Reports land as observations you confirm before they join the inventory; nothing is added automatically, and an unconfirmed observation is never integrity-checked. Dismiss them one at a time, or use Dismiss all on the Inventory tab, or wp dragon-checkout-guard dismiss.

The report endpoint is guarded by a per-install token printed in the page source of in-scope pages, not by a nonce (a nonce means nothing for an anonymous visitor). Rotating the token (Settings, tick "Issue a new token when saving") invalidates the old one immediately, but if a full-page cache is serving your payment pages, visitors keep receiving cached HTML with the old token baked in until the cache is cleared, and their reports are rejected in the meantime. Clear your page cache immediately after rotating.

The collector enforces its limits before anything is written: see "Limits" below. If the hourly or total observation limit is reached, the collector pauses for an hour and an admin notice tells you, with a collector.tripped entry in the event log (and in Dragon Activity Log, if active).

Scan scheduling

A full scan runs automatically once a week via WP-Cron, and can be triggered on demand from the admin screen ("Scan now") or WP-CLI. On-demand scans are scheduled as a single cron event rather than run inline, so the request that clicks the button returns immediately; an atomic lock prevents the weekly cron, the on-demand trigger and WP-CLI overlapping.

Every scan rehashes every pending script, with no per-run row cap, and records an evidence event describing the run. A partial scan (one where some page could not be covered) merges into the same summary rather than replacing it, and a detected change whose database write failed is reported as a failed scan rather than a clean one.

Exports and the evidence report

All three are on the Inventory tab.

  • Export inventory (CSV): one row per script. Columns: script, type, party, pages, source, provider, owner, business purpose, justification, status, changed since authorised, authorised by, authorised at, integrity method, current hash, last checked, review due, first seen, last seen, why no hash.
  • Evidence record (CSV): the 11.6.1 record of the checks that ran, one row per check. Columns: checked at (UTC), trigger, pages in run, scripts seen, new scripts, changes found by this check, scripts still flagged, headers still flagged, outcome.
  • Print evidence report: a standalone printable "Payment page script integrity record" combining both, the header baselines, and your Assessment guide answers as "Assessment context". It opens in a new tab for the browser's print dialog.

CSV cells are neutralised against spreadsheet formula injection, since the values originate outside your site. Both exports are available from WP-CLI as CSV or JSON.

Site Health

Three tests appear under Tools > Site Health, in the Payments section:

  • Payment page check record: whether the weekly check has run in the last 8 days.
  • Scripts on your payment pages: whether any script is unauthorised or has changed since it was authorised.
  • Payment page response headers: whether any header drift is outstanding.

WP-CLI

bash
wp dragon-checkout-guard scan                      # scan every enabled page key
wp dragon-checkout-guard scan --page=checkout      # scan one page key

wp dragon-checkout-guard pages                     # every page key: enabled? URL?
wp dragon-checkout-guard pages --format=json

wp dragon-checkout-guard list                                  # inventory (dcg_scripts)
wp dragon-checkout-guard list --status=browser                 # unconfirmed browser observations
wp dragon-checkout-guard list --status=changed --format=csv    # scripts whose content hash changed

wp dragon-checkout-guard authorise 12 \
    --justification="Stripe.js payment element" \
    --method=vendor_managed --provider=Stripe --user=admin

wp dragon-checkout-guard dismiss 42                # one unconfirmed observation
wp dragon-checkout-guard dismiss --page=checkout   # every unconfirmed observation for a page
wp dragon-checkout-guard dismiss --all-unconfirmed --yes

wp dragon-checkout-guard export --type=inventory > inventory.csv
wp dragon-checkout-guard export --type=evidence --format=json

wp dragon-checkout-guard prune --days=90           # trim old events + unconfirmed observations
  • scan takes --page=<key>: checkout, cart, order_pay, add_payment_method or a custom:<hash> key.
  • pages takes --format=table|json|csv and lists every recognised page key, whether it is enabled, and its canonical URL where one exists.
  • list accepts --status=new|authorised|ignored|changed|browser and --format=table|json|csv, and warns rather than silently truncating if a result set exceeds its 500-row cap: narrow with --status.
  • authorise accepts --method=sri|hash_monitored|vendor_managed|not_feasible_justified|none, plus --owner, --purpose and --provider. Run it with WP-CLI's global --user=<id|login> so the authorisation is attributed to a real actor rather than recorded anonymously.
  • dismiss takes exactly one of <id>, --page=<key> or --all-unconfirmed (with --yes to skip the prompt). A confirmed observation is part of the audit trail and is never deleted here.
  • export takes --type=inventory|evidence and --format=csv|json, with the same columns as the admin exports. The evidence export covers the last 90 days and warns if it stopped short.
  • prune takes --days=<n>, applied to both events and unconfirmed observations; 0 clears everything eligible now. Without it, events use your configured retention and observations use 30 days, the same defaults the daily maintenance cron uses.

Limits

LimitValue
Passive capture intervalOne sample per page per interval, in minutes (setting, default 10). Passive sampling reads the script registry; it does not buffer the page.
Buffered capture size5 MB (Server_Capture::MAX_BUFFER_BYTES) for a scan or Open & capture; a larger page is passed through untouched and the capture is recorded as failed.
Remote hash fetches200 per UTC day (Integrity::DAILY_BUDGET).
Per-host backoff3 failures pause that host for 1 hour.
SchemeOnly https script URLs are fetched, with the connection pinned to the validated address where the site's HTTP transport supports it (curl).
Response size hashed2 MB (Integrity::MAX_BODY_BYTES).
Stale scriptsA script not seen on any page for 30 days stops being refetched and shows "Not seen recently".
Collector requests30 per minute per address (IPv6 grouped by /64).
Collector report size300 rows and 64 KB per report.
Collector trip500 new observations in an hour, or 5000 unconfirmed in total, pause the collector for an hour with an admin notice.
Observation retentionUnconfirmed observations are pruned after 30 days.
Event retention30 to 3650 days (setting, default 365).
Export sizeExports carry at most 5000 rows and say so when they stop short.
Custom paths50 paths, 255 characters each, site-relative only.
ConcurrencyOne scan at a time, enforced by a lock.

Event retention prunes only the low-value kinds: discovered, scan run, scan failed, collector tripped and settings changed. Everything else, including the evidence, authorised, revoked, changed, confirmed, headers changed and baseline accepted records, is kept regardless of the setting, because that is the record.

A script that could not be hashed says why, on its row and in the inventory export: host did not resolve, host resolves to a private address, host temporarily backed off after failures, daily fetch budget exhausted (retried tomorrow), fetch failed, response over 2 MB, local file not found, or unsupported file type.

Settings are validated rather than silently accepted: a custom path that points off-site or is malformed is refused with a reason and your typed value is handed back, and a numeric value outside its range is clamped and reported as adjusted.

If the plugin's tables could not be created on activation, the schema version is not recorded, an admin notice says so, and the install is retried automatically every 10 minutes or immediately on re-activation.

Dragon Activity Log

If Dragon Activity Log is active, Dragon Checkout Guard records script.authorised, script.revoked, script.changed, headers.changed and collector.tripped to it. script.changed and collector.tripped are recorded at the higher severity. The plugin's own event log works with or without it.

Data and privacy

The plugin stores its script inventory, headers baseline and forensic event log in its own database tables. None of it identifies a site visitor: there is no IP address, user-agent or session column anywhere in the schema, and the browser collector's per-address rate limit uses a salted hash of the address, kept in a transient for two minutes; on sites without a persistent object cache that transient lives in the options table for that time. Inline previews are stored with sensitive values redacted, and query parameters you list under Redacted query parameters are masked in the src shown throughout the admin.

External requests. The only outbound request the plugin makes to anywhere but your own site (scans also fetch your own payment pages) is wp_safe_remote_get() re-fetching a script's own URL to keep its content hash current for drift detection. That URL comes from your payment page's own markup, so it can point to your own domain or to a third party's CDN. Either way it is a plain GET of a public URL: no data about your site, its configuration or its visitors is attached to it, and nothing is ever sent to Dragon Core. There is no account and no telemetry.

A suggested privacy-policy paragraph describing exactly what is recorded is registered with WordPress's own Settings > Privacy tool (wp_add_privacy_policy_content), so you can add it to your site's privacy policy in one click.

Uninstall

Deleting the plugin keeps all its data by default, so a reinstall picks up where you left off. To remove everything on uninstall, tick Delete this plugin's tables, options and evidence when it is uninstalled in Settings first; this sets the dragoncheckoutguard_delete_data_on_uninstall option, which uninstall.php checks before dropping anything.

For developers

Actions:

HookFires when
dragoncheckoutguard_script_discoveredA script is recorded for the first time.
dragoncheckoutguard_script_changedA script's content hash drifted from the authorised one.
dragoncheckoutguard_script_authorisedA script is authorised.
dragoncheckoutguard_script_revokedAn authorisation is revoked.
dragoncheckoutguard_headers_changedA page's response headers drifted from the accepted baseline.
dragoncheckoutguard_scan_finishedA scan completed, with its summary.
dragoncheckoutguard_scan_failedA scan failed, or could not cover a page.
dragoncheckoutguard_collector_reportA browser collector report was accepted.
dragoncheckoutguard_collector_trippedThe collector paused on an observation limit.
dragoncheckoutguard_settings_savedSettings were saved.
dragoncheckoutguard_admin_tab_<slug>Renders a tab added through dragoncheckoutguard_admin_tabs.

Filters:

HookFilters
dragoncheckoutguard_in_scopeThe resolved page key for the current request, or null when it is out of scope.
dragoncheckoutguard_page_keysThe recognised page keys and their labels.
dragoncheckoutguard_admin_tabsThe admin tab list (slug => label).
dragoncheckoutguard_settings_fieldsExtra fields rendered in the "Additional settings" block.
dragoncheckoutguard_capture_intervalThe passive capture interval, in seconds.
dragoncheckoutguard_monitored_headersThe response headers captured and compared.
dragoncheckoutguard_redacted_paramsThe query parameter names masked for display.
dragoncheckoutguard_authorised_sourcesThe authorised-script hosts, shaped for a CSP script-src allowlist (inline hashes are passed as the second argument).