fix: 5 个安全/正确性问题

1. (High) 无 DB 时审批绕过 — approval_manager 始终创建,
   无 DB 时用纯内存模式,不再绕过审批
2. (High) 多用户串上下文 — user_id 改存 ChatSession,
   spawn 前写入,executor 从 session 读(而非共享 Arc)
3. (Medium) 技能超时未杀子进程 — cmd.kill_on_drop(true)
4. (Medium) provider 配置不生效 — 模型选择跟随 LLM_PROVIDER
   环境变量(deepseek/lmstudio 各自取对应模型名)
5. (Low) manage_memos 改为 High 风险(add/delete 有副作用)
This commit is contained in:
2026-06-02 10:56:44 +08:00
parent 5daaba686b
commit 5602b08faf
5 changed files with 21 additions and 20 deletions
+1 -1
View File
@@ -5,7 +5,7 @@
备忘录存储在 `.data/memos.json` 文件中。 备忘录存储在 `.data/memos.json` 文件中。
## Risk Level ## Risk Level
Low High
## Parameters ## Parameters
```json ```json
+3
View File
@@ -30,6 +30,8 @@ pub struct SummaryEntry {
pub struct ChatSession { pub struct ChatSession {
pub user_id: String, pub user_id: String,
pub db_pool: Option<Arc<sqlx::PgPool>>, pub db_pool: Option<Arc<sqlx::PgPool>>,
/// 当前正在处理消息的用户 IDspawn 前设置,executor 中读取)
pub current_user_id: String,
/// 摘要总结点——此位置之前的消息已被压缩 /// 摘要总结点——此位置之前的消息已被压缩
pub checkpoint: usize, pub checkpoint: usize,
/// 全部消息(checkpoint 之后的保持完整) /// 全部消息(checkpoint 之后的保持完整)
@@ -49,6 +51,7 @@ impl Default for ChatSession {
Self { Self {
user_id: "default".to_string(), user_id: "default".to_string(),
db_pool: None, db_pool: None,
current_user_id: String::new(),
checkpoint: 0, checkpoint: 0,
messages: Vec::new(), messages: Vec::new(),
summaries: Vec::new(), summaries: Vec::new(),
+1 -2
View File
@@ -27,8 +27,7 @@ pub trait LlmProvider: Send + Sync {
pub type BoxedProvider = Arc<dyn LlmProvider>; pub type BoxedProvider = Arc<dyn LlmProvider>;
/// 从配置创建恰当的提供商 /// 从配置创建恰当的提供商
pub fn create_provider(_config: &ConversationConfig) -> Result<BoxedProvider, String> { pub fn create_provider(config: &ConversationConfig) -> Result<BoxedProvider, String> {
// 根据 model 前缀或环境变量选择
let provider = std::env::var("LLM_PROVIDER") let provider = std::env::var("LLM_PROVIDER")
.unwrap_or_else(|_| "deepseek".to_string()) .unwrap_or_else(|_| "deepseek".to_string())
.to_lowercase(); .to_lowercase();
+15 -17
View File
@@ -220,10 +220,9 @@ async fn cmd_listen(
Some(Arc::new(skill_registry)) Some(Arc::new(skill_registry))
}; };
// 审批管理器 let approval_manager = Arc::new(ApprovalManager::new(
let approval_manager = database database.as_ref().map(|db| Arc::new(db.pool().clone()))
.as_ref() ));
.map(|db| Arc::new(ApprovalManager::new(Some(Arc::new(db.pool().clone())))));
// 共享的当前用户 ID(用于审批消息) // 共享的当前用户 ID(用于审批消息)
let current_user_id = Arc::new(tokio::sync::Mutex::new(String::new())); let current_user_id = Arc::new(tokio::sync::Mutex::new(String::new()));
@@ -231,7 +230,12 @@ async fn cmd_listen(
let conversation = if enable_llm { let conversation = if enable_llm {
let sys_prompt = std::env::var("WEIXIN_LLM_SYSTEM_PROMPT") let sys_prompt = std::env::var("WEIXIN_LLM_SYSTEM_PROMPT")
.unwrap_or_else(|_| DEFAULT_SYSTEM_PROMPT.to_string()); .unwrap_or_else(|_| DEFAULT_SYSTEM_PROMPT.to_string());
let model = config.llm.deepseek.model.clone(); let provider = std::env::var("LLM_PROVIDER").unwrap_or_else(|_| "deepseek".to_string());
let model = if provider == "lmstudio" {
config.llm.lmstudio.model.clone()
} else {
config.llm.deepseek.model.clone()
};
let mut cfg = ConversationConfig { let mut cfg = ConversationConfig {
system_prompt: sys_prompt, system_prompt: sys_prompt,
@@ -360,7 +364,7 @@ async fn cmd_listen(
let args = args_json.to_string(); let args = args_json.to_string();
Box::pin(async move { Box::pin(async move {
let user_id = current_user.lock().await.clone(); let user_id = session.lock().await.current_user_id.clone();
match n.as_str() { match n.as_str() {
"read_memories" => return Ok(memory_store.read().await), "read_memories" => return Ok(memory_store.read().await),
"write_memory" => { "write_memory" => {
@@ -379,7 +383,7 @@ async fn cmd_listen(
if let Some(name) = cap_name { if let Some(name) = cap_name {
if let Some(ref reg) = reg { if let Some(ref reg) = reg {
let mut ctx = ExecutionContext::new(&user_id); let mut ctx = ExecutionContext::new(&user_id);
if let Some(a) = approval { ctx = ctx.with_approval(a); } ctx = ctx.with_approval(approval.clone());
ctx = ctx.with_wechat(send); ctx = ctx.with_wechat(send);
let result = reg.execute(&name, cap_params, &ctx).await; let result = reg.execute(&name, cap_params, &ctx).await;
return Ok(result.output); return Ok(result.output);
@@ -394,9 +398,7 @@ async fn cmd_listen(
let params: serde_json::Value = let params: serde_json::Value =
serde_json::from_str(&args).unwrap_or(serde_json::json!({})); serde_json::from_str(&args).unwrap_or(serde_json::json!({}));
let mut ctx = ExecutionContext::new(&user_id); let mut ctx = ExecutionContext::new(&user_id);
if let Some(a) = approval { ctx = ctx.with_approval(approval.clone());
ctx = ctx.with_approval(a);
}
ctx = ctx.with_wechat(send); ctx = ctx.with_wechat(send);
let result = reg.execute(&n, params, &ctx).await; let result = reg.execute(&n, params, &ctx).await;
Ok(result.output) Ok(result.output)
@@ -451,7 +453,7 @@ async fn cmd_listen(
file_state.save_runtime(&client.get_updates_buf().await); file_state.save_runtime(&client.get_updates_buf().await);
info!("已退出"); info!("已退出");
} }
_ = listen_loop(&client, enable_llm, echo, &account_id, database, file_state, conversation, approval_manager, listen_user_id) => {} _ = listen_loop(&client, enable_llm, echo, &account_id, database, file_state, conversation, &approval_manager, listen_user_id) => {}
} }
} }
@@ -463,7 +465,7 @@ async fn listen_loop(
database: &Option<Arc<Database>>, database: &Option<Arc<Database>>,
file_state: &state::StateManager, file_state: &state::StateManager,
conversation: Option<Arc<Conversation>>, conversation: Option<Arc<Conversation>>,
approval_manager: Option<Arc<ApprovalManager>>, approval_manager: &ApprovalManager,
current_user_id: Arc<tokio::sync::Mutex<String>>, current_user_id: Arc<tokio::sync::Mutex<String>>,
) { ) {
loop { loop {
@@ -526,11 +528,7 @@ async fn listen_loop(
} }
// 审批回复检查 // 审批回复检查
let is_approval_reply = if let Some(mgr) = &approval_manager { let is_approval_reply = approval_manager.handle_reply(from, text).await.is_some();
mgr.handle_reply(from, text).await.is_some()
} else {
false
};
if is_approval_reply { if is_approval_reply {
info!("消息匹配审批确认码,不传给 LLM"); info!("消息匹配审批确认码,不传给 LLM");
+1
View File
@@ -226,6 +226,7 @@ async fn run_skill(skill: &Skill, params: serde_json::Value, _ctx: &ExecutionCon
cmd.stdin(std::process::Stdio::piped()); cmd.stdin(std::process::Stdio::piped());
cmd.stdout(std::process::Stdio::piped()); cmd.stdout(std::process::Stdio::piped());
cmd.stderr(std::process::Stdio::piped()); cmd.stderr(std::process::Stdio::piped());
cmd.kill_on_drop(true);
let mut child = match cmd.spawn() { let mut child = match cmd.spawn() {
Ok(c) => c, Ok(c) => c,