How a Simple CSP Tweak Turned into an AI Project

Introduction

In this post, I’ll tell the story of how a stray semicolon led me to build an AI-powered tool to make creating Content Security Policies (CSPs) a bit less painful for developers.

I’ve written about CSPs on this blog before:

To quote from my 2024 post:

The Content Security Policy (CSP) header is a security feature that protects web applications from attacks like Cross-Site Scripting (XSS) and code injection by controlling the sources from which browsers can load and execute content. If there's only a single response header you implement today, then make it the CSP Response header.

The post has a few more details, so if you are interested you can find the Content-Security-Policy (CSP) section here.

As mentioned in the post, there are already a huge number of tools available on the web for creating CSP’s, but none of them really fit my needs. So, I decided to write my own tool using cursor.ai and host it on the web for everyone to evaluate and use. Note: I've included my referral link so any readers can get 50% off their first month subscription.

It is called CSP Playground⁠. Despite the name, writing a CSP is rarely described as “fun”, and “playground” may be overselling the experience somewhat.

It seemed as sensible a name as any, though. Besides, the domain wasn’t taken and only cost £9, which is cheap enough to make almost any naming decision seem justified!

Using Cloudflare Registrar I got a quote for csp-playground.secuity, only $2000.20 per year!

I briefly considered splashing out on a .security top-level domain (TLD), until Cloudflare Registrar reminded me that the privilege would cost only £1,515 per year.

I suspect telling my wife that we’ve cancelled our summer holiday plans so I can own a particularly niche bit of the internet would have tested the “for richer or poorer” part of our vows rather more thoroughly than intended. 😬

Problem

If any readers have tried to write a CSP “manually”, you will understand how painful it really is! Understanding the very particular syntax is challenging. Although CSP itself does not have to be written on a single line conceptually, when the header is sent over the network, it must not contain any arbitrary line breaks, else it will be classed as invalid. And don’t even get me started on the use of ;, one misplaced semicolon and your browser console will throw a very red looking hissy fit!

There are whole RFC’s dedicated to the specifics of HTTP header fields, that I won’t even pretend to understand! Linked below for a little light reading, if you are struggling to sleep!

Anyway, as I always do, I’ve wandered off on a bit of a tangent.

What I’m basically saying is writing CSP’s is complicated and frustrating (especially for a terrible typist like myself!), So why not make life easier and spend a couple of days in "AI world” and build a tool to make your life easier? You never know, it may even help others too!

Features

  • Form-based editor for every standard CSP directive: No hand-editing of semicolon syntax
  • Import from URL: Fetch a policy from headers or meta tags, or detect when none exists
  • Paste headers or policy: Paste a full HTTP response header block or a single Content-Security-Policy line; the CSP is extracted in the browser and used to pre-fill the form
  • Real-time security score: Including actionable recommendations that navigate you instantly to the exact directive
  • Nonce and hash helpers: Generate values and copy ready-to-paste HTML snippets
  • One-click export: Policy, header line, or server config for Apache, Nginx, Caddy, Cloudflare, Netlify, Vercel, and more
  • Report-only mode: Option to output the CSP using Content-Security-Policy-Report-Only. Allowing for testing before enforcement
  • MDN links and inline help: Every directive and sandbox flag links directly to the relevant MDN content
  • Privacy focused by design: No accounts, no tracking, and no database. Nothing is stored because there’s nowhere to store it
  • Runs mostly in the browser: Policy editing and paste import stay local; the only server-side processing happens when you explicitly import a URL to fetch your site’s CSP
  • Free and open-source: MIT licensed at https://csp-playground.dev/, code available on GitHub
  • Clean design Built on the fantastic utopia.fyi Fluid Design CSS grid built by Clearleft

Audit

Start from a real policy instead of a blank form. Enter a URL and CSP Playground will retrieve the CSP from the response headers or a <meta> tag. Alternatively, you can paste the headers or the policy string directly. Everything stays in the browser, with the exception of URL lookups.

Whichever approach you choose, the editor is pre-populated so you can quickly see what is being enforced.

If the lookup finds problems (duplicates, deprecated directives, odd formatting), it flags them and suggests fixes.

For sites where there is no CSP at all, the tool does not leave a user at in the lurch: it points you to a short guide on why a policy matters and how to start safely, so auditing either an existing policy or a blank slate becomes the first step toward a stronger CSP.

Build

Whether you are starting from scratch or updating an existing policy, you can work through each CSP directive using a structured form. Add sources without worrying about syntax, generate (placeholder) nonces and hashes for inline scripts and styles, and copy ready-to-use HTML snippets.

Every directive links directly to MDN, so the documentation is always one click away when you need it.

Improve

As you edit, CSP Playground scores your policy in real time and highlights improvements you can make. Recommendations are practical, and show the expected score increase, simply click on the recommendation, and it takes you straight to the relevant directive. You can also see your potential score if you applied every suggestion. This makes it a whole lot easier to focus on the changes that matter most, even if you’ve never written a CSP before.

Deploy

When you are happy with your shiny new Content Security Policy, CSP Playground generates the full header and provides config snippets for web servers and hosting platforms:

You can also toggle a setting so that the server setup only attaches the CSP header to HTML responses, cutting down on repeated headers across all static assets, where they aren’t required.

Lastly, you can switch to Report-Only mode to test changes before enforcement, with links to MDN guidance to help you roll out your new (or updated) CSP safely.

Result

