T09 · Insecure Skill Coding Practices
- Location
scripts/coser_card.py:2356- Finding
Unsanitized Profile Name Permits Output Path Traversal
- Content
View full analysis
Vulnerability Details
File Location:
scripts/coser_card.py:2356
Vulnerability Type: Path traversal and arbitrary file creation or overwrite
Risk Level: MediumVulnerable Code
python # Generate each requested format for fmt in formats: if fmt not in FORMAT_SIZES: print(f"Warning: unknown format {fmt}; skipping") continue output_file = os.path.join( args.output, f"{profile.name}_{args.template}_{args.style}_{fmt}.jpg" ) generate_card( profile, args.template, args.style, fmt, output_file, args.watermark )The resulting path is subsequently used by the following file-writing logic in
generate_card:python # Save os.makedirs(os.path.dirname(output_path) or ".", exist_ok=True) canvas.save(output_path, quality=95)The same vulnerable construction also appears in the batch-preview path at
scripts/coser_card.py:1759:python preview_path = os.path.join( output_dir, f"{profile.name}_batch_preview_{format_type}.jpg" ) os.makedirs(output_dir, exist_ok=True) canvas.save(preview_path, quality=90)Technical Analysis
profile.namecan originate from the--namecommand-line argument, interactive input, or a JSON configuration file. It is embedded directly into an output filename without rejecting directory separators, absolute path syntax, or traversal components such as...os.path.join()does not establish a security boundary. Traversal components in the generated filename are preserved and resolved by the operating system when Pillow opens the destination for writing. In the normal generation path,generate_card()also creates the derived parent directories before saving the image.Consequently, a user or upstream agent that supplies an attacker-controlled profile name can cause the generated JPEG to be written outside the directory selected by
--output.Attack Path
- The attacker controls or ...[truncated 1237 chars]
- Remediation
View remediation
Remediation Suggestions
-
Convert the profile name into a strict filename-safe slug. Prefer an allowlist of letters, digits, underscores, and hyphens.
-
Reject names containing
/,\,.., NUL bytes, control characters, drive prefixes, or absolute-path syntax. -
Resolve both the output directory and final output path, then verify containment before writing:
python from pathlib import Path import re def safe_filename_component(value: str) -> str: value = re.sub(r"[^A-Za-z0-9_-]+", "_", value).strip("_") if not value: raise ValueError("The profile name does not contain a safe filename component") return value output_root = Path(args.output).resolve() safe_name = safe_filename_component(profile.name) output_file = ( output_root / f"{safe_name}_{args.template}_{args.style}_{fmt}.jpg" ).resolve() if output_root not in output_file.parents: raise ValueError("Output path escapes the configured output directory") -
Apply the same validation and containment check to batch-preview output paths.
-
Avoid silently overwriting existing files. Use an explicit overwrite option or exclusive file-creation strategy where practical.
-
Add tests covering
../, absolute paths, Windows drive paths, mixed separators, empty sanitized names, and valid Unicode names.
-
