Content Security Policy (CSP) is an HTTP response header that tells the browser which origins are allowed to load scripts, styles, images, and other resources — blocking injected third-party scripts that steal payment card data even if the site is compromised via a cross-site scripting vulnerability or a supply-chain attack against a JavaScript dependency. PCI DSS 4.0 (effective 2025) explicitly requires CSP or equivalent controls on payment pages to prevent skimming attacks, making CSP implementation on WooCommerce checkout pages a compliance requirement, not just a best practice. Implementing CSP on WooCommerce requires careful inventory of every script, style, and third-party resource the checkout page loads — a missing origin in the policy causes the resource to be blocked and the checkout to break. Report-Only mode (Content-Security-Policy-Report-Only) sends violation reports to a designated endpoint without blocking any resources, allowing the policy to be tuned on a live site before enforcement begins. Subresource Integrity (SRI) adds a cryptographic hash to <script> and <link> tags — the browser refuses to execute the resource if its content does not match the expected hash, preventing tampered CDN assets from executing on the payment page. Nonce-based CSP is more robust than allowlist-based CSP because it authorises specific script elements rather than all scripts from a domain — WordPress 6.3+ outputs a CSP nonce for inline scripts when a filter provides one. Payment providers such as Stripe and PayPal serve their JavaScript from fixed CDN origins (js.stripe.com, www.paypal.com) that must be explicitly allowed in the script-src directive alongside your own domain. The frame-ancestors directive in CSP replaces the older X-Frame-Options header and prevents the checkout page from being embedded in an iframe on a phishing site — set it to 'none' unless you have a legitimate use for iframe embedding. The 2FA post covers the admin authentication layer that CSP on the front end complements — together they address both admin account takeover and checkout skimming threats. The security headers post explains the full suite of HTTP security headers that accompany CSP on a hardened WordPress installation — deploy all of them together for defence in depth. Use Content-Security-Policy-Report-Only with a free endpoint like report-uri.com for at least two weeks before switching to enforcement mode — checkout breakage due to a blocked payment script costs more than the time spent tuning the policy.
Problem: WooCommerce checkout pages without a Content Security Policy are vulnerable to JavaScript skimming attacks that inject malicious code to steal payment card data — a single compromised plugin or CDN asset can silently exfiltrate all submitted card numbers without triggering any server-side security controls.
Solution: Add a strict Content-Security-Policy header to WooCommerce checkout pages via PHP using WordPress hooks, start in Report-Only mode to identify violations, then enforce the policy and add Subresource Integrity hashes to third-party script tags.
// Add CSP header to checkout and payment pages only
add_action('send_headers', function() {
if (!function_exists('is_checkout') || !is_checkout()) return;
$nonce = base64_encode(random_bytes(16));
// Store nonce so wp_script_attributes filter can use it for inline scripts
define('MY_CSP_NONCE', $nonce);
$policy = implode('; ', [
"default-src 'self'",
"script-src 'self' 'nonce-{$nonce}' https://js.stripe.com https://www.paypal.com",
"style-src 'self' 'unsafe-inline'", // WooCommerce uses inline styles
"img-src 'self' data: https://www.paypalobjects.com",
"frame-src https://js.stripe.com https://www.paypal.com",
"connect-src 'self' https://api.stripe.com",
"frame-ancestors 'none'",
"form-action 'self'",
"base-uri 'self'",
"upgrade-insecure-requests",
]);
// Start in report-only mode: replace header name once policy is validated
header("Content-Security-Policy-Report-Only: {$policy}; report-uri /csp-report");
// Enforce: header("Content-Security-Policy: {$policy}");
// X-Frame-Options for older browsers that ignore CSP frame-ancestors
header('X-Frame-Options: DENY');
});
// Add the CSP nonce attribute to WordPress-enqueued inline scripts
add_filter('wp_inline_script_attributes', function(array $attrs): array {
if (defined('MY_CSP_NONCE')) {
$attrs['nonce'] = MY_CSP_NONCE;
}
return $attrs;
});
// Add SRI hash to a specific third-party script tag (example: lodash from CDN)
add_filter('script_loader_tag', function(string $tag, string $handle): string {
if ($handle !== 'my-cdn-lodash') return $tag;
return str_replace(
'<script ',
'<script integrity="sha384-PASTE_HASH_HERE" crossorigin="anonymous" ',
$tag
);
}, 10, 2);
NOTE: Generate SRI hashes for third-party scripts at srihash.org or with openssl dgst -sha384 -binary script.js | openssl base64 -A — regenerate the hash every time the third-party script version changes, because the hash binds to the exact bytes of that specific version.