TriFrost

TriFrost 0.29.0

|peterver

News

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 in contentSecurityPolicy, and TriFrost will generate a secure, request-scoped ctx.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 when ctx.nonce is active. This ensures all TriFrost CSS is CSP-compliant under style-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 ❄️

Loved the read? Share it with others