So, the first test for the tool was the CSP for this very website, after all it’s the reason I built it! As mentioned at the start of the post, the first result of my initial iteration of the tool caught a stray. It must have been live for quite a while, as I haven’t touched the CSP since February 2025. Thankfully, it wasn’t serious, hence why it went unnoticed. The validator picked it up and changed this:

Content-Security-Policy: base-uri 'self';child-src 'self';connect-src 'self' https://challenges.cloudflare.com/;default-src 'none';img-src 'self' https://v1.indieweb-avatar.11ty.dev/;font-src 'self';form-action 'self' https://webmention.io;frame-ancestors 'none';frame-src 'self' https://player.vimeo.com/ https://www.slideshare.net/ https://www.youtube.com/ https://giscus.app/ https://challenges.cloudflare.com/;manifest-src 'self';media-src 'self';object-src 'none';script-src 'self' 'inline-speculation-rules' https://ajax.cloudflare.com https://giscus.app/ https://challenges.cloudflare.com/;script-src-elem 'self' 'inline-speculation-rules' https://ajax.cloudflare.com https://giscus.app/ https://challenges.cloudflare.com/;style-src 'self' 'unsafe-inline' https://giscus.app/;worker-src 'self';

To this:

Content-Security-Policy: base-uri 'self';child-src 'self';connect-src 'self' https://challenges.cloudflare.com/;default-src 'none';img-src 'self' https://v1.indieweb-avatar.11ty.dev/;font-src 'self';form-action 'self' https://webmention.io;frame-ancestors 'none';frame-src 'self' https://player.vimeo.com/ https://www.slideshare.net/ https://www.youtube.com/ https://giscus.app/ https://challenges.cloudflare.com/;manifest-src 'self';media-src 'self';object-src 'none';script-src 'self' 'inline-speculation-rules' https://ajax.cloudflare.com https://giscus.app/ https://challenges.cloudflare.com/;script-src-elem 'self' 'inline-speculation-rules' https://ajax.cloudflare.com https://giscus.app/ https://challenges.cloudflare.com/;style-src 'self' 'unsafe-inline' https://giscus.app/;worker-src 'self'

It took me a little while to spot the difference because the validator simply reported a "semicolon change". If you are able to notice these small changes straight away, congratulations, you’re more observant than I am!

For any readers wondering the only difference is that the trailing semicolon in the second version has been removed. It’s actually entirely optional and has no effect on how the CSP behaves.

I admit it’s hardly a groundbreaking discovery, but it was reassuring to see that the validation functionality was doing exactly what it should.

The next changes it recommended made more of an impact and improved my CSP score. The initial score was 79%. Not bad, but we can do better with only 2 minor changes:

trusted-types (TT)

I must admit I’d never heard of this directive until the tool recommended it.

It's only very recently (February) that it has been marked as Baseline in the MDN documentation due to broad cross browser support across all modern browsers.

It was actually Mozilla who were dragging their feet on this directive, but thankfully changed their stance on it back in 2023. Full support in Firefox was released in version 148 (released 24 February 2026).

If you’re curious about this directive, here’s an ELI5 explanation:

  • Think of an Aeroplane at an airport.
  • Before TT: Anybody could walk straight onto the plane.
  • After TT: Everybody must:
    1. Go through security.
    2. Have their passport checked.
    3. Receive an approved boarding pass.

Only passengers (or potentially executable content in this analogy) with a valid boarding pass are allowed onto the plane.

Trusted Types are the boarding pass.

For readers who are older than 5, you can read all about the Trusted Types API in great depth on MDN.

upgrade-insecure-requests

This directive is pretty self-explanatory given the name. upgrade-insecure-requests instructs a browser to automatically upgrade HTTP requests to HTTPS before loading resources. Resources that do not support HTTPS will fail to load, although this is very rare on the modern web.

By simply adding:

trusted-types 'none'; upgrade-insecure-requests

to my CSP, it bumped the score from 79% up to 87%!

A respectable gain for what was, in the grand scheme of things, barely any work!

Adding upgrade-insecure-requests improves protection against mixed content by upgrading HTTP resources to HTTPS. Meanwhile, trusted-types 'none' does not actually enforce Trusted Types, but it does make the decision to not use them very explicit.

For the moment I have simply disabled the use of Trusted Types in the CSP, but in the future I will likely enable enforcement via the use of:

require-trusted-types-for 'script'; trusted-types default;

Once enforced this will bring the sites CSP score up to 95%! 🎉

Update: I tested require-trusted-types-for 'script'; with 'trusted-types default;, but it breaks a few scripts across the site, so have reverted to trusted-types 'none';. I will update once I have resolved the issues. It just goes to show how much testing you need to do when you change your site's CSP! Use Content-Security-Policy-Report-Only if in doubt!

Summary

I promised myself that I wouldn’t make this blog post an epic read this time, so fingers crossed I have kept my promise! If you happen to be interested in my “strategy” and opinion (for what it's worth) on AI assisted coding off the back of this post, I’ve wrote all about it here in my FractalAI: Generating Infinity in the Browser post from April.

Lastly, you can find CSP Playground here, and the GitHub repo for the code here. PRs and Issues welcome!

I hope you find this little tool useful! I’ve enjoyed building it and learning a little more about the world of CSPs, and Trusted Types.

As always, thanks for reading, feedback and comments are welcome. You can contact me here.


Post changelog:

  • 20/06/26: Initial post published.
  • 20/06/26: Reverted require-trusted-types-for 'script'; with trusted-types default; to trusted-types 'none'; because it broke a number of scripts on the site!

Webmentions

No mentions yet.

A webmention is a way to notify this site when you've linked to it from your own site.