From 19af16729c66e71207d0ec8d10185128e8cfcd63 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <209825114+claude[bot]@users.noreply.github.com> Date: Wed, 25 Jun 2025 21:15:22 +0200 Subject: [PATCH] fix: optionally enable CSP headers (#6033) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add optional CSP headers when ENABLE_CSP_HEADERS is set - Add middleware to conditionally set Content-Security-Policy headers - Check for ENABLE_CSP_HEADERS environment variable - Apply secure default CSP policy suitable for web applications - Follows existing middleware patterns in codebase Resolves #6031 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: Ruben Fiszel * perf: optimize CSP headers with lazy_static and add configurability - Cache CSP_ENABLED and CSP_POLICY using lazy_static to avoid env var lookups on every request - Add comprehensive documentation to add_csp_headers middleware function - Make CSP policy configurable via CSP_POLICY environment variable - Maintain backward compatibility with secure default policy - Implement proper error handling for custom CSP policy values Co-authored-by: Alexander Petric <alpetric@users.noreply.github.com> * refactor: conditionally add CSP middleware layer instead of NO-OP - Use .option_layer() to only add CSP middleware when ENABLE_CSP_HEADERS is set - Remove runtime conditional check from add_csp_headers function - Improve performance by avoiding unnecessary middleware execution when disabled Co-authored-by: Ruben Fiszel * refactor: conditionally add CSP middleware layer instead of NO-OP Remove CSP_ENABLED variable and instead check if CSP_POLICY is not empty. This eliminates the need for a separate enable flag and simplifies the logic to just check if a meaningful CSP policy is configured. - Remove CSP_ENABLED lazy_static variable - Simplify CSP_POLICY to use unwrap_or_default() - Update conditional middleware logic to check !CSP_POLICY.is_empty() - Update documentation to reflect new behavior Co-authored-by: Ruben Fiszel * refactor: conditionally add CSP middleware layer instead of NO-OP Move CSP headers from general middleware stack to static assets only. CSP headers are only relevant for browsers consuming static content, not for API endpoints. Changes: - Add CSP headers directly to static asset responses in serve_path() - Remove CSP middleware from general middleware stack - Remove unused add_csp_headers middleware function - Maintain same CSP_POLICY environment variable configuration Co-authored-by: Alexander Petric * fix: conditionally compile CSP header imports with static_frontend feature Fixes unused import errors by wrapping HeaderValue and CSP_POLICY imports with #[cfg(feature = "static_frontend")] to match their usage context. Co-authored-by: Alexander Petric * refactor: move CSP lazy static block to static_assets.rs - Move CSP_POLICY lazy static from lib.rs to static_assets.rs for better encapsulation - Remove crate-level import since CSP_POLICY is now local to static_assets module - Maintain same functionality with proper feature gating for static_frontend Co-authored-by: Ruben Fiszel --------- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> Co-authored-by: Ruben Fiszel Co-authored-by: Alexander Petric --- backend/windmill-api/src/lib.rs | 2 ++ backend/windmill-api/src/static_assets.rs | 15 +++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/backend/windmill-api/src/lib.rs b/backend/windmill-api/src/lib.rs index ee03ff1825..7c274af630 100644 --- a/backend/windmill-api/src/lib.rs +++ b/backend/windmill-api/src/lib.rs @@ -238,6 +238,7 @@ lazy_static::lazy_static! { } + // Compliance with cloud events spec. pub async fn add_webhook_allowed_origin( req: axum::extract::Request, @@ -260,6 +261,7 @@ pub async fn add_webhook_allowed_origin( next.run(req).await } + #[cfg(not(feature = "tantivy"))] type IndexReader = (); diff --git a/backend/windmill-api/src/static_assets.rs b/backend/windmill-api/src/static_assets.rs index d5656fb6c1..48bd94081c 100644 --- a/backend/windmill-api/src/static_assets.rs +++ b/backend/windmill-api/src/static_assets.rs @@ -10,6 +10,8 @@ use axum::{body::Body, extract::OriginalUri, http::Response, response::IntoRespo #[cfg(feature = "static_frontend")] use axum::http::header; +#[cfg(feature = "static_frontend")] +use http::HeaderValue; use hyper::Uri; #[cfg(feature = "static_frontend")] @@ -17,6 +19,12 @@ use mime_guess::mime; #[cfg(feature = "static_frontend")] use rust_embed::RustEmbed; +// Content Security Policy configuration +#[cfg(feature = "static_frontend")] +lazy_static::lazy_static! { + static ref CSP_POLICY: String = std::env::var("CSP_POLICY").unwrap_or_default(); +} + // static_handler is a handler that serves static files from the pub async fn static_handler(OriginalUri(original_uri): OriginalUri) -> StaticFile { StaticFile(original_uri) @@ -51,6 +59,13 @@ fn serve_path(path: &str) -> Response { let mut res = Response::builder() .header(header::CONTENT_TYPE, mime.as_ref()) .header(header::ACCESS_CONTROL_ALLOW_ORIGIN, "*"); + + // Add Content-Security-Policy header for static assets when policy is set + if !CSP_POLICY.is_empty() { + if let Ok(header_value) = HeaderValue::try_from(CSP_POLICY.as_str()) { + res = res.header("Content-Security-Policy", header_value); + } + } if mime.as_ref() == mime::APPLICATION_JAVASCRIPT || mime.as_ref() == mime::TEXT_JAVASCRIPT || path.ends_with(".wasm")