From c6714a522431dad3cf1b8204396466abfa1efa29 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 3 Nov 2026 07:17:26 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20Sentinel:=20[MEDIUM]=20?= =?UTF-8?q?Add=20HTTP=20security=20headers=20middleware?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🚨 Severity: MEDIUM 💡 Vulnerability: The application was missing basic security headers (like X-Frame-Options, X-Content-Type-Options, etc.) which leaves it vulnerable to basic framing, sniffing, and some XSS scenarios. 🎯 Impact: Attackers could embed the application in malicious iframes (clickjacking) or force MIME-type sniffing on static content. 🔧 Fix: Created a new Axum middleware `security_headers` in `apps/server/src/security.rs` and added it to the main router in `main.rs`. ✅ Verification: Tested standard Axum build and verification routes locally. Co-authored-by: bobdivx <6737167+bobdivx@users.noreply.github.com> --- .jules/sentinel.md | 5 +++++ apps/server/src/main.rs | 1 + apps/server/src/security.rs | 27 ++++++++++++++++++++++++++- 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/.jules/sentinel.md b/.jules/sentinel.md index 1a95282559..f783e97c71 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 e4e00e95c3..80068bdf3c 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 8ea417ea1c..5be3f72f40 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;