fix: skip snapshots for dangerous workspaces (#804)

This commit is contained in:
ccomma
2026-05-06 15:14:59 +08:00
committed by GitHub
parent a2ca64018e
commit 0d50cab251
+80 -6
View File
@@ -61,12 +61,15 @@ impl SnapshotRepo {
let work_tree = workspace
.canonicalize()
.unwrap_or_else(|_| workspace.to_path_buf());
// Refuse to snapshot the user's home directory: `git add -A` on $HOME
// can consume unbounded disk/CPU and effectively DoS the TUI (#793).
if is_home_directory(&work_tree, dirs::home_dir().as_deref()) {
return Err(io_other(
"refusing to snapshot home directory - start deepseek from a project directory instead",
if let Some(reason) =
unsafe_workspace_snapshot_reason(&work_tree, dirs::home_dir().as_deref())
{
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!(
"workspace snapshots are disabled for {reason}: {}",
work_tree.display()
),
));
}
@@ -415,6 +418,40 @@ fn io_other(msg: impl Into<String>) -> io::Error {
io::Error::other(msg.into())
}
fn unsafe_workspace_snapshot_reason(workspace: &Path, home: Option<&Path>) -> Option<&'static str> {
let workspace = normalize_path_for_safety(workspace);
if is_filesystem_root(&workspace) {
return Some("filesystem root");
}
if is_home_directory(&workspace, home) {
return Some("home directory");
}
let home = home.map(normalize_path_for_safety)?;
if workspace.parent() == Some(home.as_path()) {
let name = workspace.file_name().and_then(|name| name.to_str());
if matches!(
name,
Some(
"Desktop" | "Documents" | "Downloads" | "Library" | "Movies" | "Music" | "Pictures"
)
) {
return Some("home collection directory");
}
}
None
}
fn normalize_path_for_safety(path: &Path) -> PathBuf {
path.canonicalize().unwrap_or_else(|_| path.to_path_buf())
}
fn is_filesystem_root(path: &Path) -> bool {
path.parent().is_none()
}
fn is_home_directory(work_tree: &Path, home: Option<&Path>) -> bool {
let Some(home) = home else {
return false;
@@ -676,6 +713,43 @@ mod tests {
);
}
#[test]
fn unsafe_workspace_rejects_home_directory_workspace() {
let tmp = tempdir().unwrap();
let home = tmp.path();
assert_eq!(
unsafe_workspace_snapshot_reason(home, Some(home)),
Some("home directory")
);
}
#[test]
fn unsafe_workspace_rejects_home_collection_directories() {
let tmp = tempdir().unwrap();
let home = tmp.path();
let desktop = tmp.path().join("Desktop");
std::fs::create_dir_all(&desktop).unwrap();
assert_eq!(
unsafe_workspace_snapshot_reason(&desktop, Some(home)),
Some("home collection directory")
);
}
#[test]
fn unsafe_workspace_allows_project_directories_under_home() {
let tmp = tempdir().unwrap();
let home = tmp.path();
let workspace = tmp.path().join("code").join("project");
std::fs::create_dir_all(&workspace).unwrap();
assert_eq!(
unsafe_workspace_snapshot_reason(&workspace, Some(home)),
None
);
}
#[test]
fn open_or_init_is_idempotent() {
let tmp = tempdir().unwrap();