重构: workspace_root 弱化方案①去默认白名单
default_with_root 不再编译期硬塞 CARGO_MANIFEST_DIR(分发后无效路径)+ reload_allowed_dirs KV 空时不兜底塞。用户首次访问走弹窗三档授权,对齐产品定位(不预设源码目录)。3 测试改显式构造。
This commit is contained in:
@@ -450,19 +450,19 @@ impl AllowedDirs {
|
||||
/// Settings KV key:F-260619-03 Phase A 持久化白名单(JSON 字符串数组)
|
||||
pub const SETTINGS_KEY: &'static str = "allowed_dirs";
|
||||
|
||||
/// 仅含 workspace_root 的默认白名单(向后兼容:无 allowed_dirs 时行为不变)。
|
||||
/// 用于 mod.rs trust_key_for 等无白名单上下文的旧路径(零回归)。
|
||||
/// 空白名单(方案①弱化 workspace_root:不再编译期硬塞开发机 CARGO_MANIFEST_DIR)。
|
||||
/// 分发后该路径指向编译机不存在的目录,硬塞反成脏白名单;用户首次访问任意目录
|
||||
/// 走弹窗三档授权(once/session/always),对齐产品定位(不预设源码目录)。仅作 init
|
||||
/// 占位(reload_allowed_dirs 从 KV 覆盖)+ 测试构造基线。
|
||||
pub fn default_with_root() -> Self {
|
||||
let mut set = HashSet::new();
|
||||
set.insert(workspace_root_path());
|
||||
Self { persistent: set, session: HashSet::new(), once: HashSet::new() }
|
||||
Self { persistent: HashSet::new(), session: HashSet::new(), once: HashSet::new() }
|
||||
}
|
||||
|
||||
/// 路径是否被授权:persistent 或 session 白名单任一 starts_with 命中即放行。
|
||||
///
|
||||
/// **workspace_root 默认在 persistent**(default_with_root 初始插入,reload_allowed_dirs
|
||||
/// KV 未配时保持),故首次访问工程根免授权(向后兼容)。用户从白名单删除工程根后,
|
||||
/// 工程根也需授权(动态白名单完整语义,F-260619-03 收尾:去掉代码硬编码固定放行)。
|
||||
/// **方案①弱化后 workspace_root 不再默认授权**(default_with_root 空,reload_allowed_dirs
|
||||
/// KV 未配时也不塞),首次访问工程根走弹窗三档授权。用户授权(always/session/once)后
|
||||
/// 落白名单,后续命中放行(动态白名单完整语义,F-260619-03 收尾)。
|
||||
///
|
||||
/// **Phase C 黑名单优先**:即使白名单命中,若路径落入系统敏感目录(Windows
|
||||
/// `\Windows\System32` / `\Program Files\`;Unix `/etc /usr /bin /sbin /boot
|
||||
@@ -705,7 +705,7 @@ impl AppState {
|
||||
// 串联,min(3,3)=3,有效上限同未配置 → 行为零变化。
|
||||
state.reload_provider_caps().await;
|
||||
// F-260619-03 Phase A: 从 Settings KV 加载持久化授权目录白名单覆盖默认值。
|
||||
// 失败(读 KV/解析 JSON 出错)不阻断启动,保持 default_with_root(仅 workspace_root)。
|
||||
// 失败(读 KV/解析 JSON 出错)不阻断启动,保持 default_with_root(方案①后为空)。
|
||||
state.reload_allowed_dirs().await;
|
||||
// P0(设置走查):从 Settings KV 恢复持久化知识库配置覆盖 default(防 8 项重启全丢)。
|
||||
state.reload_knowledge_config().await;
|
||||
@@ -755,7 +755,7 @@ impl AppState {
|
||||
/// F-260619-03 Phase A: 从 Settings KV `allowed_dirs`(JSON 字符串数组)加载持久化白名单。
|
||||
///
|
||||
/// 启动 + Settings IPC `ai_set_allowed_dirs` 写入后调用。解析失败/缺失 → 保持
|
||||
/// default_with_root(仅 workspace_root,向后兼容:is_authorized 始终放行 workspace_root)。
|
||||
/// default_with_root(方案①后为空,首次访问走弹窗授权)。
|
||||
/// 每条路径尝试 canonicalize 规范化(防大小写/分隔符差异绕过);canonicalize 失败
|
||||
/// (目录不存在)回退原字面量 trim(写入后再校验场景:先授权目录路径,目录暂不存在)。
|
||||
pub async fn reload_allowed_dirs(&self) {
|
||||
@@ -789,10 +789,8 @@ impl AppState {
|
||||
all_dirs.sort();
|
||||
all_dirs.dedup();
|
||||
let mut set = HashSet::new();
|
||||
// KV 未配(空)时保留 workspace_root(向后兼容:默认根免授权)
|
||||
if all_dirs.is_empty() {
|
||||
set.insert(workspace_root_path());
|
||||
}
|
||||
// 方案①:KV + 项目绑定均空时不再硬塞 workspace_root(分发后该路径无效)。
|
||||
// 用户首次访问任意目录走弹窗三档授权,授权后落 KV 持久。
|
||||
for d in all_dirs {
|
||||
let d = d.trim();
|
||||
if d.is_empty() {
|
||||
@@ -978,12 +976,14 @@ mod tests {
|
||||
// 锁定:workspace_root 始终授权 + persistent 命中放行 + 未命中拒绝。
|
||||
// ============================================================
|
||||
|
||||
/// workspace_root 路径在 default_with_root 白名单内授权通过
|
||||
/// 显式授权 workspace_root 后命中(方案①:default_with_root 不再预设,需显式授权)
|
||||
#[test]
|
||||
fn test_allowed_dirs_workspace_root_authorized() {
|
||||
let allowed = AllowedDirs::default_with_root();
|
||||
let mut set = HashSet::new();
|
||||
set.insert(workspace_root_path());
|
||||
let allowed = AllowedDirs { persistent: set, session: HashSet::new(), once: HashSet::new() };
|
||||
let root = workspace_root_path();
|
||||
assert!(allowed.is_authorized(&root), "workspace_root 应被授权");
|
||||
assert!(allowed.is_authorized(&root), "显式授权的 workspace_root 应命中");
|
||||
// workspace 内子路径也应授权(starts_with workspace_root)
|
||||
let child = root.join("src").join("main.rs");
|
||||
assert!(allowed.is_authorized(&child), "workspace_root 子路径应被授权");
|
||||
@@ -1100,10 +1100,12 @@ mod tests {
|
||||
// F-260619-03 Phase B/C: check_path_authorization 三态决策测试
|
||||
// ============================================================
|
||||
|
||||
/// Phase B/C: workspace_root 路径 → Authorized
|
||||
/// Phase B/C: 显式授权 workspace_root 后,内路径 → Authorized(方案①:不再默认)
|
||||
#[test]
|
||||
fn test_check_path_authorized_workspace() {
|
||||
let allowed = AllowedDirs::default_with_root();
|
||||
let mut set = HashSet::new();
|
||||
set.insert(workspace_root_path());
|
||||
let allowed = AllowedDirs { persistent: set, session: HashSet::new(), once: HashSet::new() };
|
||||
let rel = "src/main.rs";
|
||||
match check_path_authorization(rel, &allowed) {
|
||||
PathAuthDecision::Authorized => {}
|
||||
@@ -1155,7 +1157,9 @@ mod tests {
|
||||
/// persistent 用动态 workspace_root_path(非硬编码开发机路径),candidate 拼 verbatim 前缀。
|
||||
#[test]
|
||||
fn test_is_authorized_strips_verbatim_prefix() {
|
||||
let allowed = AllowedDirs::default_with_root();
|
||||
let mut set = HashSet::new();
|
||||
set.insert(workspace_root_path());
|
||||
let allowed = AllowedDirs { persistent: set, session: HashSet::new(), once: HashSet::new() };
|
||||
let child = workspace_root_path().join("src").join("main.rs");
|
||||
// candidate 带 verbatim 前缀(handler canonicalize 后形态)应命中白名单
|
||||
let verbatim = PathBuf::from(format!("{}{}", r"\\?\", child.to_string_lossy()));
|
||||
|
||||
Reference in New Issue
Block a user