Scan grades a site’s security headers. Early on I pointed it at its own web app and got a D. Fixing that was straightforward for five of the six headers, which are static strings. The Content-Security-Policy wasn’t, because Nuxt inlines a bootstrap script carrying the build id, and a policy strict enough to matter would block it and kill hydration. So Sentinel generates a fresh nonce per request in a render hook, stamps it onto every inline script tag, and emits a matching header. That works because Sentinel renders on every request. It also breaks the moment any route gains caching, since the cached HTML would carry one nonce and the header would carry another. Nothing caches today, so the failure is prevented by absence rather than by design.
The studio site is a different shape. It’s fully prerendered, deployed as static output with no serverless function behind it. There is no request-time process, so there is nothing to generate a nonce. Its CSP ships script-src 'self' 'unsafe-inline', and I wrote a comment above it saying that this is a deliberate constraint of static generation, not an oversight.
Point Scan at it and Scan disagrees. unsafe-inline in the script directive is a warn at medium severity, six points off the headers category, a bit over two off the overall. My own tool docks my own site for a thing I documented as unavoidable.
I went in expecting to write about that as a false positive, a limit of grading a header without knowing how the page is rendered. Then I checked what actually ships inline.
One executable inline script per page. Two hundred and forty eight bytes of Nuxt runtime config, byte-identical across all twenty three pages. Everything else that looks like a script tag is a JSON data block, which the browser never executes and script-src never gates. Nothing the site itself authors runs inline at all.
A single fixed script is exactly the case a hash covers. Static output doesn’t rule out sha256-, it rules out nonces, and I had written the second and reasoned as if I’d established the first. There’s a real obstacle, which is that the script contains a per-build id, so the hash changes every build and the header is a static string in config. That needs a post-build step to compute the digest and write it into the output. It’s work. It is not a constraint. My comment claims a constraint and the honest version is narrower: no nonce, true; no option, false.
The thing that actually can’t be fixed is the styles. Thirty style attributes ship in the prerendered HTML, plus eighty five kilobytes of inlined CSS. Nonces don’t apply to style attributes, so style-src would need unsafe-hashes or unsafe-inline no matter what I did to the scripts. That one is genuinely forced by the render model.
And style-src is the directive Scan doesn’t examine.
That was deliberate. An earlier version flagged unsafe-inline wherever it appeared in a policy, which wrongly warned github.com and stripe.com, both of which have clean script directives. I narrowed the check to read only the directive that governs scripts. Inline styles are a much weaker vector than inline scripts and that reasoning holds up. It’s the right call.
It also cleared Sentinel’s own CSP, which ships style-src 'unsafe-inline' too, and the commit message says so.
I don’t think I scoped the rubric to pass my own sites. The github and stripe false positives were real and I’d have made the same change without them. But I can’t demonstrate that, and there’s no test covering the CSP branches at all, so there’s nothing external that would have caught it going the other way either. What I’m left with is a rubric that flags the weakness I could remove with a build step and ignores the one I’m stuck with, and both of those boundaries were drawn by the person being graded.
The general shape: a scoring tool makes two claims, one loud and one silent. The loud one is the grade. The silent one is that everything outside the rubric didn’t need grading. The second claim never gets audited, because there’s no output to check it against. And the author of a rubric is the worst-placed person to notice when its edges happen to fall around them.
A rubric’s scope is a claim, and it’s the one claim the rubric can’t test. Check who benefits from where you drew the line.