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:
@@ -49,6 +49,10 @@ pub async fn api_version_middleware(
|
||||
|
||||
/// 速率限制中间件
|
||||
/// 基于账号的请求频率限制
|
||||
///
|
||||
/// ⚠️ CRITICAL: DashMap 的 RefMut 持有 parking_lot 写锁。
|
||||
/// 必须在独立作用域块内完成所有 DashMap 操作,确保锁在 .await 之前释放。
|
||||
/// 否则并发请求争抢同一 shard 锁会阻塞 tokio worker thread,导致运行时死锁。
|
||||
pub async fn rate_limit_middleware(
|
||||
State(state): State<AppState>,
|
||||
req: Request<Body>,
|
||||
@@ -59,25 +63,77 @@ pub async fn rate_limit_middleware(
|
||||
.map(|ctx| ctx.account_id.clone())
|
||||
.unwrap_or_else(|| "anonymous".to_string());
|
||||
|
||||
// 无锁读取 rate limit 配置(避免每个请求获取 RwLock)
|
||||
let rate_limit = state.rate_limit_rpm() as usize;
|
||||
|
||||
let key = format!("rate_limit:{}", account_id);
|
||||
|
||||
let now = Instant::now();
|
||||
let window_start = now - std::time::Duration::from_secs(60);
|
||||
|
||||
let mut entries = state.rate_limit_entries.entry(key).or_insert_with(Vec::new);
|
||||
entries.retain(|&time| time > window_start);
|
||||
|
||||
if entries.len() >= rate_limit {
|
||||
|
||||
// DashMap 操作限定在作用域块内,确保 RefMut(持有 parking_lot 锁)在 await 前释放
|
||||
let blocked = {
|
||||
let mut entries = state.rate_limit_entries.entry(key).or_insert_with(Vec::new);
|
||||
entries.retain(|&time| time > window_start);
|
||||
|
||||
if entries.len() >= rate_limit {
|
||||
true
|
||||
} else {
|
||||
entries.push(now);
|
||||
false
|
||||
}
|
||||
}; // ← RefMut 在此处 drop,释放 parking_lot shard 锁
|
||||
|
||||
if blocked {
|
||||
return SaasError::RateLimited(format!(
|
||||
"请求频率超限,每分钟最多 {} 次请求",
|
||||
rate_limit
|
||||
)).into_response();
|
||||
}
|
||||
|
||||
entries.push(now);
|
||||
|
||||
|
||||
next.run(req).await
|
||||
}
|
||||
|
||||
/// 公共端点速率限制中间件 (基于客户端 IP,更严格)
|
||||
/// 用于登录/注册/刷新等无认证端点,防止暴力破解
|
||||
const PUBLIC_RATE_LIMIT_RPM: usize = 20;
|
||||
|
||||
pub async fn public_rate_limit_middleware(
|
||||
State(state): State<AppState>,
|
||||
req: Request<Body>,
|
||||
next: Next,
|
||||
) -> Response<Body> {
|
||||
// 从连接信息或 header 提取客户端 IP
|
||||
let client_ip = req.extensions()
|
||||
.get::<axum::extract::ConnectInfo<std::net::SocketAddr>>()
|
||||
.map(|ci| ci.0.ip().to_string())
|
||||
.unwrap_or_else(|| {
|
||||
req.headers()
|
||||
.get("x-real-ip")
|
||||
.or_else(|| req.headers().get("x-forwarded-for"))
|
||||
.and_then(|v| v.to_str().ok())
|
||||
.map(|s| s.split(',').next().unwrap_or("unknown").trim().to_string())
|
||||
.unwrap_or_else(|| "unknown".to_string())
|
||||
});
|
||||
|
||||
let key = format!("public_rate_limit:{}", client_ip);
|
||||
let now = Instant::now();
|
||||
let window_start = now - std::time::Duration::from_secs(60);
|
||||
|
||||
let blocked = {
|
||||
let mut entries = state.rate_limit_entries.entry(key).or_insert_with(Vec::new);
|
||||
entries.retain(|&time| time > window_start);
|
||||
|
||||
if entries.len() >= PUBLIC_RATE_LIMIT_RPM {
|
||||
true
|
||||
} else {
|
||||
entries.push(now);
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
if blocked {
|
||||
return SaasError::RateLimited(
|
||||
"请求频率超限,请稍后再试".into()
|
||||
).into_response();
|
||||
}
|
||||
|
||||
next.run(req).await
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user