T05 · Unauthorized Access and Privilege Escalation
- Location
scripts/ledger.py:218- Finding
Path Traversal Allows File Creation Outside the Configured Project Root
- Content
View full analysis
dict: root = Path(cfg.get('projects_root_dir') or './projects') project_dir = root / display_name sub = {} for name in ['Docs','Specs','Data','Backlog','Evidence','Releases','Archive']: p = project_dir / name p.mkdir(parents=True, exist_ok=True) sub[name] = p # starter markdown docs charter = sub['Docs'] / '项目说明(Charter).md' prd = sub['Docs'] / 'PRD(需求文档).md' backlog = sub['Backlog'] / 'Backlog(待办).md' ``` ### Technical Analysis The `display_name` parameter originates from the user-controlled `register-project --name` argument. It is directly joined to `projects_root_dir` without validation or containment verification. Python's `pathlib` path joining does not enforce confinement: - An absolute `display_name` replaces the configured root. - A relative name containing `..` components can escape the configured root. - Existing symbolic links in the selected path can also redirect file operations outside the expected project directory. The function subsequently creates multiple directories and writes or touches Markdown files at the resulting destination. Normalizing a separate `slug` value does not mitigate the issue because `display_name`, rather than `slug`, is used to construct the filesystem path. ### Attack Path 1. An attacker or untrusted automation gains control over arguments passed to `register-project`. 2. The attacker supplies a project name containing traversal components or an absolute path, such as `../../attacker-controlled`. 3. `scaffold_local_project()` evaluates `root / display_name` without rejecting the unsafe path. 4. Directory creation escapes `projects_root_dir`. 5. The function ...[truncated 812 chars]- Remediation
View remediation
