Quickback Docs

Security Headers

Configure CSP, COOP, COEP, CORP, X-Frame-Options, HSTS, Referrer-Policy, and Permissions-Policy on every response from your generated Worker.

Every compiled Quickback Worker sets a baseline of security response-headers on every response: HSTS, X-Content-Type-Options: nosniff, a clickjacking CSP (frame-ancestors 'self' …TRUSTED_ORIGINS), X-Frame-Options: SAMEORIGIN, Referrer-Policy, and Permissions-Policy. Adding the security block lets you override individual headers, ship a fuller Content-Security-Policy, or suppress them all if an upstream gateway handles headers.

Default behavior

Omit security entirely → the baseline above. No config change needed for clickjacking + HSTS protection.

quickback.config.ts
import { defineConfig } from '@quickback/compiler';

export default defineConfig({
  name: 'my-api',
  // ...
  // security defaults are applied automatically.
});

Suppress every security header

For Workers fronted by a gateway that owns headers:

quickback.config.ts
export default defineConfig({
  // ...
  security: false,
});

The compiler emits a no-op middleware comment so the route table still lines up with the rest of the generated code, but no c.header(...) calls fire.

Per-header overrides

Each field on the security block accepts either a typed value or false to suppress that specific header. Defaults still apply for fields you don't set.

quickback.config.ts
export default defineConfig({
  // ...
  security: {
    // Suppress just CSP — keeps HSTS, referrer, permissions, etc.
    csp: false,

    // Or override individual fields:
    coop: 'same-origin',
    coep: 'credentialless',
    referrerPolicy: 'no-referrer',
    permissionsPolicy: { geolocation: [], microphone: [], camera: [], payment: [] },
    hsts: { maxAge: 63072000, includeSubDomains: true, preload: true },
  },
});

Content Security Policy

security.csp accepts a typed object. Each *Src field is an array of source expressions. The compiler accepts both the @-prefixed sentinels (idiomatic, type-safe) and CSP wire-form keywords ("'self'", "'unsafe-inline'"); both normalize to the same output.

Sentinels

SentinelWire formNotes
@self'self'Same-origin.
@none'none'Empty-set keyword.
@unsafe-inline'unsafe-inline'Allows inline scripts/styles — defeats most XSS mitigation when used in script-src.
@unsafe-eval'unsafe-eval'Allows eval() and friends.
@trustedOriginsThe …TRUSTED_ORIGINS constant at runtimeResolves to auth.trustedOrigins as configured in quickback.config.ts.
@nonce'nonce-…' per requestReserved for the per-request nonce middleware shipping in 0.10.15. Using this in 0.10.14 is a compile-time error.

Unknown @… tokens are rejected at compile rather than silently stringified into the wire CSP.

Example: paste-in CSP for a Quickback SPA project

The bundled CMS and Account SPAs load Google Fonts cross-origin, so the strict 'self'-only CSP would white-screen them on load. This config is a known-working starting point for Quickback projects with the bundled SPAs:

quickback.config.ts
export default defineConfig({
  // ...
  security: {
    csp: {
      defaultSrc: ['@self'],
      // Tailwind dynamic styles + bundled Google Fonts CSS need
      // unsafe-inline + the gstatic / googleapis hosts. Tightening
      // styles to nonces ships in 0.10.15.
      styleSrc: ['@self', '@unsafe-inline', 'https://fonts.googleapis.com'],
      scriptSrc: ['@self'],
      fontSrc: ['@self', 'data:', 'https://fonts.gstatic.com'],
      imgSrc: ['@self', 'data:', 'blob:', 'https:'],
      connectSrc: ['@self', '@trustedOrigins'],
      frameAncestors: ['@self', '@trustedOrigins'],
      formAction: ['@self'],
      baseUri: ['@self'],
      objectSrc: ['@none'],
      upgradeInsecureRequests: true,
    },
  },
});

Troubleshooting CSP violations

If the browser console shows CSP errors after enabling security.csp, match the directive named in the error against this table — it maps the failure mode back to the directive that needs widening.

