T09 · Insecure Skill Coding Practices
- Location
scripts/init_project.py:37- Finding
Unvalidated project slug permits writes outside the configured project root
- Content
View full analysis
str: ts = (now or datetime.now()).strftime("%Y%m%d-%H%M%S") return f"{base_slug}-{ts}" def reserve_project_dir(projects_root: Path, initial_slug: str) -> ProjectInitResult: slug = initial_slug project_dir = projects_root / slug suffix = 2 while project_dir.exists(): slug = f"{initial_slug}-{suffix}" project_dir = projects_root / slug suffix += 1 return ProjectInitResult(slug=slug, project_dir=project_dir) def init_project(base_slug: str, author: str, mode: str, projects_root: Path) -> ProjectInitResult: projects_root.mkdir(parents=True, exist_ok=True) result = reserve_project_dir(projects_root, build_slug(base_slug)) print(f"Creating project: {result.project_dir}", file=sys.stderr) result.project_dir.mkdir(parents=True, exist_ok=False) write_progress(result.project_dir, result.slug, author, mode) touch_artifacts(result.project_dir) ``` ### Technical Analysis The positional `base_slug` argument is incorporated directly into a filesystem path without validating or normalizing it as a single safe path component. There are no checks for: - Absolute paths - `/` or platform-specific path separators - `.` or `..` traversal components - A strict allowlist of slug characters - Whether the resolved destination remains beneath `projects_root` With `pathlib`, joining a root path with an absolute second operand discards the original root. Relative values containing `..` can similarly resolve outside the configured project directory. The resulting path is then passed to `mkdir(parents=True)` and used as the destination for six generated artifact files. ### Attack Path 1. An attacker or untrusted ...[truncated 1235 chars]- Remediation
View remediation
str: if not SLUG_PATTERN.fullmatch(value): raise ValueError("base_slug must contain only lowercase letters, digits, and hyphens") return value root = projects_root.resolve() safe_slug = validate_base_slug(base_slug) candidate = (root / build_slug(safe_slug)).resolve() if candidate.parent != root: raise ValueError("Project directory must be a direct child of projects_root") ``` 4. Apply a reasonable maximum slug length to avoid filesystem and denial-of-service edge cases. 5. Add regression tests covering absolute paths, nested paths, traversal values, mixed separators, empty slugs, and valid slugs. 6. Continue using `exist_ok=False` to preserve the current collision protection, but do not rely on it as a path-containment control. ]]>
