Middleware & Sécurité

Directives CSP

Chaque directive CSP est configurable via le builder — plus via variables d'environnement.

Directives disponibles

Méthode builderDirective CSPDéfaut
.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 inclut 'unsafe-inline' par défaut — nécessaire pour des bibliothèques comme htmx qui injectent des styles inline (style="display:none") sans nonce.

Toggles

Méthode builderDéfautDescription
.with_upgrade_insecure(bool)falseupgrade-insecure-requests

HSTS, X-Frame-Options, COEP, COOP, CORP, et le nonce par requête (injecté dans script-src/style-src) ne sont pas des toggles : security_headers_middleware les génère et les injecte de façon inconditionnelle, sur toutes les réponses.

Presets

Méthode builderDescription
.policy(SecurityPolicy::default())Politique par défaut — 'self' sur la plupart des directives (default-src/object-src/frame-src/frame-ancestors restent 'none')
.policy(SecurityPolicy::strict())Strict — upgrade-insecure-requests, frame-ancestors 'none'
.policy(SecurityPolicy::permissive())Permissif — unsafe-eval autorisé, images depuis https:

Exemples courants

Minimal — CSP activée sans personnalisation

RuniqueApp::builder(config)
    .middleware(|m| {
        m.with_csp(|c| c)
    })
    .build()
    .await?;

CDN pour scripts et styles (ex. 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 + images base64

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?;

Configuration complète (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?;

Preset strict

RuniqueApp::builder(config)
    .middleware(|m| {
        m.with_csp(|c| {
            c.policy(SecurityPolicy::strict())
        })
    })
    .build()
    .await?;

Comportement du nonce sur `script-src` et `style-src`

Le nonce est toujours actif — sur toutes les réponses :

  • 'nonce-{valeur}' est ajouté automatiquement à script-src et style-src
  • 'unsafe-inline' est retiré automatiquement de ces directives si présent

Cela garantit que les scripts inline sans nonce sont bloqués, même si 'unsafe-inline' est configuré manuellement.

# Header généré avec nonce actif :
Content-Security-Policy: default-src 'none'; script-src 'self' 'nonce-abc123'; style-src 'self' 'unsafe-inline' 'nonce-abc123'; ...