fix(security): confine workspace existence checks

This commit is contained in:
Alexandre Teixeira 2026-10-06 15:18:34 +01:00
parent ee48c9c51e
commit c7961d178f
2 changed files with 81 additions and 6 deletions

View file

@ -16711,16 +16711,15 @@ def _existing_workspace_files(paths: list[str], workspace: Optional[str]) -> lis
"""
if not workspace:
return []
root = Path(str(workspace)).expanduser()
from src.tool_execution import _resolve_tool_path_in_workspace
existing: list[str] = []
for path in paths:
candidate = Path(path).expanduser()
if not candidate.is_absolute():
candidate = root / candidate
try:
if candidate.is_file():
resolved = _resolve_tool_path_in_workspace(str(workspace), str(path))
if os.path.isfile(resolved):
existing.append(path)
except OSError:
except (OSError, ValueError):
continue
return existing

View file

@ -0,0 +1,76 @@
from src.agent_loop import _existing_workspace_files
def test_existing_workspace_files_accepts_relative_file_inside_workspace(tmp_path):
workspace = tmp_path / "workspace"
workspace.mkdir()
target = workspace / "inside.py"
target.write_text("pass\n")
assert _existing_workspace_files(
["inside.py"],
str(workspace),
) == ["inside.py"]
def test_existing_workspace_files_accepts_absolute_file_inside_workspace(tmp_path):
workspace = tmp_path / "workspace"
workspace.mkdir()
target = workspace / "inside.py"
target.write_text("pass\n")
assert _existing_workspace_files(
[str(target)],
str(workspace),
) == [str(target)]
def test_existing_workspace_files_rejects_absolute_file_outside_workspace(tmp_path):
workspace = tmp_path / "workspace"
workspace.mkdir()
outside = tmp_path / "outside.py"
outside.write_text("pass\n")
assert _existing_workspace_files(
[str(outside)],
str(workspace),
) == []
def test_existing_workspace_files_rejects_parent_traversal(tmp_path):
workspace = tmp_path / "workspace"
workspace.mkdir()
outside = tmp_path / "outside.py"
outside.write_text("pass\n")
assert _existing_workspace_files(
["../outside.py"],
str(workspace),
) == []
def test_existing_workspace_files_rejects_symlink_escape(tmp_path):
workspace = tmp_path / "workspace"
workspace.mkdir()
outside = tmp_path / "outside.py"
outside.write_text("pass\n")
escape = workspace / "escape.py"
escape.symlink_to(outside)
assert _existing_workspace_files(
["escape.py"],
str(workspace),
) == []
def test_existing_workspace_files_supports_workspace_alias(tmp_path):
workspace = tmp_path / "workspace"
workspace.mkdir()
target = workspace / "inside.py"
target.write_text("pass\n")
assert _existing_workspace_files(
["/workspace/inside.py"],
str(workspace),
) == ["/workspace/inside.py"]