T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/design_system.py:511
- Finding
- Path Traversal in Persisted Design-System Output Names<![CDATA[ ## Vulnerability Details **File Location**: `scripts/design_system.py:511-535` **Vulnerability Type**: Path traversal and unintended file overwrite **Risk Level**: Medium ### Vulnerable 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) ``` ### Technical Analysis The persistence function derives filesystem paths from the user-controlled project name and page name. The only transformation replaces spaces with hyphens. It does not reject absolute paths, path separators, `.` components, or `..` parent-directory components. Python's `pathlib` path-joining operation does not establish a security boundary. When the resulting path is passed to `mkdir()` or `open()`, the operating system resolves traversal components. Consequently, a malicious project or page name can cause generated files to be written outside the intended `design-system/<project>/` hierarchy. The use of `open(..., 'w')` also truncates an existing target file without confirmation. The project-name path always ends in ` ...[truncated 2257 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Convert project and page names to strict filesystem-safe slugs using an allowlist: - Permit only lowercase ASCII letters, digits, hyphens, and underscores. - Reject empty values and the reserved names `.` and `..`. - Reject all path separators rather than attempting to normalize them. 2. Resolve the intended root and candidate output path before writing, then verify containment: ```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 characters") if slug in {".", ".."}: raise ValueError("Invalid path name") return slug root = (base_dir / "design-system").resolve() project_slug = safe_slug(project_name) design_system_dir = (root / project_slug).resolve() if not design_system_dir.is_relative_to(root): raise ValueError("Output path escapes the design-system directory") ``` 3. Apply the same validation independently to `page` before constructing the page filename. 4. Verify the final file path remains below the expected project or pages directory after resolution: ```python pages_root = (design_system_dir / "pages").resolve() page_file = (pages_root / f"{safe_slug(page)}.md").resolve() if not page_file.is_relative_to(pages_root): raise ValueError("Page path escapes the pages directory") ``` 5. Avoid silent replacement of existing files. Use exclusive creation mode (`'x'`) by default, create a backup, or require an explicit `--force` option before overwriting. 6. Add automated tests covering `../`, nested traversal, absolute paths, platform-specific separators, empty names, Unicode separator lookalikes, and valid names. ]]>
