Skip to main content
PCI Monitor

Content Security Policy (CSP) for payment pages

Defenses · 7 min read · Last reviewed: October 17, 2026

A Content Security Policy is an HTTP header that tells the browser which sources of scripts, frames, images and connections a page may use, and where it may send data. On a payment page a strict policy based on nonces or hashes can stop injected skimmers from running and limit where stolen data can go. Allow-lists of hosts, 'unsafe-inline' and wildcards weaken it badly, and it needs testing in report-only mode before enforcement.

What a Content Security Policy does

Content Security Policy (CSP) is a browser security feature, switched on by sending a Content-Security-Policy header with the page. The policy is a list of directives, each naming the kinds of resource it controls and the sources allowed. If the page tries to load or run something the policy does not allow, the browser blocks it and can report the attempt.

For a payment page the value is twofold. It can stop a skimmer from running, by refusing script from a source you did not approve, and it can stop a skimmer from sending data out, by limiting where the page can make requests, load images and submit forms. It is therefore both a prevention control and, with reporting, a source of signals. It does not replace fixing the underlying flaw; see XSS and injected skimmer code.

The directives that matter for payment pages

DirectiveControlsWhy it matters
script-srcWhere scripts may be loaded from, and whether inline scripts may run.The core control against injected script.
connect-srcAddresses the page may contact with fetch, XHR, WebSocket and similar.Limits where a skimmer can send stolen data.
form-actionWhere forms on the page may be submitted.Stops a form being redirected to an attacker.
img-srcWhere images may load from.Image requests are a common way to send data out.
frame-src and frame-ancestorsWhat the page may embed, and who may embed the page.Controls payment iframes and clickjacking.
base-uriThe base address for relative links.Stops an injected <base> tag from redirecting script paths.
object-srcPlugins and embedded objects.Usually set to 'none'.

script-src: allow-lists, nonces, hashes and strict-dynamic

Allow-lists of hosts: easy but fragile

The simplest policy lists the hosts scripts may come from. It is weak in practice: any script on an allowed host runs, so one allowed host that serves user-supplied content, an open redirect or a large library collection can be abused to run an attacker's code. Long lists are also hard to keep accurate.

Nonces

A nonce is a random value generated for every response and placed both in the header and on each legitimate <script> tag. The browser runs only scripts that carry the nonce. It must be unpredictable and fresh on every response, at least 128 bits long before encoding according to the CSP specification. Nonces need pages that are generated per request; a page cached and served identically to everyone cannot use them.

Hashes

A hash lists the exact content of an allowed script, so the browser runs a script only if its hash matches. Hashes suit static inline scripts that change rarely.

'strict-dynamic'

With 'strict-dynamic', a script that is trusted through a nonce or hash may load further scripts, and those inherit trust without being listed. In browsers that support it, host allow-lists are then ignored. It works well with tag managers, but it also means anything the trusted script loads is trusted, so the governance of that script matters; see tag manager risk.

What defeats a policy

  • 'unsafe-inline' allows any inline script, including an injected one, and removes most of the protection. When a nonce or hash is present, modern browsers ignore it, which is why it is sometimes included as a fallback for old browsers.
  • 'unsafe-eval' allows code to be built from strings.
  • Wildcards such as *, https: or a broad domain pattern allow far more than intended.
  • data: as a script source allows script to be embedded in a link.

Rolling out safely: report-only mode and reporting

A new policy almost always blocks something legitimate. Send it first as Content-Security-Policy-Report-Only: the browser does not block anything but reports each violation. Review the reports, fix the policy or remove the unneeded script, and enforce when the reports are quiet.

Reports are sent to an address you name. The older report-uri directive is deprecated in favor of report-to with a named endpoint set in the Reporting-Endpoints header, but browser support differs, so many sites send both for now. Check MDN for current support.

An illustration, not a policy to copy

Content-Security-Policy:
  default-src 'none';
  script-src 'nonce-{random-per-response}' 'strict-dynamic';
  connect-src 'self' https://payments.example-provider.com;
  frame-src https://payments.example-provider.com;
  img-src 'self';
  style-src 'self';
  form-action 'self';
  base-uri 'none';
  object-src 'none';
  frame-ancestors 'none'

A real policy has to be built from what your payment page actually needs; Google's CSP Evaluator and the guidance on strict CSP at web.dev are useful for checking it.

CSP and PCI DSS

A CSP can serve as part of the authorization method for requirement 6.4.3, because it enforces which sources may run, and it supports the exfiltration limits that make a skimmer less useful. It does not, by itself, show that an allowed file has not been altered, so it is usually paired with an integrity measure such as Subresource Integrity. And because the policy is itself an HTTP header, requirement 11.6.1 expects you to be alerted if it is changed without authorization. See PCI DSS 6.4.3 explained and HTTP security headers for payment pages.

Key takeaways

  • A CSP can stop injected script from running and limit where a page sends data, which makes it one of the strongest browser-side controls.
  • script-src with nonces or hashes (and optionally strict-dynamic) is far stronger than a list of allowed hosts.
  • 'unsafe-inline', 'unsafe-eval', wildcards and data: sources weaken or defeat a policy.
  • Roll out in report-only mode, review reports, then enforce.
  • A CSP supports 6.4.3 but does not replace integrity checks, and as a header it should itself be monitored under 11.6.1.

Frequently asked questions

What does unsafe-inline do in a CSP?

It allows any inline script and inline event handler to run, including one an attacker injects, which removes most of the protection against XSS and skimmers. When a policy also has a nonce or a hash, modern browsers ignore unsafe-inline.

Should I use nonces or hashes?

Use nonces when pages are generated for each request, because a fresh nonce per response is the most flexible. Use hashes for static inline scripts that rarely change. Both are stronger than allow-listing hosts.

Does a Content Security Policy stop skimmers?

It can block a skimmer that loads from an unapproved source and can stop it sending data to an unapproved address. It cannot stop one that arrives inside an allowed script or from an allowed host, which is why it is combined with integrity checks, an inventory and monitoring.

Is report-only mode safe to use on a live payment page?

Yes. In report-only mode the browser enforces nothing; it only reports. That makes it the usual way to learn what a policy would block before you turn enforcement on.

References

Last reviewed: October 17, 2026