清理: 授权目录白名单退役与审批面板收敛

- 授权目录白名单机制退役为默认全放行(2026-10-25 拍板): 删 AllowedDirsPanel/DirAuthDialog/ApprovalPopup 三件+设置页目录选择 UI+i18n 键
- 后端 path_auth bypass 恒开收敛,allowed_dirs 机制保留代码路径
- 新增 ScriptSafetyPanel(脚本安全面板替代审批弹窗)
- restore.rs 测试 import 断线修复(类型已迁 commands/ai/mod.rs)
This commit is contained in:
lxy
2026-10-26 01:36:31 +08:00
parent 0dd95e0e6e
commit d97cbb4439
18 changed files with 178 additions and 1606 deletions
+9 -1
View File
@@ -7,7 +7,7 @@
use std::sync::Arc;
use tauri::AppHandle;
use tauri::{AppHandle, Manager};
use tokio::sync::Mutex;
use df_ai::ai_tools::{AiToolRegistry, RiskLevel};
@@ -16,6 +16,7 @@ use df_storage::crud::AiToolExecutionRepo;
use df_storage::db::Database;
use super::cache::{cache_hit_warning, find_cached_high_risk_result, pending_placeholder_for};
use super::approval_expires_at_millis;
use super::super::event_bus::emit_ai_chat_event;
use super::diff::build_write_file_diff;
use super::reason::build_approval_reason;
@@ -205,6 +206,12 @@ pub(super) async fn insert_pending_approval(
}
// 慢操作锁外:拼 reason(DB 读)+ emit + 审计落 pending 纪录(DB 写)
let reason = build_approval_reason(&draft.name, &args, risk_level, db).await;
// 审批超时收敛单一真值=后端(arm_approval_timeout_timers + cleanup 惰性兜底,前端本地
// 自动拒绝定时器已退役 2026-10-25):到期时刻随事件透传,前端倒计时只读不算。
let expires_at_millis = approval_expires_at_millis(
app_handle.state::<crate::state::AppState>().inner(),
std::time::SystemTime::now(),
);
// L3 emit 双写:Med/High 风险审批挂起 AiApprovalRequired 双路发布(tunnel 透传 miniapp 弹审批窗)。
let ev = AiChatEvent::AiApprovalRequired {
id: draft.id.clone(),
@@ -213,6 +220,7 @@ pub(super) async fn insert_pending_approval(
reason,
diff: approval_diff,
conversation_id: Some(conv_id.to_string()),
expires_at_millis,
};
emit_ai_chat_event(&app_handle, ev);
// P1 bg 化:审批挂起需用户介入,主窗隐藏到托盘时系统通知补位(条件发送,失败静默)。
+5 -4
View File
@@ -8,11 +8,12 @@
//! process_tool_calls 通过 `use path_auth::check_file_tool_auth` 裸名调用。
//! 测试模块通过 `super::check_file_tool_auth` 引用。
use super::PathAuthRequest;
use crate::commands::ai::PathAuthRequest;
/// 「信任所有目录」总开关 KV key(app_settings,bool,默认关)。
/// 开启后白名单外路径不再挂起授权弹窗,黑名单拦截也一并放行(产品拍板:体验优先,
/// 用户知情选择,后期再收敛)。前端设置页读写同一 key。
/// 「信任所有目录」总开关 KV key(app_settings)。
/// 【已退役】授权目录白名单机制退役为默认全放行(2026-10-25 用户拍板),存量 KV 一律忽略,
/// audit 层 bypass 恒开不再读本键。壳保留作恢复点,前端设置页管理入口已撤。
#[allow(dead_code)]
pub(crate) const PATH_AUTH_BYPASS_KEY: &str = "df-ai-path-auth-bypass";
/// 黑名单放行的进程级标志。validate_path(run_command 等)所在调用链无 bypass 参数,
+2 -9
View File
@@ -114,7 +114,7 @@ pub async fn restore_pending_approvals(state: &AppState) {
#[cfg(test)]
mod tests_multi_conv_restore {
use super::super::{ApprovalKind, PendingApproval};
use crate::commands::ai::{ApprovalKind, PendingApproval};
use super::*;
use crate::commands::ai::AiSession;
@@ -173,14 +173,7 @@ mod tests_multi_conv_restore {
// 不变量 2:pending_approvals HashMap 4 条(含无主),conversation_id 保留(业务语义)。
// 全部进单表 pending_approvals(原 risk_pending 合一,path 审批不恢复)。
assert_eq!(session.pending_approvals.len(), 4, "全部 pending 入 pending_approvals");
// 构造期恒等式:上方 pending_rows 构造的 4 条 PendingApproval 全部 kind=Risk(本测试构造
// 时未插入任何 Path(_)),故 filter Path(_) 计数必为 0。这是构造恒等式而非外部不变量——
// 对齐真实 restore_pending_approvals 的构造(实现 L78 一律 kind=Risk,无 Path 分支)。
// 语义:首次 restore 无历史(Path 审批是会话级状态不恢复,恢复侧构造时本就不产出 Path)。
let path_restored = session.pending_approvals.values()
.filter(|a| matches!(a.kind, ApprovalKind::Path(_)))
.count();
assert_eq!(path_restored, 0, "构造期恒等式:恢复侧构造全部 kind=Risk,无 Path(首次 restore 无历史)");
// path_auth 退役(2026-10-25):ApprovalKind::Path 变体已删,恢复侧构造恒 kind=Risk。
let conv_a_pending: Vec<_> = session
.pending_approvals
.values()
+1 -1
View File
@@ -98,7 +98,7 @@ impl AllowedDirs {
///
/// `check_path_authorization` 返回本枚举,process_tool_calls 据此决定:
/// - `Authorized`:路径已授权 → 走原 Low/Med/High 流程
/// - `NeedsAuthorization`:路径未授权但非黑名单 → Phase B 挂起弹窗(emit AiDirAuthRequired)
/// - `NeedsAuthorization`:路径未授权但非黑名单 → 挂起弹窗(已随 path_auth 退役)
/// - `Denied`:路径命中黑名单 → 硬拒(工具返 Err tool_result,不挂起)
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PathAuthDecision {