T09 · Insecure Skill Coding Practices
- Location
scripts/design_system.py:504- Finding
Unsanitized Persistence Paths Permit Writes Outside the Intended Output Directory
- Content
View full analysis
Vulnerability Details
File Location:
scripts/design_system.py, lines 504–531
Vulnerability Type: Path traversal and unrestricted file overwrite
Risk Level: MediumVulnerable Code
python base_dir = Path(output_dir) if output_dir else Path.cwd() # Use project name for project-specific folder project_name = design_system.get("project_name", "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.mkdir(parents=True, exist_ok=True) master_file = design_system_dir / "MASTER.md" # Generate and write MASTER.md master_content = format_master_md(design_system) with open(master_file, 'w', encoding='utf-8') as f: f.write(master_content) created_files.append(str(master_file)) # If page is specified, create page override file with intelligent content if page: page_file = pages_dir / f"{page.lower().replace(' ', '-')}.md" page_content = format_page_override_md(design_system, page, page_query) with open(page_file, 'w', encoding='utf-8') as f: f.write(page_content) created_files.append(str(page_file))Technical Analysis
The persistence implementation derives filesystem paths from the CLI-controlled project name and page name. Its only normalization is replacing spaces with hyphens. It does not reject:
- Absolute paths
..traversal components/or platform-specific path separators- Symlink-based escapes
- Existing destination files
Python path composition does not provide automatic containment. In particular, joining a
Pathwith an absolute user-controlled component discards the preceding path components. Relative traversal components can likewise escape the intended directory after filesystem resolut ...[truncated 2316 chars]- Remediation
View remediation
Remediation Suggestions
-
Apply strict slug validation
- Permit only a conservative character set such as lowercase ASCII letters, digits, hyphens, and underscores.
- Reject empty names,
.and.., path separators, drive prefixes, and absolute paths. - Prefer generating the slug rather than attempting to clean a path-like input.
-
Enforce resolved-path containment
- Resolve the approved output root.
- Resolve each candidate destination.
- Verify that every destination is beneath the approved root using
Path.relative_to()or an equivalent containment check. - Perform containment checks for both project and page output paths.
-
Address symlink traversal
- Reject symlinked destination directories where appropriate.
- Recheck containment immediately before writing.
- For stronger guarantees, use directory-relative, no-follow filesystem operations supported by the platform.
-
Prevent silent destructive overwrites
- Use exclusive creation mode (
'x') by default. - Require an explicit
--forceoption before replacing existing files. - If replacement is intended, write to a temporary file in the validated destination directory and atomically replace the target.
- Use exclusive creation mode (
-
Validate
--output-dirseparately- Treat it as an explicitly authorized root, not as validation for descendant paths.
- Optionally require the directory to exist and be selected by the caller rather than creating arbitrary parent trees.
A suitable containment pattern is:
python import re from pathlib import Path def safe_slug(value: str) -> str: slug = value.strip().lower().replace(" ", "-") if not re.fullmatch(r"[a-z0-9_-]+", slug): raise ValueError("Name contains unsupported path characters") return slug base_dir = (Path(output_dir) if output_dir else Path.cwd()).resolve() output_root = (base_dir / "design-system").resolve() project_slug ...[truncated 381 chars]-
