diff --git a/.jules/sentinel.md b/.jules/sentinel.md index ffc0bd2ce..778295429 100644 --- a/.jules/sentinel.md +++ b/.jules/sentinel.md @@ -2,3 +2,7 @@ **Vulnerability:** The manual SVG sanitizer `apps/web/src/lib/sanitize-svg.ts` used an insufficient allowlist/blocklist. It allowed ``, ``, ``, ``, ``, and `` which could lead to mutation XSS (mXSS) and other attack vectors when rendering untrusted Mermaid or Math blocks. **Learning:** Manual DOM tree sanitization is notoriously hard to get right because attackers can hide active elements (e.g. `` tags with `javascript:` payloads) inside grandchild trees (like ``) or use SVG-specific tricks (like ``) which naive list-based cleaners overlook. **Prevention:** Rather than adding heavy third-party dependencies (like DOMPurify) against explicit project instructions, expanding the `BLOCKED` Set to include these structural injection vectors provides a robust, zero-dependency mitigation. Future expansions should verify nested elements carefully. +## 2026-10-14 - [Axum Middleware Security Headers] +**Vulnerability:** Missing fundamental security headers (X-Frame-Options, X-Content-Type-Options, Strict-Transport-Security) across the Axum HTTP routing backend (`apps/server/src/security.rs`). +**Learning:** Axum provides easy layer definitions using `middleware::from_fn`. We can inject these security headers universally into existing router layers without disrupting other caching (`static_cache_headers`) or CORS (`cors_layer`) mechanisms. +**Prevention:** When developing custom backend routing in Rust Axum, ensure a standard middleware layer enforces security headers alongside CORS configurations to provide defense in depth against client-side exploitation such as clickjacking and MIME-sniffing. diff --git a/apps/server/src/main.rs b/apps/server/src/main.rs index 71dfc8e3d..3c5b65334 100644 --- a/apps/server/src/main.rs +++ b/apps/server/src/main.rs @@ -195,6 +195,7 @@ async fn main() -> Result<(), Box> { }); } let mut app = worker::worker_router(state.clone()) + .layer(middleware::from_fn(security::security_headers)) .layer(middleware::from_fn(security::static_cache_headers)) .layer(security::cors_layer()) .layer(TraceLayer::new_for_http()); @@ -392,6 +393,7 @@ async fn main() -> Result<(), Box> { state.clone(), worker::fence_stale_leader, )) + .layer(middleware::from_fn(security::security_headers)) .layer(middleware::from_fn(security::static_cache_headers)) .layer(security::cors_layer()) .layer(TraceLayer::new_for_http()) diff --git a/apps/server/src/security.rs b/apps/server/src/security.rs index 8ea417ea1..85f655849 100644 --- a/apps/server/src/security.rs +++ b/apps/server/src/security.rs @@ -49,6 +49,28 @@ pub fn cors_layer() -> CorsLayer { ]) } +/// Ajoute les headers de sécurité HTTP (X-Frame-Options, X-Content-Type-Options, HSTS). +pub async fn security_headers(req: Request, next: Next) -> Response { + let mut res = next.run(req).await; + + // HSTS : Strict-Transport-Security, max-age=1 year, includeSubDomains + res.headers_mut().insert( + header::STRICT_TRANSPORT_SECURITY, + HeaderValue::from_static("max-age=31536000; includeSubDomains") + ); + // Anti-Clickjacking : X-Frame-Options DENY + res.headers_mut().insert( + header::X_FRAME_OPTIONS, + HeaderValue::from_static("DENY") + ); + // Anti-MIME-Sniffing : X-Content-Type-Options nosniff + res.headers_mut().insert( + header::X_CONTENT_TYPE_OPTIONS, + HeaderValue::from_static("nosniff") + ); + res +} + /// HTML / routes app : pas de cache (évite page blanche après MAJ Flatpak). /// Assets fingerprinted `/_astro/*` : cache long. pub async fn static_cache_headers(req: Request, next: Next) -> Response {