From 2b994141b81813a2d9d4b1b97f1b4d6739b172ec Mon Sep 17 00:00:00 2001 From: voson Date: Sun, 5 Apr 2026 12:26:11 +0800 Subject: [PATCH] =?UTF-8?q?release(secrets-mcp):=200.5.5=20=E2=80=94=20?= =?UTF-8?q?=E7=94=9F=E4=BA=A7=20CORS=20=E6=98=BE=E5=BC=8F=20allow=5Fmethod?= =?UTF-8?q?s=EF=BC=8C=E4=BF=AE=E5=A4=8D=20tower-http=20=E5=90=AF=E5=8A=A8?= =?UTF-8?q?=20panic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit credentials + wildcard methods/headers 被 tower-http 禁止;生产环境改为 GET/POST/PATCH/DELETE/OPTIONS 白名单。 --- Cargo.lock | 2 +- crates/secrets-mcp/Cargo.toml | 2 +- crates/secrets-mcp/src/main.rs | 30 +++++++++++++++++++++++++++--- 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ff75b1f..5c321ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2066,7 +2066,7 @@ dependencies = [ [[package]] name = "secrets-mcp" -version = "0.5.4" +version = "0.5.5" dependencies = [ "anyhow", "askama", diff --git a/crates/secrets-mcp/Cargo.toml b/crates/secrets-mcp/Cargo.toml index af7bc61..fd2d85f 100644 --- a/crates/secrets-mcp/Cargo.toml +++ b/crates/secrets-mcp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "secrets-mcp" -version = "0.5.4" +version = "0.5.5" edition.workspace = true [[bin]] diff --git a/crates/secrets-mcp/src/main.rs b/crates/secrets-mcp/src/main.rs index 9679fc0..f6188ac 100644 --- a/crates/secrets-mcp/src/main.rs +++ b/crates/secrets-mcp/src/main.rs @@ -250,12 +250,26 @@ fn production_allowed_headers() -> [axum::http::HeaderName; 5] { ] } +/// Production CORS allowed methods. +/// +/// Keep this list explicit because tower-http rejects +/// `allow_credentials(true)` together with `allow_methods(Any)`. +fn production_allowed_methods() -> [axum::http::Method; 5] { + [ + axum::http::Method::GET, + axum::http::Method::POST, + axum::http::Method::PATCH, + axum::http::Method::DELETE, + axum::http::Method::OPTIONS, + ] +} + /// Build the CORS layer for the application. /// /// In production mode the origin is restricted to the BASE_URL origin /// (scheme://host:port, path stripped) and credentials are allowed. -/// `allow_headers` uses an explicit whitelist to avoid the tower-http -/// restriction on `allow_credentials(true)` + `allow_headers(Any)`. +/// `allow_headers` and `allow_methods` use explicit whitelists to avoid the +/// tower-http restriction on `allow_credentials(true)` + wildcards. /// /// In development mode all origins, methods and headers are allowed. fn build_cors_layer(base_url: &str, is_production: bool) -> CorsLayer { @@ -272,7 +286,7 @@ fn build_cors_layer(base_url: &str, is_production: bool) -> CorsLayer { }; CorsLayer::new() .allow_origin(allowed_origin) - .allow_methods(Any) + .allow_methods(production_allowed_methods()) .allow_headers(production_allowed_headers()) .allow_credentials(true) } else { @@ -304,6 +318,16 @@ mod tests { assert!(names.contains(&"x-mcp-session")); } + #[test] + fn production_cors_methods_include_all_required() { + let methods = production_allowed_methods(); + assert!(methods.contains(&axum::http::Method::GET)); + assert!(methods.contains(&axum::http::Method::POST)); + assert!(methods.contains(&axum::http::Method::PATCH)); + assert!(methods.contains(&axum::http::Method::DELETE)); + assert!(methods.contains(&axum::http::Method::OPTIONS)); + } + #[test] fn production_cors_normalizes_base_url_with_path() { let url = url::Url::parse("https://secrets.example.com/secrets/app").unwrap();