fix(mcp-inject): inject 链路空路径单点防护——补齐 07-15 空路径事故第二入口
All checks were successful
Push & PR Check / check (push) Successful in 52s

辩证收缩实战产出:清理污染文件时追问「删文件是不是正确动作层」,
发现 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
This commit is contained in:
lanrtop 2026-07-18 10:24:03 +09:00
parent d15d3895d7
commit cbc1d46c4e
2 changed files with 48 additions and 2 deletions

View File

@ -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均 untrackedliangjing.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 才会分发到子项目**(模板打包在二进制资源)
## 任务卡

View File

@ -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<PathBuf, String> {
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<PathBuf, String> {
let conn = db::pool().get().map_err(|e| e.to_string())?;
@ -24,6 +40,9 @@ fn project_root(project_id: &str) -> Result<PathBuf, String> {
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<PathBuf, String> {
)))
} 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<String> {
}
errors
}
#[cfg(test)]
mod tests {
use super::*;
/// 回归:空路径登记曾使 inject 链路把 settings.json/liangjing.json 写进
/// 炼境进程 cwdsrc-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 路径应通过"
);
}
}