T09 · Insecure Skill Coding Practices
- Location
scripts/run_eval.py:34- Finding
Path Traversal Enables Artifact Creation Outside the Intended Run Directory
- Content
View full analysis
Vulnerability Details
File Location:
scripts/run_eval.py:34-38
Vulnerability Type: Unsanitized path construction and path traversal
Risk Level: MediumVulnerable Code
python def resolve_run_dirs(skill_dir: Path, run_group: str | None, mode: str) -> tuple[str, Path]: resolved_group = run_group or run_stamp() run_root = skill_dir / "evals" / "runs" / resolved_group run_dir = run_root / mode run_dir.mkdir(parents=True, exist_ok=False) return resolved_group, run_dirThe resulting uncontained directory is subsequently used for writes at
scripts/run_eval.py:342-344:python (run_dir / "summary.json").write_text(json.dumps(summary, ensure_ascii=False, indent=2) + "\n") (run_dir / "summary.md").write_text(make_summary_md(skill_dir.name, summary)) (run_dir / "run_metadata.json").write_text(json.dumps(run_metadata, ensure_ascii=False, indent=2) + "\n")Technical Analysis
The user-controlled
--run-groupargument is appended directly to the nominalevals/runsroot. The implementation does not reject absolute paths, parent-directory components such as.., or path separators. It also does not resolve the resulting path and verify that it remains beneath the authorized run root.In Python's
pathlib, parent-directory components can escape the intended root, while joining an absolute path can discard the preceding path components entirely. Consequently,run_dir.mkdir()and the subsequentwrite_text()operations can act outside the target Skill directory.The
modecomponent is constrained byargparsechoices, but this does not mitigate traversal throughresolved_group.Attack Path
- The attacker can invoke
run_eval.pyand control--run-group. - The attacker supplies an absolute path or a traversal sequence, for example:
bash python3 scripts/run_eval.py /path/to/skill \ --run-group ../../../../attacker-selected-dire
...[truncated 1312 chars]
- The attacker can invoke
- Remediation
View remediation
Remediation Suggestions
-
Restrict run-group names to simple identifiers:
python import re RUN_GROUP_PATTERN = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$") def validate_run_group(value: str) -> str: if value in {".", ".."} or not RUN_GROUP_PATTERN.fullmatch(value): raise SystemExit( "Error: run-group may contain only letters, digits, dots, " "underscores, and hyphens." ) return value -
Resolve and enforce containment before creating anything:
python runs_root = (skill_dir / "evals" / "runs").resolve() resolved_group = validate_run_group(run_group or run_stamp()) run_root = (runs_root / resolved_group).resolve() if not run_root.is_relative_to(runs_root): raise SystemExit("Error: run-group escapes the evals/runs directory.") -
Perform the same containment check on
run_dirbefore callingmkdir()or writing files. -
If target directories may be writable by untrusted users, reject symlinked path components or open files through directory-relative, no-follow mechanisms to reduce symlink race risks.
-
Add regression tests covering absolute paths,
../traversal, nested separators,.,.., and symlink-based escape attempts.
-
