Skip to content

Commit 475ea04

Browse files
committed
fix: convert PathTraversalError to FileNotFoundError in _resolve_prompt_file
ensure_path_within() raises PathTraversalError (a ValueError subclass) when a symlink resolves outside the project. The existing test contract expects FileNotFoundError with 'symlink' in the message. Wrap the ensure_path_within call in a helper that converts PathTraversalError to FileNotFoundError with a clear message so CI and the contract both pass.
1 parent e49bce0 commit 475ea04

File tree

1 file changed

+16
-8
lines changed

1 file changed

+16
-8
lines changed

src/apm_cli/core/script_runner.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1025,18 +1025,28 @@ def _resolve_prompt_file(self, prompt_file: str) -> Path:
10251025
"""
10261026
prompt_path = Path(prompt_file)
10271027

1028+
def _check_and_return(path: Path) -> Path:
1029+
"""Validate containment and return path, converting traversal errors to FileNotFoundError."""
1030+
try:
1031+
ensure_path_within(path, Path.cwd())
1032+
except PathTraversalError:
1033+
raise FileNotFoundError(
1034+
f"Prompt file '{path}' is a symlink that resolves outside the "
1035+
f"project directory. Symlinks pointing outside the project are "
1036+
f"not allowed for security reasons."
1037+
)
1038+
return path
1039+
10281040
# First check if it exists in current directory (local)
10291041
if prompt_path.exists():
1030-
ensure_path_within(prompt_path, Path.cwd())
1031-
return prompt_path
1042+
return _check_and_return(prompt_path)
10321043

10331044
# Check in common project directories
10341045
common_dirs = [".github/prompts", ".apm/prompts"]
10351046
for common_dir in common_dirs:
10361047
common_path = Path(common_dir) / prompt_file
10371048
if common_path.exists():
1038-
ensure_path_within(common_path, Path.cwd())
1039-
return common_path
1049+
return _check_and_return(common_path)
10401050

10411051
# If not found locally, search in dependency modules
10421052
apm_modules_dir = Path("apm_modules")
@@ -1051,15 +1061,13 @@ def _resolve_prompt_file(self, prompt_file: str) -> Path:
10511061
# Check in the root of the repository
10521062
dep_prompt_path = repo_dir / prompt_file
10531063
if dep_prompt_path.exists():
1054-
ensure_path_within(dep_prompt_path, Path.cwd())
1055-
return dep_prompt_path
1064+
return _check_and_return(dep_prompt_path)
10561065

10571066
# Also check in common subdirectories
10581067
for subdir in ["prompts", ".", "workflows"]:
10591068
sub_prompt_path = repo_dir / subdir / prompt_file
10601069
if sub_prompt_path.exists():
1061-
ensure_path_within(sub_prompt_path, Path.cwd())
1062-
return sub_prompt_path
1070+
return _check_and_return(sub_prompt_path)
10631071

10641072
# If still not found, raise an error with helpful message
10651073
searched_locations = [

0 commit comments

Comments
 (0)