Block injected scripts at the edge, even if PHP is compromised.
With code execution on your PHP origin, an attacker can add a wallet drainer or card skimmer to every page it serves. A Content-Security-Policy set at your CDN or reverse proxy still reaches the browser, and the browser refuses scripts from any host the policy doesn't list. Build one here, run it in Report-Only, then enforce it.
Runs in your browser. Nothing you type here is sent anywhere.
Pick a starting point, then trim it to what you use.
Content-Security-Policy-Report-Only
Warnings
About this preset
Where these hosts come from
What an edge policy stops, and what it doesn't.
Why a header set at the edge survives a compromised origin
When PHP is compromised, the attacker controls whatever the origin sends: the HTML, the scripts it serves, and its response headers. A header added by your CDN or reverse proxy is outside that reach, as long as the edge runs on a different system with different credentials. The browser gets your policy on every response either way.
The origin can't loosen it, either. A page can carry several policies at once (headers and <meta> tags), and every one of them is enforced: a resource has to be allowed by all of them, so adding a policy can only restrict further (MDN, CSP3 §8.1). On Cloudflare, a Set static response header rule goes further and overrides any header with the same name, so the origin's own value is replaced (Cloudflare docs).
So when an injected <script src="https://drainer.example/x.js"> or an inline <script> shows up in your page, a policy with script-src 'self' and no 'unsafe-inline' stops it from running. A tight connect-src also limits where a script that does run can send what it collects.
If the header comes from the same Apache that runs PHP, the attacker is on that server. At the very least set it in the virtual host, not in .htaccess, and set AllowOverride None and AllowOverrideList None, which makes Apache ignore .htaccess files entirely. Otherwise a dropped .htaccess can use Header, which is allowed there under FileInfo (Apache core, mod_headers). A CDN or a proxy on another machine is better.
What it can't do
- It allows by origin, not by content. Any script from a source you allow will run. If your JavaScript is served by the same PHP origin that was compromised,
'self'includes every file the attacker can write, from edited bundles to a new.jsin an upload directory. Serve static assets from a separate origin the PHP host can't write to, and keep upload directories from serving scripts. - It doesn't protect the server. Code running as your PHP worker can still read keys, database credentials and sessions. Patch, then rotate (see Fixes and Crypto & Solana).
- It doesn't stop changes to your own content. The attacker can still rewrite text, swap a deposit address in the HTML, or add a form that posts to your own origin.
- It doesn't cover browser extensions. The spec says a policy shouldn't interfere with add-ons and extensions (CSP3 §9.1), so wallet extensions run whatever your policy says.
- A static nonce is no nonce. A nonce must be freshly generated for every response and should be at least 128 bits (CSP3 §7.1). A header fixed at the edge can't do that, so use hashes for inline code you must keep.
- https:// and wss:// are listed separately. CSP upgrades
httptohttpsandwstowss, but anhttps://source doesn't match awss://URL (CSP3 §6.7.2.9).'self'does coverwss:on your own origin (CSP3 §1.3). - A <meta> tag is a weaker fallback. Your origin serves it, so a compromised origin can drop it. Browsers also ignore
frame-ancestors,report-uriandsandboxin it, and it doesn't apply to anything above it in the document (CSP3 §3.3).
Pin third-party scripts with Subresource Integrity
If you load a script from a CDN, add an integrity hash so the browser refuses the file if its contents change. A mismatch is treated as a network error, and the accepted algorithms are sha256, sha384 and sha512 (MDN). Generate the hash from the exact file you reviewed:
openssl dgst -sha384 -binary file.js | openssl base64 -A
Prefix the output with sha384- and add crossorigin="anonymous". Cross-origin SRI needs CORS, so the CDN has to send Access-Control-Allow-Origin; without crossorigin the request always fails.
<script src="https://cdn.example.com/lib@1.2.3/lib.min.js"
integrity="sha384-PASTE_THE_HASH_HERE"
crossorigin="anonymous"></script>To check a live file against the hash in your page, hash what the CDN serves right now and compare the two strings:
curl -s https://cdn.example.com/lib@1.2.3/lib.min.js | openssl dgst -sha384 -binary | openssl base64 -A
Pin a versioned URL, never a "latest" alias, or the hash breaks on the next release. Keep in mind what SRI covers: the integrity attribute lives in your HTML, so it protects you from a compromised CDN, not from a compromised origin that rewrites the tag. That's what the edge CSP is for.
Test with Report-Only before you enforce
Content-Security-Policy-Report-Only reports violations without blocking anything (MDN). It has to be a header; it isn't supported in a <meta> tag.
- Ship the policy with the Report-Only header name (switch the mode above).
- Click through every kind of page: home, login, checkout or wallet connect, account pages and anything embedded. Watch the browser console, or the endpoint you set as the report URL.
- For each violation, decide whether it's yours. Add hosts you use; move inline code into files or allow it by hash. Don't reach for
'unsafe-inline'inscript-src. - Switch to the enforcing header name once the reports are quiet. You can keep sending a Report-Only header with a stricter draft alongside the enforced one, since both are applied.
For reports to be sent anywhere, report-to names an endpoint defined in the Reporting-Endpoints header. report-uri is deprecated but still worth sending next to it until report-to works everywhere; browsers that support report-to ignore report-uri (MDN). The builder adds both when you fill in a report URL.
- Content Security Policy Level 3, W3C — w3.org/TR/CSP3 (source-list grammar §2.3.1, meta element §3.3, scheme matching §6.7.2.9, nonce reuse §7.1, multiple policies §8.1, extensions §9.1)
- Content-Security-Policy, MDN — developer.mozilla.org
- Content-Security-Policy-Report-Only, MDN — developer.mozilla.org
- base-uri, form-action and frame-ancestors have no default-src fallback, MDN — base-uri, form-action, frame-ancestors
- sandbox isn't supported in <meta> or Report-Only, MDN — developer.mozilla.org
- report-to and Reporting-Endpoints, MDN — developer.mozilla.org
- Subresource Integrity, MDN — developer.mozilla.org
- Cloudflare response header Transform Rules — developers.cloudflare.com; Cloudflare Pages
_headers— developers.cloudflare.com; Netlify_headers— docs.netlify.com - nginx add_header — nginx.org; Apache mod_headers — httpd.apache.org; Caddy header — caddyserver.com