Console errorDirectiveWhy it happensFix
Loading the stylesheet 'https://fonts.googleapis.com/...' violates 'style-src ...'styleSrcBundled CMS/Account SPAs <link> to Google Fonts CSS.Add 'https://fonts.googleapis.com' to styleSrc.
Refused to load the font 'https://fonts.gstatic.com/...' because it violates 'font-src ...'fontSrcThe CSS file from googleapis loads .woff2 files from gstatic.Add 'https://fonts.gstatic.com' to fontSrc.
Connecting to 'https://api.your-domain/...' violates 'connect-src 'self''connectSrcAPI on a sibling subdomain (e.g. SPA on app.example.com, API on api.example.com) — 'self' only covers exact origin.Use connectSrc: ['@self', '@trustedOrigins'] so the same allowlist cors() enforces gets reused.
Refused to apply inline style because it violates 'style-src ...'styleSrcTanStack Router / Tailwind's style="..." attributes are inline.Add '@unsafe-inline' to styleSrc until 0.10.15 ships per-request nonces.
Refused to load the image '...' because it violates 'img-src ...'imgSrcUser-uploaded avatars / R2-hosted assets / data: URIs from blob previews.Widen imgSrc to ['@self', 'data:', 'blob:', 'https:'] (or list specific hosts).
Refused to connect to 'wss://...' because it violates 'connect-src ...'connectSrcRealtime / Durable Object WebSockets.The @trustedOrigins sentinel covers HTTPS only — add the explicit wss:// origin if your DO lives elsewhere.

The general rule: 'self' means exact origin (scheme + host + port). Anything cross-origin needs an explicit host or the @trustedOrigins sentinel. The paste-in recipe above is a known-good baseline for a stock Quickback project — start there and tighten, rather than starting from 'self'-only and chasing console errors.

Auto-derivations

