From cbc1d46c4e32bdb7f697100574538922235c4e0d Mon Sep 17 00:00:00 2001 From: lanrtop Date: Sat, 18 Jul 2026 10:24:03 +0900 Subject: [PATCH] =?UTF-8?q?fix(mcp-inject):=20inject=20=E9=93=BE=E8=B7=AF?= =?UTF-8?q?=E7=A9=BA=E8=B7=AF=E5=BE=84=E5=8D=95=E7=82=B9=E9=98=B2=E6=8A=A4?= =?UTF-8?q?=E2=80=94=E2=80=94=E8=A1=A5=E9=BD=90=2007-15=20=E7=A9=BA?= =?UTF-8?q?=E8=B7=AF=E5=BE=84=E4=BA=8B=E6=95=85=E7=AC=AC=E4=BA=8C=E5=85=A5?= =?UTF-8?q?=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 辩证收缩实战产出:清理污染文件时追问「删文件是不是正确动作层」, 发现 473cf7e 只拦了 apply 链路,inject 链路 project_root() 对空串 win_path 穿透(ok_or_else 拦 NULL 不拦空串),settings.json/ liangjing.json 正是经此写进 src-tauri/。 - validate_registered_path 单点校验:拒空串/空白/相对路径(顺带堵 相对路径解析到 cwd 的边角),WSL 分支拒空 wsl_path - 2 条回归测试驻军(纯函数无 DB,cargo test 105 全绿) - 台账同步:污染文件已移出、两条空路径僵尸登记已注销 Rules-Applied: R06,R07 --- .../modules/governance-feedback-loop.md | 2 +- src-tauri/src/mcp_inject.rs | 48 ++++++++++++++++++- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/.blueprint/modules/governance-feedback-loop.md b/.blueprint/modules/governance-feedback-loop.md index e276617..546f946 100644 --- a/.blueprint/modules/governance-feedback-loop.md +++ b/.blueprint/modules/governance-feedback-loop.md @@ -66,7 +66,7 @@ MANAGED 块警示(5b0b068)、Bug Fix/Feature 分类 + S 级轻量卡 + 汇 - **OpenAI SDK tool_calls 联合类型守卫**:可作 AI 代码审查检查项候选(project_type:web) ### 遗留尾巴 -- src-tauri/ 下 6 处空路径污染残留(.blueprint/.claude/docs/AGENTS.md/CLAUDE.md/lefthook.yml,均 untracked,liangjing.json 指向「agent编排界面(opencode)」)——2026-07-18 会话中删除操作被用户拦下,保留待人工决定 +- ~~src-tauri/ 下空路径污染残留~~ → 2026-07-18 辩证收缩后三步收官:①9 个污染文件已移出(暂存 `%TEMP%\liangjing-pollution-20260718`);②两条空路径僵尸登记(agent编排界面(opencode)/银龙集团应用)已 unregister(磁盘无对应目录,实为死登记);③辩证挖出 **inject 链路漏拦同一空路径洞**(`project_root()` 对空串 win_path 穿透写 cwd,正是 settings.json/liangjing.json 污染的来源)——已单点补 `validate_registered_path`(拒空+拒相对路径)+ 2 条回归测试。空路径事故至此两链路(apply/inject)全堵 - envoy.md 硬门槛已进模板,**需重建安装炼境后 apply 才会分发到子项目**(模板打包在二进制资源) ## 任务卡 diff --git a/src-tauri/src/mcp_inject.rs b/src-tauri/src/mcp_inject.rs index d987c65..3c145ea 100644 --- a/src-tauri/src/mcp_inject.rs +++ b/src-tauri/src/mcp_inject.rs @@ -9,6 +9,22 @@ const LEGACY_KEY_PREFIX: &str = "lian-jing:group-"; // ── 路径解析 ────────────────────────────────────────────────────────────────── +/// 校验并解析登记路径:空串/空白/相对路径一律拒绝。 +/// 空路径经 PathBuf::from("") 会解析到炼境进程 cwd——2026-07-15 空路径登记 +/// 曾把 settings.json/liangjing.json 写进 src-tauri/(apply 链路同日已拦, +/// inject 链路本处单点补防)。 +fn validate_registered_path(raw: &str, field: &str) -> Result { + let trimmed = raw.trim(); + if trimmed.is_empty() { + return Err(format!("{field} 为空,拒绝解析(会写进炼境进程 cwd)")); + } + let path = PathBuf::from(trimmed); + if !path.is_absolute() { + return Err(format!("{field} 非绝对路径,拒绝解析: {trimmed:?}")); + } + Ok(path) +} + /// 解析项目根目录路径(WSL 项目自动转为 Windows UNC 路径) fn project_root(project_id: &str) -> Result { let conn = db::pool().get().map_err(|e| e.to_string())?; @@ -24,6 +40,9 @@ fn project_root(project_id: &str) -> Result { if platform.as_deref() == Some("wsl") { let wsl = wsl_path.ok_or_else(|| "WSL 项目未配置 wsl_path".to_string())?; + if wsl.trim().is_empty() { + return Err("wsl_path 为空,拒绝解析(会指向 WSL 发行版根目录)".to_string()); + } let distro: String = conn .query_row( "SELECT value FROM settings WHERE key = 'wsl_distro'", @@ -38,7 +57,7 @@ fn project_root(project_id: &str) -> Result { ))) } else { let win = win_path.ok_or_else(|| "Windows 项目未配置 win_path".to_string())?; - Ok(PathBuf::from(win)) + validate_registered_path(&win, "win_path") } } @@ -429,3 +448,30 @@ pub fn reinject_all_mcp() -> Vec { } errors } + +#[cfg(test)] +mod tests { + use super::*; + + /// 回归:空路径登记曾使 inject 链路把 settings.json/liangjing.json 写进 + /// 炼境进程 cwd(src-tauri/,2026-07-15 实锤,apply 链路同日已拦本处漏拦)。 + #[test] + fn registered_path_rejects_empty_and_relative() { + assert!(validate_registered_path("", "win_path").is_err(), "空串应拒绝"); + assert!(validate_registered_path(" ", "win_path").is_err(), "空白应拒绝"); + assert!( + validate_registered_path("src-tauri", "win_path").is_err(), + "相对路径应拒绝(相对炼境 cwd 解析)" + ); + } + + #[test] + fn registered_path_accepts_absolute_and_unc() { + assert!(validate_registered_path("E:\\code-project\\demo", "win_path").is_ok()); + assert!(validate_registered_path("E:/code-project/demo", "win_path").is_ok()); + assert!( + validate_registered_path("\\\\wsl.localhost\\Ubuntu\\home\\u\\p", "win_path").is_ok(), + "WSL UNC 路径应通过" + ); + } +}