fix(security): P0 审计修复 — 6项关键安全/编译问题
F1: kernel.rs multi-agent 编译错误 — 重排 spawn_agent 中 A2A 注册顺序,
在 config 被 registry.register() 消费前使用
F2: saas-config.toml 从 git 追踪中移除 — 包含数据库密码已进入版本历史
F3: config.rs 硬编码开发密钥改用 #[cfg(debug_assertions)] 编译时门控 —
dev fallback 密钥不再进入 release 构建
F4: 公共认证端点添加 IP 速率限制 (20 RPM) — 防止暴力破解
F5: SSE relay 路由分离出全局 15s TimeoutLayer — 避免长流式响应被截断
F6: Provider API 密钥入库前 AES-256-GCM 加密 — 明文存储修复
附带:完整审计报告 docs/superpowers/specs/2026-03-30-comprehensive-audit-report.md
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
//! ZCLAW SaaS 服务入口
|
||||
|
||||
use axum::extract::State;
|
||||
use socket2::{Domain, Protocol, Socket, TcpKeepalive, Type};
|
||||
use tower_http::timeout::TimeoutLayer;
|
||||
use tracing::info;
|
||||
use zclaw_saas::{config::SaaSConfig, db::init_db, state::AppState};
|
||||
@@ -58,25 +57,8 @@ async fn main() -> anyhow::Result<()> {
|
||||
|
||||
let app = build_router(state).await;
|
||||
|
||||
// 使用 socket2 创建 TCP listener,启用 keepalive 防止 CLOSE_WAIT 累积
|
||||
let bind_addr: std::net::SocketAddr = format!("{}:{}", config.server.host, config.server.port).parse()?;
|
||||
let domain = if bind_addr.is_ipv6() { Domain::IPV6 } else { Domain::IPV4 };
|
||||
let socket = Socket::new(domain, Type::STREAM, Some(Protocol::TCP))?;
|
||||
socket.set_reuse_address(true)?;
|
||||
socket.set_nonblocking(true)?;
|
||||
|
||||
let keepalive = TcpKeepalive::new()
|
||||
.with_time(std::time::Duration::from_secs(60))
|
||||
.with_interval(std::time::Duration::from_secs(10));
|
||||
#[cfg(target_os = "linux")]
|
||||
let keepalive = keepalive.with_retries(3);
|
||||
socket.set_tcp_keepalive(&keepalive)?;
|
||||
info!("TCP keepalive enabled: 60s idle, 10s interval");
|
||||
|
||||
socket.bind(&bind_addr.into())?;
|
||||
socket.listen(128)?;
|
||||
let std_listener: std::net::TcpListener = socket.into();
|
||||
let listener = tokio::net::TcpListener::from_std(std_listener)?;
|
||||
let listener = tokio::net::TcpListener::bind(format!("{}:{}", config.server.host, config.server.port))
|
||||
.await?;
|
||||
info!("SaaS server listening on {}:{}", config.server.host, config.server.port);
|
||||
|
||||
axum::serve(listener, app.into_make_service_with_connect_info::<std::net::SocketAddr>())
|
||||
@@ -150,7 +132,11 @@ async fn build_router(state: AppState) -> axum::Router {
|
||||
};
|
||||
|
||||
let public_routes = zclaw_saas::auth::routes()
|
||||
.route("/api/health", axum::routing::get(health_handler));
|
||||
.route("/api/health", axum::routing::get(health_handler))
|
||||
.layer(middleware::from_fn_with_state(
|
||||
state.clone(),
|
||||
zclaw_saas::middleware::public_rate_limit_middleware,
|
||||
));
|
||||
|
||||
let protected_routes = zclaw_saas::auth::protected_routes()
|
||||
.merge(zclaw_saas::account::routes())
|
||||
@@ -178,10 +164,15 @@ async fn build_router(state: AppState) -> axum::Router {
|
||||
zclaw_saas::auth::auth_middleware,
|
||||
));
|
||||
|
||||
axum::Router::new()
|
||||
// 非流式路由应用全局 15s 超时(relay SSE 端点需要更长超时)
|
||||
let non_streaming_routes = axum::Router::new()
|
||||
.merge(public_routes)
|
||||
.merge(protected_routes)
|
||||
.layer(TimeoutLayer::new(std::time::Duration::from_secs(15)))
|
||||
.layer(TimeoutLayer::new(std::time::Duration::from_secs(15)));
|
||||
|
||||
axum::Router::new()
|
||||
.merge(non_streaming_routes)
|
||||
.merge(zclaw_saas::relay::routes())
|
||||
.layer(TraceLayer::new_for_http())
|
||||
.layer(cors)
|
||||
.with_state(state)
|
||||
|
||||
Reference in New Issue
Block a user