T05 · Unauthorized Access and Privilege Escalation
Warning
- Location
- skill_evolver/analyzer.py:58
- Finding
- Unrestricted Skill Path Allows Analysis Outside the Configured Skill Directory<![CDATA[ ## Vulnerability Details **File Location**: `skill_evolver/analyzer.py:58-63, 70-74, 267` **Vulnerability Type**: Path traversal and unauthorized local file inspection **Risk Level**: Medium ### Vulnerable Code ```python def __init__(self, skills_dir: Optional[str] = None): if skills_dir is None: # 默认使用 ~/.openclaw/skills self.skills_dir = Path.home() / ".openclaw" / "skills" else: self.skills_dir = Path(skills_dir) def analyze_skill(self, skill_name: str) -> SkillAnalysisResult: """ 分析单个技能 """ skill_path = self.skills_dir / skill_name result = SkillAnalysisResult( skill_name=skill_name, skill_path=str(skill_path) ) ``` The resulting path is subsequently used for recursive file discovery: ```python def _analyze_python_code(self, result: SkillAnalysisResult, skill_path: Path): """分析 Python 代码文件""" python_files = list(skill_path.rglob("*.py")) ``` ### Technical Analysis The analyzer constructs the target path by directly joining the configured Skill directory with the caller-controlled `skill_name`. It does not reject: - Parent-directory components such as `..` - Absolute paths - Symbolic links that resolve outside the Skill root - Alternate paths that normalize outside the intended directory With `pathlib`, joining a base path with an absolute path discards the base path. Relative traversal components can similarly escape the configured directory after filesystem resolution. The escaped path is used to read `SKILL.md`, parse `package.json`, and recursively discover and parse every Python file beneath the target. Although the implementation does not print complete source contents, it can disclose directory existence, resolved paths, filenames, syntax errors, line numbers, and code-quality information about files outside the authorized Skill repository. Recursive traversal can also impose substantial CPU and memory costs when pointed at a large directory. ### Atta ...[truncated 1324 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Resolve and normalize the configured Skill root during initialization: ```python self.skills_dir = Path(skills_dir).expanduser().resolve() ``` 2. Treat Skill names as identifiers rather than paths. Reject absolute paths, empty names, `.` and `..` components, and path separators where they are not required. 3. Resolve the candidate path and verify that it remains under the authorized root: ```python root = self.skills_dir.resolve() candidate = (root / skill_name).resolve(strict=False) try: candidate.relative_to(root) except ValueError: raise ValueError("Skill path must remain inside the configured Skill directory") ``` 4. Define and enforce a symbolic-link policy. If Skills must not escape through links, inspect each path component or reject linked Skill roots before recursive traversal. 5. Add maximum file-count, file-size, recursion-depth, and total-byte limits to prevent resource exhaustion. 6. Add tests covering parent traversal, absolute paths, nested traversal, symbolic links, and valid Skill identifiers. ]]>
