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