fix(mcp-inject): inject 链路空路径单点防护——补齐 07-15 空路径事故第二入口
All checks were successful
Push & PR Check / check (push) Successful in 52s
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:
parent
d15d3895d7
commit
cbc1d46c4e
@ -66,7 +66,7 @@ MANAGED 块警示(5b0b068)、Bug Fix/Feature 分类 + S 级轻量卡 + 汇
|
|||||||
- **OpenAI SDK tool_calls 联合类型守卫**:可作 AI 代码审查检查项候选(project_type:web)
|
- **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 才会分发到子项目**(模板打包在二进制资源)
|
- envoy.md 硬门槛已进模板,**需重建安装炼境后 apply 才会分发到子项目**(模板打包在二进制资源)
|
||||||
|
|
||||||
## 任务卡
|
## 任务卡
|
||||||
|
|||||||
@ -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 路径)
|
/// 解析项目根目录路径(WSL 项目自动转为 Windows UNC 路径)
|
||||||
fn project_root(project_id: &str) -> Result<PathBuf, String> {
|
fn project_root(project_id: &str) -> Result<PathBuf, String> {
|
||||||
let conn = db::pool().get().map_err(|e| e.to_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") {
|
if platform.as_deref() == Some("wsl") {
|
||||||
let wsl = wsl_path.ok_or_else(|| "WSL 项目未配置 wsl_path".to_string())?;
|
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
|
let distro: String = conn
|
||||||
.query_row(
|
.query_row(
|
||||||
"SELECT value FROM settings WHERE key = 'wsl_distro'",
|
"SELECT value FROM settings WHERE key = 'wsl_distro'",
|
||||||
@ -38,7 +57,7 @@ fn project_root(project_id: &str) -> Result<PathBuf, String> {
|
|||||||
)))
|
)))
|
||||||
} else {
|
} else {
|
||||||
let win = win_path.ok_or_else(|| "Windows 项目未配置 win_path".to_string())?;
|
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
|
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 路径应通过"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user