CSP Directives
Each CSP directive is configurable via the builder — environment variables are no longer used.
Available directives
| Builder method | CSP Directive | Default |
|---|---|---|
.default_src(vec![...]) | default-src | 'none' |
.scripts(vec![...]) | script-src | 'self' |
.styles(vec![...]) | style-src | 'self', 'unsafe-inline' |
.images(vec![...]) | img-src | 'self' |
.fonts(vec![...]) | font-src | 'self' |
.connect(vec![...]) | connect-src | 'self' |
.objects(vec![...]) | object-src | 'none' |
.media(vec![...]) | media-src | 'self' |
.frames(vec![...]) | frame-src | 'none' |
.frame_ancestors(vec![...]) | frame-ancestors | 'none' |
.base_uri(vec![...]) | base-uri | 'self' |
.form_action(vec![...]) | form-action | 'self' |
style-src includes 'unsafe-inline' by default — required for libraries like htmx that inject inline styles (style="display:none") without a nonce.
Toggles
| Builder method | Default | Description |
|---|---|---|
.with_upgrade_insecure(bool) | false | upgrade-insecure-requests |
HSTS, X-Frame-Options, COEP, COOP, CORP, and the per-request nonce (injected into script-src/style-src) are not toggles: security_headers_middleware generates and injects them unconditionally, on every response.
Presets
| Builder method | Description |
|---|---|
.policy(SecurityPolicy::default()) | Default policy — 'self' on most directives (default-src/object-src/frame-src/frame-ancestors stay 'none') |
.policy(SecurityPolicy::strict()) | Strict — upgrade-insecure-requests, frame-ancestors 'none' |
.policy(SecurityPolicy::permissive()) | Permissive — unsafe-eval allowed, images from https: |
Common examples
Minimal — CSP enabled with no customization
RuniqueApp::builder(config)
.middleware(|m| {
m.with_csp(|c| c)
})
.build()
.await?;
CDN for scripts and styles (e.g. Bootstrap)
RuniqueApp::builder(config)
.middleware(|m| {
m.with_csp(|c| {
c.scripts(vec!["'self'", "https://cdn.jsdelivr.net"])
.styles(vec!["'self'", "https://cdn.jsdelivr.net"])
})
})
.build()
.await?;
Google Fonts + base64 images
RuniqueApp::builder(config)
.middleware(|m| {
m.with_csp(|c| {
c.fonts(vec!["'self'", "https://fonts.gstatic.com"])
.styles(vec!["'self'", "https://fonts.googleapis.com"])
.images(vec!["'self'", "data:"])
})
})
.build()
.await?;
WebSocket + iframes
RuniqueApp::builder(config)
.middleware(|m| {
m.with_csp(|c| {
c.connect(vec!["'self'", "wss://ws.example.com"])
.frames(vec!["'self'"])
.frame_ancestors(vec!["'self'"])
})
})
.build()
.await?;
Full configuration (production)
RuniqueApp::builder(config)
.middleware(|m| {
m.with_csp(|c| {
c.with_upgrade_insecure(true)
.scripts(vec!["'self'", "https://cdn.jsdelivr.net"])
.styles(vec!["'self'", "https://cdn.jsdelivr.net", "https://fonts.googleapis.com"])
.fonts(vec!["'self'", "https://fonts.gstatic.com"])
.images(vec!["'self'", "data:", "https://cdn.example.com"])
.connect(vec!["'self'", "wss://ws.example.com"])
})
})
.build()
.await?;
Strict preset
RuniqueApp::builder(config)
.middleware(|m| {
m.with_csp(|c| {
c.policy(SecurityPolicy::strict())
})
})
.build()
.await?;
Nonce behavior on `script-src` and `style-src`
The nonce is always active — on every response:
'nonce-{value}'is automatically appended toscript-srcandstyle-src'unsafe-inline'is automatically removed from those directives if present
This ensures inline scripts without a nonce are blocked, even if 'unsafe-inline' was manually configured.
# Generated header with active nonce:
Content-Security-Policy: default-src 'none'; script-src 'self' 'nonce-abc123'; style-src 'self' 'unsafe-inline' 'nonce-abc123'; ...