I've known the founders of Aikido for a long time, so when I was exploring ways to expand TriFrost’s automated security testing (we already used GitHub CodeQL, but there’s always room for more), I reached out to them.
They got us set up quickly, and within minutes, Aikido flagged a single medium-severity issue, not in our code, but in our CI/CD setup 🙃, related to unpinned third-party modules. Resolved. ✅ Thanks Aikido!
Then I thought: Why not run a scan on our website? Boom, “Content Security Policy not set.” Fair. So I dug in 💪, and that sparked a small but meaningful improvement to our Security() middleware.
This patch brings enhanced Content Security Policy (CSP) handling via automatic nonce injection in the Security()
middleware, improving script safety, SSR support, and JSX ergonomics.
Added
- feat:
Security()
middleware now supports dynamic CSP nonce injection. You can specify"'nonce'"
as a placeholder value incontentSecurityPolicy
, and TriFrost will generate a secure, request-scopedctx.nonce
, replacing it across all matching directives:
.use(Security({
contentSecurityPolicy: {
[ContentSecurityPolicy.ScriptSrc]: ['"self"', "'nonce'"],
[ContentSecurityPolicy.StyleSrc]: ['"self"', "'nonce'"],
},
}));
This will automatically generate a secure base64 nonce and emit a valid CSP like:
Content-Security-Policy: script-src "self" 'nonce-AbC123...'; style-src "self" 'nonce-AbC123...'
- feat: A new
nonce
getter has been added to the TriFrost context, allowing easy access to the nonce (which is stored on ctx.state):
ctx.html(<script nonce={ctx.nonce}>...</script>);
- feat:
ctx.nonce
is now automatically linked to the JSX rendering engine and exposed through a nonce util, accessible anywhere inside a ctx.html() JSX component render:
import {nonce, Style} from '@trifrost/core';
export function Scripts () {
return <>
<script nonce={nonce()}>...</script>
<script nonce={nonce()}>...</script>
</>
}
export function Layout () {
return <html>
<head>
<title>Hello world</title>
<Scripts />
<Style /> {/* This one automatically checks nonce */}
<style type="text/css" nonce={nonce()}>...</style>
</head>
<body>
...
</body>
</html>;
}
export function myHandler (ctx) {
return ctx.html(<Layout />);
}
Improved
- feat: The
StyleEngine
now auto-injects a nonce attribute on its<Style />
tag whenctx.nonce
is active. This ensures all TriFrost CSS is CSP-compliant understyle-src "'nonce-...'"
policies — improving compatibility and SSR safety. Without having to lift a finger. - cicd: Pin 3rd party github actions in workflow ci
- deps: Upgrade @cloudflare/workers-types to 4.20250610.0
- deps: Upgrade @types/node to 22.15.31
- deps: Upgrade @vitest/coverage-v8 to 3.2.3
- deps: Upgrade typescript-eslint to 8.34.0
- deps: Upgrade vitest to 3.2.3
This change makes nonce-based CSP safer and easier, no manual nonce generation, no middleware coordination. Just drop 'nonce'
where you need it, adjust your inline scripts to work with nonce={nonce()}
and TriFrost takes care of the rest.
As always, stay frosty ❄️