The compiler applies these so config stays minimal:

  • csp.frameAncestors defaults to ['@self', '@trustedOrigins'] when csp is set but frameAncestors is omitted. Same allowlist as CORS.
  • X-Frame-Options derives from csp.frameAncestors (['@none']DENY, contains @selfSAMEORIGIN, otherwise omitted — XFO can't represent arbitrary origins so CSP becomes the truth there). Override with security.frameOptions.
  • corp: 'same-origin' is auto-emitted when coep is set. require-corp without CORP breaks the ASSETS binding fetches on Cloudflare; the compiler couples them so users don't have to remember.

CSP auto-config (0.10.15+)

When you opt into a csp object, the compiler additively widens directives based on which Quickback features your project has enabled. Additive means anything you already wrote stays; we only push values that aren't already present.

TriggerAuto-addedTo directiveWhy
cms: true OR account: true'unsafe-inline'styleSrcTailwind / TanStack Router emit inline style= attributes
cms: true OR account: truehttps://fonts.googleapis.comstyleSrcBundled SPAs <link> to Google Fonts CSS
cms: true OR account: truehttps://fonts.gstatic.comfontSrcThe googleapis CSS loads .woff2 from gstatic
csp set (always)@trustedOriginsconnectSrcXHR/fetch must reach the same allowlist cors() permits — covers cross-subdomain APIs without re-listing them
cms: true OR account: truehttps://${domain} for each cms.domain / account.domain / domain / inferred unified domainconnectSrcThe Vite bundle hardcodes VITE_QUICKBACK_API_URL at compile time, so the SPA fetches that exact URL even when the page is served from a different origin (e.g. localhost dev hitting prod-baked SPA)

Scaffold rule: if you set defaultSrc but omit the target directive (e.g. you wrote defaultSrc: ['@self'] and nothing for connectSrc), auto-config creates the new directive seeded with your defaultSrc values plus the addition. defaultSrc itself stays clean. So defaultSrc: ['@self'] + auto-config produces:

default-src 'self'
style-src   'self' 'unsafe-inline' https://fonts.googleapis.com
font-src    'self' https://fonts.gstatic.com
connect-src 'self' @trustedOrigins https://api.your-app.com

Each directive is scoped to exactly what it needs. defaultSrc is not widened with style-only or connect-only hosts — that would let those hosts also reach script-src / img-src / media-src via fall-back, which is wider than the bundled SPA actually needs. (This rule changed in 0.10.17 — earlier versions incorrectly pushed additions into defaultSrc.)

Visibility: every applied addition prints to stderr at compile time — one line per addition with the directive, value, and reason. No silent magic. Loop's compile output now looks like:

[security] auto-added 'unsafe-inline' to csp.styleSrc — account: true → bundled Account SPA → inline style attributes from Tailwind / TanStack Router
[security] auto-added https://fonts.googleapis.com to csp.styleSrc — account: true → bundled Account SPA → loads Google Fonts CSS cross-origin
[security] auto-added https://fonts.gstatic.com to csp.fontSrc — account: true → bundled Account SPA → woff2 font files served from gstatic.com
[security] auto-added @trustedOrigins to csp.connectSrc — CORS-trusted origins must be reachable from XHR/fetch (matches cors() allowlist)

Opt-out: security: { cspAutoConfig: false, csp: { ... } } for projects fronted by a CDN that injects its own CSP, or teams that want strict from-scratch control. With auto-config off, every value above must be authored by hand.

With auto-config on, the paste-in recipe above becomes much shorter — most users only need:

security: {
  csp: {
    defaultSrc: ['@self'],
    scriptSrc: ['@self'],
    imgSrc: ['@self', 'data:', 'blob:', 'https:'],
    objectSrc: ['@none'],
    upgradeInsecureRequests: true,
  },
}

The compiler fills in the SPA-specific font hosts, inline-style allowance, and trusted-origin connect rules from the project's own feature flags.

Cross-origin window/embedder isolation

quickback.config.ts
security: {
  coop: 'same-origin',          // Defends against window.opener back-references from popups.
  coep: 'credentialless',       // Required for SharedArrayBuffer / cross-origin isolation.
  // corp auto-emits as 'same-origin' here.
}

HSTS

Default: max-age=31536000; includeSubDomains; preload (HSTS preload-list eligible).

quickback.config.ts
security: {
  hsts: { maxAge: 63072000, includeSubDomains: true, preload: true },
}

The generated middleware automatically suppresses HSTS on localhost, 127.0.0.1, and *.localhost regardless of this config. Without that gate, wrangler dev would pin the developer's browser to HTTPS for maxAge seconds, which is observable across every project on that machine.

To submit to the HSTS preload list, you need:

  • maxAge ≥ 31536000 (one year)
  • includeSubDomains: true
  • preload: true

The compiler warns at compile time if preload: true without the other two — the preload list rejects either anyway.

Permissions Policy

Map of feature name → list of allowed origins.

quickback.config.ts
security: {
  permissionsPolicy: {
    geolocation: [],                        // disabled for everyone
    microphone: ['self'],                   // allowed only on own origin
    camera: ['self', 'https://meet.example.com'],
    payment: [],
  },
}

The compiler encodes the wire syntax (which differs from CSP — values use (), not space-separated tokens, with bareword self and quoted URLs).

Compile-time lint rules

The compiler walks the resolved config and emits warnings (or errors) for known footguns. Errors block compile; warnings print to stderr.

CodeSeverityTrigger
csp/nonce-not-yet-supportederror@nonce sentinel used in 0.10.14. Per-request nonce middleware ships in 0.10.15.
csp/unsafe-inline-with-nonceerror@unsafe-inline and @nonce in the same directive (CSP3 silently ignores unsafe-inline when nonce is present).
csp/empty-resolved-directiveerror*Src array contains only @trustedOrigins and auth.trustedOrigins is empty — would emit a directive Chrome reads as 'none'.
csp/unsafe-inline-scriptwarn@unsafe-inline in scriptSrc defeats most XSS mitigations.
csp/open-sourcewarnWildcard * or scheme-only https: / http: in any *-src.
csp/unsafe-eval-with-datawarn@unsafe-eval combined with any data: source — trivial bypass.
csp/missing-default-srcwarnOther *-src set without defaultSrc.
csp/upgrade-with-httpwarnupgradeInsecureRequests: true plus an http:// source elsewhere.
csp/report-uri-onlywarnreportUri set without reportTo (deprecated CSP2 path).
hsts/preload-needs-long-max-agewarnpreload: true with maxAge < 31536000.
hsts/preload-needs-include-subdomainswarnpreload: true without includeSubDomains.

Verification

Spin up the deployed Worker (or wrangler dev) and check the response headers:

curl -sI https://my-api.example.com/health \
  | grep -E '^(content-security-policy|x-frame-options|strict-transport-security|cross-origin-opener-policy|cross-origin-embedder-policy|cross-origin-resource-policy|referrer-policy|permissions-policy):'

Use securityheaders.com for an external grade — Quickback's defaults are A on the baseline and A+ once a fuller csp is configured.

On this page