优化: AI授权目录改进(误弹窗根因+安全加固)

strip_verbatim比对侧收口/黑名单统一/symlink/bind双路径reload/toast接线;cargo test 18
This commit is contained in:
2026-06-20 15:54:51 +08:00
parent e5a89165e6
commit e2ece2d898
8 changed files with 251 additions and 79 deletions

View File

@@ -130,14 +130,14 @@ fn validate_path(path: &str) -> anyhow::Result<()> {
anyhow::bail!("禁止路径遍历 (..)");
}
if lower.contains("\\.ssh")
|| lower.contains("\\.aws")
|| lower.contains("\\.gnupg")
|| lower.contains("\\appdata\\")
|| lower.contains("\\programdata\\")
|| lower.contains("\\windows\\")
|| lower.contains("\\system32\\")
{
// 敏感系统目录(.ssh/.aws/.gnupg + Windows/ProgramFiles/ProgramData + Unix 核心):
// 统一走 state::is_in_system_blacklist 分段匹配(消除 contains 子串误伤如 my\windows\backup,
// 与 is_authorized 运行时黑名单同源,单一来源 — 修 agent1 问题4 两套黑名单不一致)
if crate::state::is_in_system_blacklist(&PathBuf::from(&normalized)) {
anyhow::bail!("禁止访问敏感系统目录");
}
// AppData 保留单独 contains(用户级数据,分段匹配会误伤 D:\backup\appdata 这类合法目录名)
if lower.contains("\\appdata\\") {
anyhow::bail!("禁止访问敏感系统目录");
}
Ok(())
@@ -542,6 +542,10 @@ fn register_project_tools(registry: &mut AiToolRegistry, db: &Arc<Database>) {
let stack_opt = args.get("stack").and_then(|v| v.as_str()).filter(|s| !s.is_empty());
let repo = df_storage::crud::ProjectRepo::new(&db);
let stack = bind_dir_to_project(&repo, id, path, stack_opt).await?;
// F-260619-03: 项目绑定目录加 AI 白名单由 reload_allowed_dirs 自动读 projects.bind_directory
// 统一处理(优雅:绑定 = 自动授权,reload 时合并)。bind handler 不直接写 allowed_dirs
// (register_project_tools 无 allowed_dirs 参数,且 reload 集中更易维护)。
// 绑定后触发 reload:通过 app_handle emit 事件让 state reload,或下次工具调用前 reload 兜底。
Ok(serde_json::json!({ "id": id, "path": path, "stack": stack, "bound": true }))
})
})},
@@ -1790,14 +1794,20 @@ fn search_files_recursive<'a>(
.map_err(|e| anyhow::anyhow!("无法读取目录 {}: {}", path, e))?;
while let Some(entry) = dir.next_entry().await? {
let name = entry.file_name().to_string_lossy().to_string();
let metadata = entry.metadata().await?;
let is_dir = metadata.is_dir();
// file_type 不跟随 symlink(对比 entry.metadata 会跟随);symlink 目录递归会逃逸出白名单
// (workspace 内 symlink → 外部目录),故 symlink 一律跳过(不匹配/不递归)。对齐 list_dir_recursive。
let file_type = entry.file_type().await?;
if file_type.is_symlink() {
continue;
}
let is_dir = file_type.is_dir();
if !is_dir {
// 字符串包含匹配(大小写不敏感)
if name.to_lowercase().contains(pattern) {
*total += 1;
if results.len() < max_results {
let full_path = std::path::Path::new(path).join(&name).to_string_lossy().into_owned();
let metadata = entry.metadata().await?;
results.push(serde_json::json!({ "path": full_path, "size": metadata.len() }));
}
}