Merge branch 'bugfix/resolve-safe-path-doubled-prefix'

This commit is contained in:
2026-03-11 08:48:02 -05:00
2 changed files with 23 additions and 2 deletions

View File

@@ -57,7 +57,10 @@ class AgentLoop:
"You are SneakyCode, a local AI coding agent. "
"You help users with software engineering tasks by reading files, "
"searching code, and answering questions about their project.\n\n"
f"Workspace root: {self._config.agent.workspace_root}\n\n"
f"Workspace root: {self._config.agent.workspace_root}\n"
"IMPORTANT: All tool path arguments must be RELATIVE to the workspace root. "
'Use "." for the root, "app/main.py" for files, "app/" for subdirectories. '
"Never pass absolute paths or the workspace root path itself.\n\n"
"Available tools: " + ", ".join(tool_names) + "\n\n"
"When you have fully completed the user's request, call the `finish` tool "
"with a brief summary. If you can answer directly without tools, just respond "

View File

@@ -18,6 +18,9 @@ class BinaryFileError(Exception):
def resolve_safe_path(path: str | Path, workspace_root: Path) -> Path:
"""Resolve a path and verify it is within the workspace root.
Handles absolute paths that point within the workspace (e.g. the LLM
passing the full workspace path) and relative paths alike.
Args:
path: The path to resolve (absolute or relative to workspace_root).
workspace_root: The allowed root directory (must be absolute).
@@ -29,7 +32,22 @@ def resolve_safe_path(path: str | Path, workspace_root: Path) -> Path:
PathSecurityError: If the resolved path is outside workspace_root.
"""
workspace_root = workspace_root.resolve()
resolved = (workspace_root / path).resolve()
path = Path(path)
# If the path is absolute and within the workspace, use it directly.
# If absolute but outside, it will fail the security check below.
if path.is_absolute():
resolved = path.resolve()
else:
# Strip workspace_root prefix if the LLM included it as a relative path
# e.g. "home/user/project/foo.py" when workspace is "/home/user/project"
path_str = str(path)
ws_str = str(workspace_root).lstrip("/")
if path_str.startswith(ws_str + "/"):
path_str = path_str[len(ws_str) + 1:]
elif path_str == ws_str:
path_str = "."
resolved = (workspace_root / path_str).resolve()
if not resolved.is_relative_to(workspace_root):
raise PathSecurityError(