T05 · Unauthorized Access and Privilege Escalation
Error
- Location
- src/index.py:428
- Finding
- Caller-Controlled Paths Permit Unauthorized File Read and Write## Vulnerability Details **File Location**: `src/index.py`, lines 428-440 **Vulnerability Type**: Path traversal and unrestricted filesystem access **Risk Level**: High ### Vulnerable Code ```python async def handler(input: dict[str, Any], _context: Any) -> dict[str, Any]: skill_root = Path(__file__).resolve().parent.parent template_file = input.get("template_file") or "PPT_Template.pptx" template_path = skill_root / template_file if not template_path.exists(): raise FileNotFoundError(f"Template not found: {template_path}") mode = (input.get("mode") or "xml").strip().lower() if mode != "xml": raise ValueError("Only xml mode is supported") title = input.get("title", "Corporate Deck") output_filename = input.get("output_filename", "openclaw_generated_xml.pptx") output_path = skill_root / output_filename ``` The resulting paths are subsequently used without confinement checks: ```python prs = Presentation(str(template_path)) ... prs.save(str(output_path)) ``` ### Technical Analysis Both `template_file` and `output_filename` are controlled by the caller. Joining an untrusted path to `skill_root` does not ensure that the result remains inside the skill directory: - A path containing `../` can traverse outside the intended directory. - If the supplied path is absolute, Python's `pathlib` discards the preceding `skill_root`. - The implementation does not call `resolve()` and verify that the resolved path is beneath an approved directory. - The output path has no extension restriction, overwrite protection, or collision check. The template input must be a file that `python-pptx` can successfully parse, which limits arbitrary reading to compatible PowerPoint files. Nevertheless, the caller can select any accessible compatible file outside the skill directory. More critically, the output operation can create or overwrite any file writable by the skil ...[truncated 1415 chars]
- Remediation
- ## Remediation Suggestions 1. Define separate, explicit directories for approved templates and generated output. 2. Reject absolute caller-supplied paths. 3. Resolve each candidate path and verify that it remains beneath the corresponding approved directory. 4. Restrict both template and output files to the `.pptx` extension. 5. Treat caller input as a filename rather than an unrestricted path when subdirectories are unnecessary. 6. Refuse to overwrite existing files unless overwrite behavior is explicitly authorized. 7. Ensure the output path cannot equal the template path. 8. Run the skill under a dedicated account with minimal filesystem permissions. Example hardening logic: ```python def confined_pptx_path(base: Path, supplied: str) -> Path: if not supplied: raise ValueError("A filename is required") untrusted = Path(supplied) if untrusted.is_absolute(): raise ValueError("Absolute paths are not allowed") if untrusted.suffix.lower() != ".pptx": raise ValueError("Only .pptx files are allowed") resolved_base = base.resolve() candidate = (resolved_base / untrusted).resolve() if not candidate.is_relative_to(resolved_base): raise ValueError("Path escapes the approved directory") return candidate template_path = confined_pptx_path(template_directory, template_file) output_path = confined_pptx_path(output_directory, output_filename) if output_path == template_path: raise ValueError("Output path cannot overwrite the template") if output_path.exists(): raise FileExistsError(f"Output already exists: {output_path}") ``` Where atomic output creation is required, write to a securely created temporary file inside the approved output directory and atomically rename it after successful generation.
