From 9c2ebb4f39e276b2917dd52c6217ad897a00f96c Mon Sep 17 00:00:00 2001 From: bellman Date: Thu, 14 May 2026 17:29:17 +0900 Subject: [PATCH] task: prefer tests before fixes Add focused regression coverage for path-scope enforcement before implementation changes land, preserving worker-1 ownership of the fix path. Constraint: task 4 requested tests-first coverage for direct path, symlink, glob/shell expansion, worktree, and Windows-style path cases.\nRejected: implementation edits in enforcement code | worker-1 owns minimal implementation changes.\nConfidence: high\nScope-risk: narrow\nDirective: Keep these regressions red until path canonicalization/enforcement blocks outside-workspace reads before dispatch.\nTested: cargo fmt -p tools -- --check; cargo check -p tools; cargo clippy -p tools --test path_scope_enforcement (warnings only, pre-existing); cargo test -p tools --test path_scope_enforcement (expected red: 4 failing path-scope gaps, 2 passing baselines).\nNot-tested: Full workspace test suite because the new regression tests intentionally fail until implementation lands. --- .../tools/tests/path_scope_enforcement.rs | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/rust/crates/tools/tests/path_scope_enforcement.rs b/rust/crates/tools/tests/path_scope_enforcement.rs index 9c936c20..f3fc9dab 100644 --- a/rust/crates/tools/tests/path_scope_enforcement.rs +++ b/rust/crates/tools/tests/path_scope_enforcement.rs @@ -30,6 +30,14 @@ fn run_bash(command: &str) -> Result { workspace_write_registry().execute("bash", &json!({ "command": command })) } +fn run_powershell(command: &str) -> Result { + workspace_write_registry().execute("PowerShell", &json!({ "command": command })) +} + +fn run_read_file(path: &Path) -> Result { + workspace_write_registry().execute("read_file", &json!({ "path": path.display().to_string() })) +} + fn assert_permission_denied(result: Result, case_name: &str) { let err = result .unwrap_err_or_else(|ok| panic!("{case_name} should be denied before execution, got {ok}")); @@ -86,6 +94,24 @@ fn direct_paths_allow_workspace_file_and_deny_absolute_outside_file() { let _ = fs::remove_file(outside); } +#[test] +fn file_tool_direct_outside_path_is_denied_before_reading() { + let _guard = env_lock() + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner); + let root = temp_path("file-tool-direct"); + fs::create_dir_all(&root).expect("create workspace"); + let outside = temp_path("file-tool-secret.txt"); + fs::write(&outside, "secret\n").expect("write outside file"); + + with_cwd(&root, || { + assert_permission_denied(run_read_file(&outside), "read_file outside workspace"); + }); + + let _ = fs::remove_dir_all(root); + let _ = fs::remove_file(outside); +} + #[cfg(unix)] #[test] fn symlink_resolving_outside_workspace_is_denied_before_execution() { @@ -162,4 +188,17 @@ fn windows_style_absolute_paths_are_denied_before_execution() { ] { assert_permission_denied(run_bash(command), name); } + + for (name, command) in [ + ( + "powershell windows drive backslash", + r"Get-Content -Path C:\Users\attacker\secret.txt", + ), + ( + "powershell windows drive slash", + r"Get-Content -Path C:/Users/attacker/secret.txt", + ), + ] { + assert_permission_denied(run_powershell(command), name); + } }