diff --git a/.jules/sentinel.md b/.jules/sentinel.md index 1a9528255..f783e97c7 100644 --- a/.jules/sentinel.md +++ b/.jules/sentinel.md @@ -17,3 +17,8 @@ **Vulnerability:** The `verify_github_sig` function in `apps/server/src/infra_routes.rs` verified GitHub webhook signatures using standard string comparison `expected.eq_ignore_ascii_case(hex_sig)`. **Learning:** String comparison functions exit early as soon as a character mismatch is found. In a cryptographic context, this variable response time leaks information and allows an attacker to perform a timing attack to forge a valid HMAC signature by guessing it byte by byte. **Prevention:** Always use a constant-time comparison library (like `hmac`'s `verify_slice()`) to compare cryptographic hashes, secrets, and signatures to prevent side-channel timing attacks. Avoid custom implementations of standard cryptographic algorithms. + +## 2024-05-18 - [Missing Security Headers] +**Vulnerability:** Missing important security headers like `X-Frame-Options`, `X-Content-Type-Options`, `Strict-Transport-Security`. +**Learning:** Axum doesn't add security headers by default. Tower has some utilities, but we can also use custom middleware. +**Prevention:** Implement standard security headers for all HTTP responses in `apps/server/src/security.rs`. diff --git a/apps/server/src/main.rs b/apps/server/src/main.rs index e4e00e95c..80068bdf3 100644 --- a/apps/server/src/main.rs +++ b/apps/server/src/main.rs @@ -469,6 +469,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..5be3f72f4 100644 --- a/apps/server/src/security.rs +++ b/apps/server/src/security.rs @@ -2,7 +2,7 @@ use axum::{ body::Body, - http::{header, HeaderValue, Method, Request, Response}, + http::{header, HeaderName, HeaderValue, Method, Request, Response}, middleware::Next, }; use tower_http::cors::{AllowOrigin, CorsLayer}; @@ -51,6 +51,31 @@ pub fn cors_layer() -> CorsLayer { /// HTML / routes app : pas de cache (évite page blanche après MAJ Flatpak). /// Assets fingerprinted `/_astro/*` : cache long. +/// Ajoute des en-têtes de sécurité standard à toutes les réponses HTTP. +pub async fn security_headers(req: Request, next: Next) -> Response { + let mut res = next.run(req).await; + let headers = res.headers_mut(); + + headers.insert( + HeaderName::from_static("x-content-type-options"), + HeaderValue::from_static("nosniff"), + ); + headers.insert( + HeaderName::from_static("x-frame-options"), + HeaderValue::from_static("sameorigin"), + ); + headers.insert( + HeaderName::from_static("strict-transport-security"), + HeaderValue::from_static("max-age=31536000; includeSubDomains"), + ); + headers.insert( + HeaderName::from_static("x-xss-protection"), + HeaderValue::from_static("1; mode=block"), + ); + + res +} + pub async fn static_cache_headers(req: Request, next: Next) -> Response { let path = req.uri().path().to_string(); let mut res = next.run(req).await;