A Content Security Policy is the single most useful thing you can add to a checkout page to blunt a skimmer, and most sites do not have one. It is a header the server sends with the page that tells the browser, in advance, which places the page is allowed to load code from and, crucially, which places it is allowed to send data to. A skimmer that manages to run can still be stopped at the last step, when it tries to post the stolen card details to a server it controls and the browser refuses.
That last point is the one people miss. Most of the attention on CSP goes to script-src, controlling what can run. That matters, but a determined skimmer often arrives inside a script you already allow. The clause that saves you is connect-src, which governs where the page may send data. Get that right and the theft fails at the exfiltration step even when the skimmer is running.
A policy you can actually deploy
The mistake that kills most CSP projects is starting too strict, breaking the site, and switching it off. Start the other way round. Ship the policy in report-only mode first, so the browser tells you what it would have blocked without blocking anything, then tighten from real data.
The header to begin with is Content-Security-Policy-Report-Only. Give it a connect-src listing your own domain and the genuine endpoints your checkout talks to, your payment provider and your analytics, and a report-uri or report-to pointing at somewhere that will collect the violation reports. Leave it running for a week. The reports will show you every destination the page currently reaches, which is almost always more than you expected, and some of which you will not recognise. That list is the real inventory of your payment page, and building it is worth the exercise on its own.
Once the reports are quiet and you trust the list, move the same policy to the enforcing Content-Security-Policy header. Keep the reporting endpoint attached. A skimmer that is injected later will try to reach a host that is not on the list, the browser will block it, and the attempt will land in your reports as an early warning.
The parts that matter
Keep connect-src tight. This is the clause doing the security work. Every host on it is a place your customers’ data is allowed to go. If a marketing tag wants to add three more, that is a conversation worth having before you agree.
Avoid unsafe-inline in script-src if you possibly can. It is the setting that undoes most of the protection, because it lets any inline script run. If your stack needs it, that is a sign the checkout has inline scripts that should be moved into files you can control and pin.
Do not allow a whole CDN when you mean one file. Allowing *.some-cdn.com because one library lives there also allows every other customer of that CDN. Name the exact origin, and pair it with Subresource Integrity so the specific file cannot change underneath you.
Watch the reports after every deployment. A sudden new violation from an unfamiliar host is exactly the signal you built this to catch. Route the reports somewhere a person will see them, not a log nobody reads.
What it does not do
A CSP is a strong last line, not a complete defence. It will not stop a skimmer that exfiltrates through a host you have allowed, which is why a compromised legitimate supplier is still dangerous. It does not remove the need to patch, to reduce the number of scripts on the page, or to move the card fields out of your document entirely where you can. Treat it as one control in a set, the one that turns a successful injection into a failed theft, and it earns its place many times over.
The short version
A Content Security Policy lets the browser refuse to send card data anywhere you did not approve. Start in report-only mode to learn where your page really connects, tighten connect-src to the short list of hosts that belong there, then enforce it and keep watching the reports. It will not prevent every injection, but it turns the ones it catches into nothing.
