fix(security): block runtime config state edits

This commit is contained in:
李龙 0668001470
2026-03-16 17:21:22 +08:00
committed by Roman Tataurov
parent d43ce63b4d
commit a2fdf64a5e
3 changed files with 161 additions and 0 deletions
+67
View File
@@ -1193,6 +1193,44 @@ impl SecurityPolicy {
false
}
fn runtime_config_dir(&self) -> Option<PathBuf> {
let parent = self.workspace_dir.parent()?;
Some(
parent
.canonicalize()
.unwrap_or_else(|_| parent.to_path_buf()),
)
}
pub fn is_runtime_config_path(&self, resolved: &Path) -> bool {
let Some(config_dir) = self.runtime_config_dir() else {
return false;
};
if !resolved.starts_with(&config_dir) {
return false;
}
if resolved.parent() != Some(config_dir.as_path()) {
return false;
}
let Some(file_name) = resolved.file_name().and_then(|value| value.to_str()) else {
return false;
};
file_name == "config.toml"
|| file_name == "config.toml.bak"
|| file_name == "active_workspace.toml"
|| file_name.starts_with(".config.toml.tmp-")
|| file_name.starts_with(".active_workspace.toml.tmp-")
}
pub fn runtime_config_violation_message(&self, resolved: &Path) -> String {
format!(
"Refusing to modify ZeroClaw runtime config/state file: {}. Use dedicated config tools or edit it manually outside the agent loop.",
resolved.display()
)
}
pub fn resolved_path_violation_message(&self, resolved: &Path) -> String {
let guidance = if self.allowed_roots.is_empty() {
"Add the directory to [autonomy].allowed_roots (for example: allowed_roots = [\"/absolute/path\"]), or move the file into the workspace."
@@ -2787,4 +2825,33 @@ mod tests {
};
assert!(!p.is_under_allowed_root("/any/path"));
}
#[test]
fn runtime_config_paths_are_protected() {
let workspace = PathBuf::from("/tmp/zeroclaw-profile/workspace");
let policy = SecurityPolicy {
workspace_dir: workspace.clone(),
..SecurityPolicy::default()
};
let config_dir = workspace.parent().unwrap();
assert!(policy.is_runtime_config_path(&config_dir.join("config.toml")));
assert!(policy.is_runtime_config_path(&config_dir.join("config.toml.bak")));
assert!(policy.is_runtime_config_path(&config_dir.join(".config.toml.tmp-1234")));
assert!(policy.is_runtime_config_path(&config_dir.join("active_workspace.toml")));
assert!(policy.is_runtime_config_path(&config_dir.join(".active_workspace.toml.tmp-1234")));
}
#[test]
fn workspace_files_are_not_runtime_config_paths() {
let workspace = PathBuf::from("/tmp/zeroclaw-profile/workspace");
let policy = SecurityPolicy {
workspace_dir: workspace.clone(),
..SecurityPolicy::default()
};
let nested_dir = workspace.join("notes");
assert!(!policy.is_runtime_config_path(&workspace.join("notes.txt")));
assert!(!policy.is_runtime_config_path(&nested_dir.join("config.toml")));
}
}