release(secrets-mcp): 0.5.5 — 生产 CORS 显式 allow_methods,修复 tower-http 启动 panic
All checks were successful
Secrets MCP — Build & Release / 检查 / 构建 / 发版 (push) Successful in 4m59s
Secrets MCP — Build & Release / 部署 secrets-mcp (push) Successful in 6s

credentials + wildcard methods/headers 被 tower-http 禁止;生产环境改为 GET/POST/PATCH/DELETE/OPTIONS 白名单。
This commit is contained in:
voson
2026-04-05 12:26:11 +08:00
parent 9d6ac5c13a
commit 2b994141b8
3 changed files with 29 additions and 5 deletions

2
Cargo.lock generated
View File

@@ -2066,7 +2066,7 @@ dependencies = [
[[package]]
name = "secrets-mcp"
version = "0.5.4"
version = "0.5.5"
dependencies = [
"anyhow",
"askama",

View File

@@ -1,6 +1,6 @@
[package]
name = "secrets-mcp"
version = "0.5.4"
version = "0.5.5"
edition.workspace = true
[[bin]]

View File

@@ -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();