T09 · Insecure Skill Coding Practices
- Location
scripts/main.py:130- Finding
Unrestricted Output Path Allows Arbitrary File Overwrite
- Content
View full analysis
Vulnerability Details
File Location:
scripts/main.py:130-144
Vulnerability Type: Unrestricted user-controlled file write
Risk Level: MediumVulnerable Code
python parser.add_argument( "--output", "-o", type=str, help="Output JSON file path (optional)" ) args = parser.parse_args() # Output output = json.dumps(result, indent=2) if args.output: with open(args.output, 'w', encoding='utf-8') as f: f.write(output) print(f"Result saved to: {args.output}") else: print(output)Technical Analysis
The
--outputargument is accepted as an unrestricted path and passed directly toopen()in write mode. The implementation does not resolve and validate the destination against an approved workspace directory, reject path traversal, detect symbolic links, or prevent replacement of an existing file.Because mode
'w'truncates an existing file before writing, any file writable by the process can be replaced with the generated JSON. Relative traversal paths, absolute paths, and paths referencing symbolic links can all potentially reach files outside the intended project workspace. This also conflicts with the output-directory restriction identified in the security checklist inSKILL.md.Attack Path
- An attacker or untrusted caller controls the command-line arguments used to invoke the Skill.
- The caller supplies a destination outside the intended workspace, such as:
bash python scripts/main.py \ --type followup \ --date 2024-03-01 \ --output ../../writable-target - Alternatively, the caller points
--outputto a symbolic link whose target is a sensitive writable file. - The path is passed to
open(args.output, 'w')without validation. - Python follows the path and truncates the target.
- The target's previous contents are replaced with the calculation result.
Impact Assessment
E ...[truncated 481 chars]
- Remediation
View remediation
Remediation Suggestions
- Define an explicit approved output directory and resolve both it and the requested destination with
pathlib.Path.resolve(). - Reject the destination unless it is a descendant of the approved directory.
- Reject absolute paths when they are not required.
- Reject symbolic-link destinations and verify that relevant parent directories are not symbolic links.
- Avoid silently overwriting existing files. Use exclusive creation mode (
'x') where replacement is unnecessary. - If replacement is required, write to a securely created temporary file in the same approved directory and atomically replace the intended destination.
- Run the Skill under a least-privileged operating-system account with write permission restricted to the designated output directory.
- Return a sanitized error when validation fails without exposing sensitive filesystem details.
Example workspace-bound validation:
python from pathlib import Path output_root = (Path.cwd() / "output").resolve() output_root.mkdir(parents=True, exist_ok=True) requested = Path(args.output) if requested.is_absolute(): raise ValueError("Absolute output paths are not allowed") destination = (output_root / requested).resolve() if output_root not in destination.parents: raise ValueError("Output path must remain within the output directory") if destination.exists() or destination.is_symlink(): raise ValueError("Refusing to overwrite an existing destination") with destination.open("x", encoding="utf-8") as f: f.write(output)- Define an explicit approved output directory and resolve both it and the requested destination with
