GitHubnpm

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 });
  },
});