T09 · Insecure Skill Coding Practices
- Location
scripts/pre_commit_gen.py:532- Finding
Unrestricted Output Path Allows Writes Outside the Target Project
- Content
View full analysis
Vulnerability Details
File Location:
scripts/pre_commit_gen.py, lines 532-539; related argument definition at lines 486-487
Vulnerability Type: Path traversal and arbitrary file write
Risk Level: MediumVulnerable Code:
python ap.add_argument("--output", "-o", default=".pre-commit-config.yaml", help="Output file (default: .pre-commit-config.yaml)")python out_path = root / args.output if out_path.exists() and not args.overwrite: print(f"\n⚠️ {out_path} already exists.") print(" Use --overwrite to replace, or --audit to check the existing file.") print(" Use --dry-run to preview the generated config.") sys.exit(0) out_path.write_text(yaml_output + "\n", encoding="utf-8")Technical Analysis
The user-controlled
--outputvalue is joined to the project root without validating that the resulting path remains within that root. Python'spathlibpermits both relevant escape mechanisms:- If
args.outputis absolute,root / args.outputresolves to the absolute path and effectively discardsroot. - If it contains parent-directory components such as
../, the resulting path can reference a location outside the target project.
The destination is not resolved and checked against
rootbeforewrite_text()is called. Consequently, the script can create a new file outside the project. If--overwriteis also supplied, it can replace an existing file wherever the invoking account has write permission.Attack Path
- An attacker influences the arguments supplied by an Agent, automation process, or user invoking the skill.
- The attacker supplies a traversal or absolute output path, such as:
bash python3 scripts/pre_commit_gen.py /project \ --output ../../writable-target.yaml \ --overwrite - The script constructs
out_pathwithout containment validation. - The generated YAML is written ...[truncated 1174 chars]
- If
- Remediation
View remediation
Remediation Suggestions
Resolve and validate the output path before writing:
python root = Path(args.target).resolve() requested = Path(args.output) if requested.is_absolute(): print("Error: --output must be relative to the project root", file=sys.stderr) sys.exit(2) out_path = (root / requested).resolve() try: out_path.relative_to(root) except ValueError: print("Error: --output must remain inside the project root", file=sys.stderr) sys.exit(2)Additional hardening should include:
- Reject absolute output paths and paths that resolve outside
root. - Use exclusive creation, such as mode
x, when overwrite was not explicitly requested. - Verify that the destination's parent directories do not traverse symlinks outside the project if the threat model includes attacker-controlled repository contents.
- For overwrite operations, verify the final resolved path again immediately before writing.
- Consider restricting output to the exact filename
.pre-commit-config.yamlunless custom destinations are required.
- Reject absolute output paths and paths that resolve outside
