Security
Juice enables CSRF protection by default and provides CSP nonce support, client boundary enforcement, and production error suppression out of the box.
CSRF Protection
Cross-site request forgery protection is enabled by default for all POST requests and server actions. The runtime validates that the Origin header (or Referer fallback) matches the request's Host header. Mismatched origins receive 403 Forbidden.
Configuration
// Default: CSRF on (same-origin only)
createRouter(manifest);
// Allow additional trusted origins
createRouter(manifest, {
csrfProtection: {
allowedOrigins: ['https://admin.example.com'],
},
});
// Disable entirely (for public APIs or webhook endpoints)
createRouter(manifest, {
csrfProtection: false,
});Prototype Pollution Protection
The runtime's body parser uses safe parsing techniques. JSON payloads are parsed with standard JSON.parse() and never merged into prototype chains. Form data uses the web-standard FormData API, which is immune to prototype pollution.
Client Boundary Enforcement
The Juice compiler enforces a strict boundary between server and client code. Files without 'use client' at the top are server-only. They cannot be imported by client components and are never included in the client bundle. This prevents accidental leaking of secrets, database connections, or API keys to the browser.
Server actions ('use server') are extracted into separate modules and exposed only through opaque action IDs. The client never sees the action code.
CSP Nonce
When using streaming SSR, Juice injects inline scripts for error recovery, CSS bootstrapping, and React hydration. Pass a nonce to make these scripts Content Security Policy compliant:
createRouter(manifest, {
nonce: crypto.randomUUID(),
});Set your CSP header to match:
Content-Security-Policy: script-src 'nonce-<value>'The nonce is passed through to React's renderToReadableStreamso any scripts React injects during streaming also include the nonce attribute.
Production Error Suppression
In production mode (mode: 'production'), the runtime returns minimal error responses without stack traces or internal details. In development mode, rich HTML error overlays with stack traces are shown for faster debugging.
createRouter(manifest, {
mode: 'production', // default
onError: (err, req) => {
// Log internally but don't expose to clients
console.error('[app]', req.url, err);
return new Response('Something went wrong', { status: 500 });
},
});