T09 · Insecure Skill Coding Practices
- Location
scripts/init_book.py:14- Finding
Unvalidated Project Identifiers Allow Filesystem Path Traversal
- Content
View full analysis
2 else "" # Create project root directory base_dir = Path.home() / ".qclaw" / "workspace" / "novels" / book_name dirs = [ base_dir / "settings" / "characters", base_dir / "outline", base_dir / "chapters", base_dir / "tracker", ] for d in dirs: d.mkdir(parents=True, exist_ok=True) state = { "book": book_name, "author": author, "current_arc": 1, "current_chapter": 0, "total_words": 0, "unresolved_foreshadowing": 0, "last_updated": "", "status": "drafting" } foreshadowing = {"伏笔列表": []} conflicts = {"冲突列表": []} (base_dir / "state.json").write_text( json.dumps(state, ensure_ascii=False, indent=2), encoding="utf-8" ) (base_dir / "tracker" / "foreshadowing.json").write_text( json.dumps(foreshadowing, ensure_ascii=False, indent=2), encoding="utf-8" ) (base_dir / "tracker" / "conflicts.json").write_text( json.dumps(conflicts, ensure_ascii=False, indent=2), encoding="utf-8" ) (base_dir / "tracker" / "style-log.md").write_text( f"# 文风日志 - {book_name}\n\n## 记录\n", encoding="utf-8" ) (base_dir / "settings" / "world.md").write_text( f"# 世界观设定 - {book_name}\n\n", encoding="utf-8" ) (base_dir / "outline" / "arc-outline.md").write_text( f"# 分卷大纲 - {book_name}\n\n", encoding="utf-8" ) ``` The same unsafe path constructio ...[truncated 4090 chars]- Remediation
View remediation
str: value = value.strip() if ( not value or not IDENTIFIER.fullmatch(value) or value in {".", ".."} or "/" in value or "\\" in value ): raise ValueError(f"Invalid {field}") return value ``` 2. **Enforce canonical path containment** Resolve both the trusted root and candidate path, then verify that the candidate remains beneath the root before every read, write, creation, or directory enumeration. ```python ROOT = ( Path.home() / ".qclaw" / "workspace" / "novels" ).resolve() def confined_path(*parts: str) -> Path: candidate = ROOT.joinpath(*parts).resolve() if not candidate.is_relative_to(ROOT): raise ValueError("Path escapes the novel workspace") return candidate ``` For Python versions without `Path.is_relative_to`, compare using `candidate.relative_to(ROOT)` and reject `ValueError`. 3. **Defend against symbolic-link traversal** Validate resolved paths immediately before access. Where supported, use no-follow filesystem operations or explicitly reject symbolic links in every existing component between `ROOT` and the target. 4. **Separate display names from storage identifiers** Store user-facing book and character names inside JSON, but derive directory names from generated UUIDs or securely normalized slugs. Do not use unrestricted display text as a filesystem path. 5. **Use atomic and non-destructive writes** Write to a temporary file in the same validated directory, flush it, and atomically replace the target. Initialization shou ...[truncated 483 chars]
