Skip to content
Development

The CSP error that wasn't mine to fix

By Victor Da Luz
astrocloudflaresecuritydev-logsite

Deep Cut Atlas shipped to the App Store, and the site had a whole checklist waiting for exactly that moment: swap the “in development” copy, add a badge, wire up the pricing line, update the structured data. Most of it was mechanical. One part of it wasn’t, and the part that wasn’t turned out not to be a problem at all.

The setup

I’d already confirmed the app was live by fetching the actual App Store listing to get the real price ($4.99 for the one-time Pro unlock) rather than trusting the “free + Pro IAP” placeholder that had been sitting in the issue since before launch. Badge added, copy swapped, JSON-LD got an offers block and a screenshot array.

Type checker and formatter, both clean. The accessibility suite, 15 for 15. Then I opened the page in a real browser to look at it, and the console had a Content Security Policy violation on the exact script I’d just touched.

The part that looked like my bug

The error named the JSON-LD script directly: blocked by script-src, no hash, no nonce. I’d just edited that script’s contents, so the obvious read was that I’d broken the CSP. I went looking for what I’d done wrong.

I hadn’t done anything wrong. The site computes CSP hashes for inline scripts at build time, one hash per script, injected into the static _headers file that Cloudflare serves alongside the prerendered HTML. astro dev doesn’t run that step. It never has. Every prerendered page with an inline script has always shown this exact violation under plain dev, for any change at all, including no change.

The comment sitting right above the middleware’s CSP string says as much, if you read it before panicking.

What actually verifies it

The fix wasn’t a fix. It was building the site and serving the real output. npm run build triggers the hash injection, then wrangler dev against dist/server serves the same headers production gets. Same JSON-LD script, same page, zero console errors, because now the hash in the header matches the hash of what I’d shipped. That gap between dev and production is the same one that bit me from the other direction a while back.

I’d already run the full accessibility suite that way - it builds and serves through wrangler dev internally - and it had passed clean. That should have been my first signal that the dev-mode console error wasn’t real. I went and looked anyway, because a CSP violation on the line I’d just edited is exactly specific enough to feel like proof.

Lesson

When a check environment and a real environment disagree, work out which one is lying before you start fixing code. astro dev is missing a build step, not simulating production; a passing wrangler dev run against the actual build output is closer to what a visitor gets than anything astro dev shows. The dev server not saying so out loud is a recurring theme.

The tell was in a comment I’d already read once, earlier in the same repo, and forgotten by the time the error showed up in a different tab.

Related reading

Development

The CSP that only broke in production

Four failures behind one header: dead middleware, _headers rules that merge instead of override, a blob-URL worker under script-src, and a script only the edge injects.

Read