fix(file-tools): sanitize host/relative cwd override before it reaches container sandbox (#54447)

This commit is contained in:
Tranquil-Flow 2026-06-29 00:07:40 +02:00
parent 16ff1a3b93
commit 82132f7911
2 changed files with 133 additions and 0 deletions

View file

@ -144,3 +144,114 @@ class TestOverrideCwdSanitizedAtCallSite:
# RL/benchmark envs set an in-container path; it must pass through.
cwd = self._run_and_capture_cwd(monkeypatch, "/workspace/task42")
assert cwd == "/workspace/task42"
class TestFileOpsCwdSanitizedAtCallSite:
"""E2E pin: file tools (_get_file_ops) must sanitize a host/relative cwd
override before it reaches _create_environment on a container backend
the same guard the terminal tool got in #50636. Without it, a Desktop/TUI
host cwd (e.g. ``/Users/me/workspace``) leaks straight into
``docker run -w`` and ``search_files`` returns an empty workspace (#54447).
"""
def _run_and_capture_cwd(self, monkeypatch, override_cwd, env_type="docker",
config_cwd="/workspace"):
"""Drive ``_get_file_ops()`` on a container backend with a host-path cwd
override registered, and return the cwd that reached
``_create_environment`` (i.e. the cwd passed to ``docker run -w``).
"""
import tools.terminal_tool as tt
import tools.file_tools as ft
captured = {}
config = {
"env_type": env_type,
"docker_image": "pytorch/pytorch:latest",
"singularity_image": "docker://pytorch/pytorch:latest",
"modal_image": "pytorch/pytorch:latest",
"daytona_image": "pytorch/pytorch:latest",
"cwd": config_cwd,
"host_cwd": None,
"timeout": 180,
"lifetime_seconds": 300,
"container_cpu": 1,
"container_memory": 5120,
"container_disk": 51200,
"container_persistent": True,
"docker_volumes": [],
"docker_env": {},
"docker_extra_args": [],
"docker_mount_cwd_to_workspace": False,
"docker_run_as_host_user": False,
"docker_forward_env": [],
"modal_mode": "auto",
"ssh_host": "",
"ssh_user": "",
"ssh_port": 22,
"ssh_key": "",
"ssh_persistent": False,
"local_persistent": False,
}
class _DummyEnv:
cwd = config_cwd
def execute(self, *a, **k):
return {"output": "", "exit_code": 0}
def fake_create_environment(env_type, image, cwd, timeout, **kwargs):
captured["cwd"] = cwd
return _DummyEnv()
monkeypatch.setattr(tt, "_get_env_config", lambda: config)
monkeypatch.setattr(tt, "_start_cleanup_thread", lambda: None)
monkeypatch.setattr(tt, "_create_environment", fake_create_environment)
# Force a fresh environment build.
monkeypatch.setattr(tt, "_active_environments", {})
monkeypatch.setattr(tt, "_last_activity", {})
monkeypatch.setattr(ft, "_file_ops_cache", {})
monkeypatch.setattr(ft, "_last_known_cwd", {})
task_id = "sess-fileops-host-cwd"
tt.register_task_env_overrides(task_id, {"cwd": override_cwd})
try:
ft._get_file_ops(task_id)
finally:
tt.clear_task_env_overrides(task_id)
return captured.get("cwd")
def test_macos_host_override_does_not_reach_container(self, monkeypatch):
# Desktop/TUI registers /Users/<me>/workspace as the session cwd.
cwd = self._run_and_capture_cwd(monkeypatch, "/Users/me/workspace")
assert cwd == "/workspace", (
f"Host-path cwd override leaked to the container builder: {cwd!r}. "
"It must be sanitized back to config['cwd']."
)
def test_posix_home_host_override_does_not_reach_container(self, monkeypatch):
cwd = self._run_and_capture_cwd(monkeypatch, "/home/someuser/project")
assert cwd == "/workspace"
def test_windows_host_override_does_not_reach_container(self, monkeypatch):
cwd = self._run_and_capture_cwd(monkeypatch, r"C:\Users\someuser")
assert cwd == "/workspace"
def test_relative_cwd_override_does_not_reach_container(self, monkeypatch):
cwd = self._run_and_capture_cwd(monkeypatch, "src/app")
assert cwd == "/workspace"
def test_valid_container_override_is_preserved(self, monkeypatch):
# RL/benchmark envs set an in-container path; it must pass through.
cwd = self._run_and_capture_cwd(monkeypatch, "/workspace/task42")
assert cwd == "/workspace/task42"
def test_host_override_sanitized_on_singularity(self, monkeypatch):
cwd = self._run_and_capture_cwd(
monkeypatch, "/Users/me/workspace", env_type="singularity")
assert cwd == "/workspace"
def test_host_override_sanitized_on_modal(self, monkeypatch):
cwd = self._run_and_capture_cwd(
monkeypatch, "/Users/me/workspace", env_type="modal")
assert cwd == "/workspace"

View file

@ -832,6 +832,8 @@ def _get_file_ops(task_id: str = "default") -> ShellFileOperations:
_creation_locks,
_creation_locks_lock,
_resolve_container_task_id,
_is_unusable_container_cwd,
_CONTAINER_BACKENDS,
)
import time
@ -893,6 +895,26 @@ def _get_file_ops(task_id: str = "default") -> ShellFileOperations:
image = ""
cwd = overrides.get("cwd") or _last_known_cwd.get(task_id) or config["cwd"]
# Re-apply the container cwd guard that _get_env_config() already
# ran on config["cwd"] (see #50636). A per-task cwd override
# registered by the gateway/TUI/ACP for workspace tracking is a
# raw host path (e.g. a Desktop session's /Users/<me>/workspace or
# C:\\Users\\<me>). On a container backend that reaches
# ``docker run -w <host-path>`` and the container starts in a
# directory that doesn't exist inside the sandbox, so search_files
# and friends silently return empty results (#54447). Sanitize it
# back to the already-validated config["cwd"] so the override can't
# bypass the guard. Valid in-container override paths (RL/benchmark
# sandboxes that set cwd to /workspace, /root, etc.) are absolute
# non-host paths and pass through untouched.
if env_type in _CONTAINER_BACKENDS and _is_unusable_container_cwd(cwd):
if cwd != config["cwd"]:
logger.info(
"Ignoring host/relative cwd override %r for %s backend "
"(won't exist in sandbox). Using %r instead.",
cwd, env_type, config["cwd"],
)
cwd = config["cwd"]
logger.info("Creating new %s environment for task %s...", env_type, task_id[:8])
container_config = None