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.
This commit is contained in:
bellman
2026-05-14 17:29:17 +09:00
parent 2c48400293
commit 9c2ebb4f39

View File

@@ -30,6 +30,14 @@ fn run_bash(command: &str) -> Result<String, String> {
workspace_write_registry().execute("bash", &json!({ "command": command }))
}
fn run_powershell(command: &str) -> Result<String, String> {
workspace_write_registry().execute("PowerShell", &json!({ "command": command }))
}
fn run_read_file(path: &Path) -> Result<String, String> {
workspace_write_registry().execute("read_file", &json!({ "path": path.display().to_string() }))
}
fn assert_permission_denied(result: Result<String, String>, 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);
}
}