T09 · Insecure Skill Coding Practices
- Location
scripts/main.py:62- Finding
Unrestricted Output Path Allows Arbitrary File Overwrite
- Content
View full analysis
Vulnerability Details
File Location:
scripts/main.py, lines 62-79
Vulnerability Type: Unrestricted file write and file overwrite
Risk Level: MediumVulnerable Code:
python parser.add_argument("--output", "-o", help="Output file") args = parser.parse_args() generator = MentorshipAgenda() topics = None if args.topics: topics = [t.strip() for t in args.topics.split(",")] agenda = generator.generate_agenda(args.student, args.phase, topics) print(agenda) if args.output: with open(args.output, 'w') as f: f.write(agenda) print(f"\nSaved to: {args.output}")Technical Analysis
The
--outputargument is passed directly toopen()without path validation, canonicalization, workspace confinement, symlink checks, or overwrite protection. Python'sopen(..., 'w')operation creates the target file if it does not exist and truncates it if it already exists.Consequently, the argument can contain an absolute path or a traversal path such as
../../target-file. The operating system will resolve that path using the current working directory and the invoking process's permissions. This behavior contradicts the security expectations inSKILL.md, which identify traversal validation and workspace-restricted output as required controls.Attack Path
- An attacker influences the
--outputvalue passed to the Skill, directly or through an automated agent workflow. - The attacker supplies an absolute path or directory-traversal path that identifies an existing writable file.
- The agent invokes
scripts/main.pywith the crafted argument. - The program passes the path directly to
open(args.output, 'w'). - The target file is truncated and replaced with generated agenda content.
Example:
bash python scripts/main.py \ --student "Alice" \ --phase early \ --output "../../writable-target"A symlink placed at the selected ...[truncated 622 chars]
- An attacker influences the
- Remediation
View remediation
Remediation Suggestions
- Define a trusted output directory inside the workspace and resolve all requested output paths relative to it.
- Reject absolute paths and any canonicalized path that falls outside the approved directory.
- Use
pathlib.Path.resolve()followed by a containment check against the resolved workspace directory. - Reject symlinks or securely verify that the final destination and its parent components do not redirect outside the approved directory.
- Avoid silently truncating existing files. Use exclusive creation mode (
"x") by default or require explicit overwrite authorization. - Run the Skill with least-privilege filesystem permissions.
Example hardening pattern:
python from pathlib import Path output_root = Path.cwd().resolve() requested = Path(args.output) if requested.is_absolute(): parser.error("Absolute output paths are not allowed") destination = (output_root / requested).resolve() if destination != output_root and output_root not in destination.parents: parser.error("Output path must remain inside the workspace") if destination.exists() or destination.is_symlink(): parser.error("Refusing to overwrite an existing file") with destination.open("x", encoding="utf-8") as f: f.write(agenda)
