diff --git a/tests/tools/test_file_write_safety.py b/tests/tools/test_file_write_safety.py index 12bc1ccacb..e2eef17ab1 100644 --- a/tests/tools/test_file_write_safety.py +++ b/tests/tools/test_file_write_safety.py @@ -79,5 +79,33 @@ class TestSafeWriteRoot: assert _is_write_denied(os.path.expanduser("~/.ssh/id_rsa")) is True +class TestCheckSensitivePathMacOSBypass: + """Verify _check_sensitive_path blocks /private/etc paths (issue #8734).""" + + def test_etc_hosts_blocked(self): + from tools.file_tools import _check_sensitive_path + assert _check_sensitive_path("/etc/hosts") is not None + + def test_private_etc_hosts_blocked(self): + from tools.file_tools import _check_sensitive_path + assert _check_sensitive_path("/private/etc/hosts") is not None + + def test_private_etc_ssh_config_blocked(self): + from tools.file_tools import _check_sensitive_path + assert _check_sensitive_path("/private/etc/ssh/sshd_config") is not None + + def test_private_var_blocked(self): + from tools.file_tools import _check_sensitive_path + assert _check_sensitive_path("/private/var/db/something") is not None + + def test_boot_still_blocked(self): + from tools.file_tools import _check_sensitive_path + assert _check_sensitive_path("/boot/grub/grub.cfg") is not None + + def test_safe_path_allowed(self): + from tools.file_tools import _check_sensitive_path + assert _check_sensitive_path("/tmp/safe_file.txt") is None + + if __name__ == "__main__": pytest.main([__file__, "-v"]) diff --git a/tools/file_tools.py b/tools/file_tools.py index 186a9d052c..5aa2d793e2 100644 --- a/tools/file_tools.py +++ b/tools/file_tools.py @@ -92,7 +92,10 @@ def _is_blocked_device(filepath: str) -> bool: # Paths that file tools should refuse to write to without going through the # terminal tool's approval system. These match prefixes after os.path.realpath. -_SENSITIVE_PATH_PREFIXES = ("/etc/", "/boot/", "/usr/lib/systemd/") +_SENSITIVE_PATH_PREFIXES = ( + "/etc/", "/boot/", "/usr/lib/systemd/", + "/private/etc/", "/private/var/", +) _SENSITIVE_EXACT_PATHS = {"/var/run/docker.sock", "/run/docker.sock"} @@ -102,17 +105,16 @@ def _check_sensitive_path(filepath: str) -> str | None: resolved = os.path.realpath(os.path.expanduser(filepath)) except (OSError, ValueError): resolved = filepath + normalized = os.path.normpath(os.path.expanduser(filepath)) + _err = ( + f"Refusing to write to sensitive system path: {filepath}\n" + "Use the terminal tool with sudo if you need to modify system files." + ) for prefix in _SENSITIVE_PATH_PREFIXES: - if resolved.startswith(prefix): - return ( - f"Refusing to write to sensitive system path: {filepath}\n" - "Use the terminal tool with sudo if you need to modify system files." - ) - if resolved in _SENSITIVE_EXACT_PATHS: - return ( - f"Refusing to write to sensitive system path: {filepath}\n" - "Use the terminal tool with sudo if you need to modify system files." - ) + if resolved.startswith(prefix) or normalized.startswith(prefix): + return _err + if resolved in _SENSITIVE_EXACT_PATHS or normalized in _SENSITIVE_EXACT_PATHS: + return _err return None