新增: AI工具授权目录总开关(信任所有目录)

- path_auth判定加bypass旁路:开启时白名单外目录免逐次询问直接放行
- 黑名单凭据目录保护不随开关放行(安全硬边界保留)
- 设置页授权目录面板加开关(默认关,读取失败按关处理)
- 对话区开启时常驻放行提示横幅防静默裸奔,中英i18n齐
This commit is contained in:
lxy
2026-09-29 14:59:02 +08:00
parent 0168617503
commit 8a1675b58c
8 changed files with 114 additions and 5 deletions
+18 -5
View File
@@ -28,7 +28,7 @@ mod diff;
// path_auth(audit/path_auth.rs):路径授权预校验。
// 第六批从本文件抽离,行为零变更。pub(super) use 供 tests 子模块引用 + process_tool_calls 裸名调用。
mod path_auth;
pub(super) use path_auth::{check_file_tool_auth, FileToolAuthOutcome};
pub(super) use path_auth::{check_file_tool_auth, FileToolAuthOutcome, PATH_AUTH_BYPASS_KEY};
// approval(audit/approval.rs):审批门控逻辑。
// 第六批从本文件抽离,行为零变更。use 保持 process_tool_calls 裸名调用。
@@ -325,6 +325,19 @@ pub(crate) async fn process_tool_calls(
.allowed_dirs
.clone();
let allowed_guard = allowed_snapshot.read().await.clone();
// 「信任所有目录」总开关(KV df-ai-path-auth-bypass,默认关):开启后白名单外路径不再挂起
// 授权弹窗(NeedsAuth→直接放行),黑名单 Denied 仍硬拒(凭据目录自保护不受影响)。
// 读取失败按关处理(fail-safe,默认行为不变);前端设置页改后下一轮 process_tool_calls 即生效。
let path_auth_bypass = app_handle
.state::<crate::state::AppState>()
.settings
.get(PATH_AUTH_BYPASS_KEY)
.await
.ok()
.flatten()
// 前端 appSettings.set 以 JSON.stringify 落库(带引号),归一后比对(同 df-ai-trace-approve 先例)
.map(|v| { let v = v.trim_matches('"'); v == "true" || v == "1" })
.unwrap_or(false);
// 收集 NeedsAuth/Denied drafts(从 drafts 移除,不进下方 RiskLevel 循环)
let mut path_auth_pending: Vec<(ToolCallDraft, serde_json::Value, PathAuthRequest)> = Vec::new();
let mut path_denied: Vec<(ToolCallDraft, String)> = Vec::new();
@@ -332,7 +345,7 @@ pub(crate) async fn process_tool_calls(
for (idx, draft, args) in drafts {
// 会话级临时授权目录在 allowed_guard.session 内(进程级,随 active 会话切换清空),
// handler 闭包 read lock 与此处预校验读同一字段,两端授权判定一致。
match check_file_tool_auth(&draft.name, &args, &allowed_guard) {
match check_file_tool_auth(&draft.name, &args, &allowed_guard, path_auth_bypass) {
FileToolAuthOutcome::Authorized => authorized_drafts.push((idx, draft, args)),
FileToolAuthOutcome::NeedsAuth(req) => {
// all 档全自动(用户拍板):路径授权弹窗也不打断,与 RiskLevel 门同语义。
@@ -875,7 +888,7 @@ mod tests {
let allowed = crate::state::AllowedDirs::default();
let args = serde_json::json!({ "pattern": "foo", "path": "C:/some/unauthorized/dir" });
let outcome = check_file_tool_auth("grep", &args, &allowed);
let outcome = check_file_tool_auth("grep", &args, &allowed, false);
match outcome {
FileToolAuthOutcome::NeedsAuth(_) => { /* 期望:grep 触发授权申请 */ }
FileToolAuthOutcome::Authorized => panic!("grep 未授权应返 NeedsAuth(申请授权),实际 Authorized"),
@@ -883,7 +896,7 @@ mod tests {
}
// 对照:search_files 同样未授权但被硬拒(Denied),锁定两工具差异
let search_outcome = check_file_tool_auth("search_files", &args, &allowed);
let search_outcome = check_file_tool_auth("search_files", &args, &allowed, false);
match search_outcome {
FileToolAuthOutcome::Denied(_) => { /* 期望:search_files 盲拒 */ }
FileToolAuthOutcome::NeedsAuth(_) => panic!("search_files 未授权应返 Denied(盲拒),实际 NeedsAuth"),
@@ -897,7 +910,7 @@ mod tests {
let allowed = crate::state::AllowedDirs::default_with_root();
// .ssh 命中系统黑名单(is_in_system_blacklist)
let args = serde_json::json!({ "pattern": "foo", "path": "/home/user/.ssh/config" });
let outcome = check_file_tool_auth("grep", &args, &allowed);
let outcome = check_file_tool_auth("grep", &args, &allowed, false);
match outcome {
FileToolAuthOutcome::Denied(_) => { /* 期望:黑名单硬拒 */ }
FileToolAuthOutcome::NeedsAuth(_) => panic!("grep 黑名单路径应返 Denied,实际 NeedsAuth"),