T09 · Insecure Skill Coding Practices
- Location
scripts/design_system.py:711- Finding
Path Traversal Enables Arbitrary Markdown File Creation or Overwrite
- Content
View full analysis
Vulnerability Details
File Location:
scripts/search.py:83-86, 101-113;scripts/design_system.py:711-740
Vulnerability Type: Path traversal and unrestricted file write
Risk Level: HighVulnerable Code
scripts/search.py:83-86accepts untrusted path-related values:python parser.add_argument("--project-name", "-p", type=str, default=None, help="Project name for design system output") parser.add_argument("--persist", action="store_true", help="Save design system to design-system/MASTER.md (creates hierarchical structure)") parser.add_argument("--page", type=str, default=None, help="Create page-specific override file in design-system/pages/") parser.add_argument("--output-dir", "-o", type=str, default=None, help="Output directory for persisted files (default: current directory)")scripts/search.py:101-113forwards these values to the persistence implementation without validation:python result = generate_design_system( args.query, args.project_name, args.format, persist=args.persist, page=args.page, output_dir=args.output_dir, variance=args.variance, motion=args.motion, density=args.density )scripts/design_system.py:711-740directly incorporates the values into filesystem paths and writes files:python base_dir = Path(output_dir) if output_dir else Path.cwd() # Use project name for project-specific folder. Coalesce falsy values # (missing key, explicit None, or "") so the .lower() below can't crash. project_name = design_system.get("project_name") or "default" project_slug = project_name.lower().replace(' ', '-') design_system_dir = base_dir / "design-system" / project_slug pages_dir = design_system_dir / "pages" created_files = [] # Create directories design_system_dir.mkdir(parents=True, exist_ok=True) pages_dir ...[truncated 3293 chars]- Remediation
View remediation
Remediation Suggestions
-
Convert project and page names to strict slugs using an allowlist such as
[a-z0-9_-]+; reject empty results. -
Explicitly reject absolute paths, path separators, null bytes,
.components, and..components. -
Resolve both the approved output root and candidate destination, then verify containment before any directory creation or file write:
python root = (Path(output_dir) if output_dir else Path.cwd()).resolve() output_root = (root / "design-system").resolve() destination = (output_root / safe_project_slug).resolve() if output_root != destination and output_root not in destination.parents: raise ValueError("Destination escapes the approved output directory") -
Apply the same containment check separately to page override files.
-
Treat
--output-diras privileged configuration: restrict it to an explicitly approved workspace root or remove it from untrusted invocation paths. -
Avoid silent overwrite. Use exclusive creation mode (
x) by default, create backups, or require explicit overwrite confirmation. -
Refuse symbolic-link destinations or open files using platform-appropriate no-follow protections where hostile workspaces are possible.
-
Add automated tests covering
../, nested traversal, absolute paths, mixed separators, encoded separators, symbolic links, and existing-file overwrite behavior.
-
