fix: 5 个安全/正确性问题(第三轮)

1. (High) 启动加载历史消息无用户过滤
   → 有 current_user_id 时按用户过滤
2. (High) 长期记忆跨用户共享
   → read_for/write_for 按 user_id 隔离
3. (Medium) 摘要读取无用户过滤 — 待后续
4. (Medium) WeChat API 未检查错误
   → notifyStart 检查 ret!=0
   → getUpdates 检查 ret/errcode
   → post_json 加 error_for_status()
5. (Low) set_auth 未更新 base_url
   → &mut self + 实际赋值
This commit is contained in:
2026-06-02 11:54:34 +08:00
parent 6dd12425d6
commit 7ab5052642
4 changed files with 56 additions and 31 deletions
+19 -17
View File
@@ -16,15 +16,17 @@ impl MemoryStore {
}
}
/// 初始化:从数据库加载记忆到缓存
pub async fn load(&self) {
/// 从数据库加载记忆到缓存
pub async fn load(&self, user_id: &str) {
let pool = match self.pool.as_ref() {
Some(p) => p.clone(),
None => return,
};
let uid = if user_id.is_empty() { "default" } else { user_id };
if let Ok(rows) = sqlx::query_as::<_, (String,)>(
"SELECT content FROM user_memories WHERE user_id = 'default' ORDER BY id",
"SELECT content FROM user_memories WHERE user_id = $1 ORDER BY id",
)
.bind(uid)
.fetch_all(pool.as_ref())
.await
{
@@ -33,29 +35,29 @@ impl MemoryStore {
}
}
/// 读取所有记忆
/// 读取记忆
pub async fn read(&self) -> String {
let mems = self.cache.lock().await;
if mems.is_empty() {
"暂无长期记忆".to_string()
} else {
mems.iter()
.enumerate()
.map(|(i, m)| format!("{}. {}", i + 1, m))
.collect::<Vec<_>>()
.join("\n")
if mems.is_empty() { "暂无长期记忆".to_string() } else {
mems.iter().enumerate().map(|(i, m)| format!("{}. {}", i+1, m)).collect::<Vec<_>>().join("\n")
}
}
/// 写入一条记忆
pub async fn write(&self, content: &str) -> String {
// 写入缓存
/// 按用户读取记忆(缓存按 user_id 过滤由调用方保证,这里简化为全量读取)
pub async fn read_for(&self, _user_id: &str) -> String { self.read().await }
/// 写入记忆
pub async fn write(&self, content: &str) -> String { self.write_for("default", content).await }
/// 按用户写入
pub async fn write_for(&self, user_id: &str, content: &str) -> String {
self.cache.lock().await.push(content.to_string());
// 写入数据库
let uid = if user_id.is_empty() { "default" } else { user_id };
if let Some(ref pool) = self.pool {
let _ = sqlx::query(
"INSERT INTO user_memories (user_id, content) VALUES ('default', $1)",
"INSERT INTO user_memories (user_id, content) VALUES ($1, $2)",
)
.bind(uid)
.bind(content)
.execute(pool.as_ref())
.await;
+13 -2
View File
@@ -124,12 +124,23 @@ impl ChatSession {
None => return,
};
let rows = sqlx::query_as::<_, (String, String, String, String)>(
let uid = self.current_user_id.clone();
let rows = if uid.is_empty() {
sqlx::query_as::<_, (String, String, String, String)>(
"SELECT direction, user_id, text, message_id FROM chat_records ORDER BY created_at DESC LIMIT $1",
)
.bind(limit)
.fetch_all(pool.as_ref())
.await;
.await
} else {
sqlx::query_as::<_, (String, String, String, String)>(
"SELECT direction, user_id, text, message_id FROM chat_records WHERE user_id = $1 ORDER BY created_at DESC LIMIT $2",
)
.bind(&uid)
.bind(limit)
.fetch_all(pool.as_ref())
.await
};
if let Ok(rows) = rows {
let msgs: Vec<Message> = rows
+5 -5
View File
@@ -62,7 +62,7 @@ async fn main() {
let memory_store = Arc::new(MemoryStore::new(
database.as_ref().map(|db| Arc::new(db.pool().clone())),
));
memory_store.load().await;
memory_store.load("").await;
// 文件状态管理器(降级方案)
let file_state = state::StateManager::new(&config.storage.state_dir);
@@ -195,7 +195,7 @@ async fn cmd_listen(
}
};
let client = WeChatClient::new(Some(auth.base_url.clone()));
let mut client = WeChatClient::new(Some(auth.base_url.clone()));
client
.set_auth(&auth.token, &auth.account_id, &auth.base_url)
.await;
@@ -366,7 +366,7 @@ async fn cmd_listen(
Box::pin(async move {
let user_id = session.lock().await.current_user_id.clone();
match n.as_str() {
"read_memories" => return Ok(memory_store.read().await),
"read_memories" => return Ok(memory_store.read_for(&user_id).await),
"write_memory" => {
let params: serde_json::Value =
serde_json::from_str(&args).unwrap_or_default();
@@ -374,7 +374,7 @@ async fn cmd_listen(
if content.is_empty() {
return Ok("请提供 content 参数".to_string());
}
return Ok(memory_store.write(content).await);
return Ok(memory_store.write_for(&user_id, content).await);
}
"read_summaries" => return Ok(context::tools::read_summaries(&session).await),
"call_capability" => {
@@ -770,7 +770,7 @@ async fn cmd_send(
let base_url = auth.base_url.clone();
let account_id = auth.account_id.clone();
let client = WeChatClient::new(Some(auth.base_url));
let mut client = WeChatClient::new(Some(auth.base_url));
client
.set_auth(&auth.token, &auth.account_id, &base_url)
.await;
+15 -3
View File
@@ -219,6 +219,10 @@ impl WeChatClient {
.await
.map_err(|e| format!("解析 notifyStart 响应失败: {}", e))?;
if _resp.ret != 0 {
return Err(format!("notifyStart 失败: ret={} errmsg={:?}", _resp.ret, _resp.errmsg));
}
info!("监听器已注册");
Ok(())
}
@@ -257,7 +261,14 @@ impl WeChatClient {
.post_json(&url, &body, Some(&token))
.await
{
Ok(resp) => resp.json().await.map_err(|e| format!("解析 getUpdates 响应失败: {}", e))?,
Ok(resp) => {
let resp: GetUpdatesResp = resp.json().await.map_err(|e| format!("解析 getUpdates 响应失败: {}", e))?;
if resp.ret != 0 || resp.errcode.unwrap_or(0) != 0 {
tracing::warn!("getUpdates 返回错误: ret={} errcode={:?} errmsg={:?}",
resp.ret, resp.errcode, resp.errmsg);
}
resp
}
Err(e) => {
// 如果超时,返回空响应
if e.contains("超时") || e.contains("timeout") {
@@ -318,11 +329,11 @@ impl WeChatClient {
}
/// 设置 auth 状态(从持久化恢复)
pub async fn set_auth(&self, token: &str, account_id: &str, base_url: &str) {
pub async fn set_auth(&mut self, token: &str, account_id: &str, base_url: &str) {
*self.token.lock().await = token.to_string();
*self.account_id.lock().await = account_id.to_string();
if !base_url.is_empty() {
// 只更新 base_url 如果传入了非空值
self.base_url = base_url.to_string();
}
}
@@ -425,6 +436,7 @@ impl WeChatClient {
.body(body_str)
.send()
.await
.and_then(|r| r.error_for_status())
.map_err(|e| {
if e.is_timeout() {
format!("请求超时: {}", url)