Fix doubled workspace path when LLM passes absolute-style relative paths
resolve_safe_path now detects when the LLM passes the workspace root as a relative path (e.g. "home/user/project/file.py") and strips the prefix before joining. Also updated the system prompt to explicitly instruct the model to use relative paths for all tool arguments. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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 "
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user