Skip to content

Content Security Policy

How τjs generates and manages CSP headers for your routes.

τjs provides CSP middleware that generates nonce-based Content Security Policy headers and exposes the nonce to your rendering pipeline. This allows inline scripts to execute safely while blocking unauthorised code.

Configure CSP globally in your τjs config:

taujs.config.ts
export default defineConfig({
security: {
csp: {
directives: {
"default-src": ["'self'"],
"script-src": ["'self'"],
"style-src": ["'self'", "'unsafe-inline'"],
"img-src": ["'self'", "data:", "https:"],
},
},
},
});

τjs automatically:

  1. Generates a unique nonce per request
  2. Adds 'nonce-<value>' to script-src if not present
  3. Applies the header to all responses
  4. Passes nonce to React’s rendering pipeline

For each request, τjs:

// Internal - τjs does this automatically
const nonce = crypto.randomBytes(16).toString('base64');
// Adds to script-src
'script-src': ["'self'", "'nonce-abc123...'"]
// Sets header
Content-Security-Policy: script-src 'self' 'nonce-abc123...'
// Passes to the renderer, in the render options
renderStream(writable, callbacks, data, location, modules, meta, signal, { cspNonce: nonce });

The nonce is automatically applied to:

  • the renderer’s own script output - React’s renderToPipeableStream nonce option, Vue’s injected scripts, Solid’s hydration script
  • window.__INITIAL_DATA__ script
  • Client bootstrap script

You do not add nonces manually - τjs handles this.

τjs manages the policy for the pages it renders. If the host applies its own CSP, or any other response header, the hook it uses decides how reliably it is applied. Use onRequest for an unconditional policy like this one, or preHandler when the policy depends on the route or on authentication having succeeded. onSend reaches streamed responses as well, but a pre-byte streaming failure gives it a second pass, so an unconditional security header belongs earlier. See Response policy and lifecycle hooks for the verified matrix.

τjs automatically relaxes CSP for development:

// Your config
{
directives: {
'script-src': ["'self'"],
'style-src': ["'self'"]
}
}
// τjs adds in development
{
'script-src': ["'self'", "'nonce-...'"],
'connect-src': ["'self'", 'ws:', 'http:'], // For Vite/HMR
'style-src': ["'self'", "'unsafe-inline'"] // For hot styles
}

In production, only your directives plus nonce are used:

// Your config
{
'script-src': ["'self'"],
'style-src': ["'self'"]
}
// Result in production
{
'script-src': ["'self'", "'nonce-...'"],
'style-src': ["'self'"]
}

If you ship with development-style directives, τjs logs a warning.

Override or extend CSP for specific routes:

Route directives are merged with global directives:

// Global config
security: {
csp: {
directives: {
'default-src': ["'self'"],
'script-src': ["'self'"]
}
}
}
// Route config
{
path: '/embed',
attr: {
render: 'ssr',
middleware: {
csp: {
directives: {
'frame-ancestors': ["'self'", 'https://trusted.com']
}
}
}
}
}
// Result for /embed
{
'default-src': ["'self'"],
'script-src': ["'self'", "'nonce-...'"],
'frame-ancestors': ["'self'", 'https://trusted.com']
}

Replace global directives entirely:

{
path: '/widget',
attr: {
render: 'ssr',
middleware: {
csp: {
mode: 'replace',
directives: {
'default-src': ["'self'"],
'script-src': ["'self'", 'https://cdn.example.com'],
'style-src': ["'self'", "'unsafe-inline'"]
}
}
}
}
}

Generate directives based on request parameters:

{
path: '/user/:id',
attr: {
render: 'ssr',
middleware: {
csp: {
directives: ({ params, headers }) => ({
'img-src': [
"'self'",
`https://cdn.example.com/users/${params.id}/`
],
'connect-src': ["'self'", 'https://api.example.com']
})
}
}
}
}

Function receives:

  • params - Route parameters
  • headers - Request headers
{
path: '/legacy',
attr: {
render: 'ssr',
middleware: {
csp: false // No CSP header at all
}
}
}

Use when:

  • Legacy HTML that can’t work with CSP
  • Third-party widgets with inline scripts
{
path: '/report',
attr: {
render: 'ssr',
middleware: {
csp: {
disabled: true // Skip route overrides, use global only
}
}
}
}

Test CSP without blocking:

export default defineConfig({
security: {
csp: {
directives: {
"default-src": ["'self'"],
"script-src": ["'self'"],
},
reporting: {
reportOnly: true,
},
},
},
});

Header sent: Content-Security-Policy-Report-Only

{
path: '/experimental',
attr: {
render: 'ssr',
middleware: {
csp: {
reportOnly: true,
directives: {
'script-src': ["'self'", "'strict-dynamic'"]
}
}
}
}
}
export default defineConfig({
security: {
csp: {
directives: {
"default-src": ["'self'"],
"script-src": ["'self'"],
},
reporting: {
endpoint: "/api/csp-violations",
onViolation: (report, req) => {
console.log("CSP violation:", {
documentUri: report["document-uri"],
violatedDirective: report["violated-directive"],
blockedUri: report["blocked-uri"],
});
},
},
},
},
});
reporting: {
endpoint: '/api/csp-violations',
onViolation: (report, req) => {
const violation = report['csp-report'];
// Log to monitoring service
logger.warn({
event: 'csp_violation',
documentUri: violation['document-uri'],
directive: violation['violated-directive'],
blockedUri: violation['blocked-uri'],
userAgent: req.headers['user-agent']
});
// Alert on specific violations
if (violation['blocked-uri'].includes('malicious')) {
alertSecurityTeam(violation);
}
}
}
security: {
csp: {
directives: {
'default-src': ["'self'"],
'script-src': ["'self'", 'https://cdn.example.com'],
'style-src': ["'self'", 'https://cdn.example.com'],
'img-src': ["'self'", 'https://cdn.example.com', 'data:'],
'font-src': ["'self'", 'https://cdn.example.com']
}
}
}
security: {
csp: {
directives: {
'default-src': ["'self'"],
'script-src': [
"'self'",
'https://www.google-analytics.com',
'https://www.googletagmanager.com'
],
'connect-src': [
"'self'",
'https://www.google-analytics.com',
'https://analytics.google.com'
],
'img-src': [
"'self'",
'https://www.google-analytics.com',
'data:'
]
}
}
}
{
path: '/embed',
attr: {
render: 'ssr',
middleware: {
csp: {
directives: {
'frame-src': ["'self'", 'https://www.youtube.com'],
'frame-ancestors': ["'self'", 'https://trusted-partner.com']
}
}
}
}
}
// Start here
directives: {
'default-src': ["'self'"],
'script-src': ["'self'"],
'style-src': ["'self'"],
'img-src': ["'self'"]
}
// Add sources only when needed

2. Avoid ‘unsafe-inline’ in Production

Section titled “2. Avoid ‘unsafe-inline’ in Production”
// Defeats CSP purpose
'script-src': ["'self'", "'unsafe-inline'"]
// Use nonces (τjs does this automatically)
'script-src': ["'self'", "'nonce-...'"]
// Too broad
'script-src': ["'self'", 'https:']
// Specific domains
'script-src': ["'self'", 'https://cdn.example.com', 'https://analytics.google.com']
reporting: {
endpoint: '/api/csp-violations',
onViolation: (report, req) => {
monitoringService.track('csp_violation', {
directive: report['violated-directive'],
blockedUri: report['blocked-uri'],
page: report['document-uri']
});
}
}

Use report-only mode initially:

security: {
csp: {
directives: { /* ... */ },
reporting: {
reportOnly: true // Monitor without blocking
}
}
}

After confirming no false positives, switch to enforce mode.