T09 · Insecure Skill Coding Practices
- Location
scripts/parse_entry.py:314- Finding
Path Prefix Validation Allows Writes Outside the Authorized Workspace
- Content
View full analysis
bool: """Security validation: only permit paths within ~/.openclaw/workspace/.""" try: if str(path.resolve()).startswith( str(Path.home() / ".openclaw" / "workspace") ): return True except Exception: pass return False ``` The result is used to authorize a user-supplied ledger path: ```python path = Path(ledger_path) if not _safe_write_path(path): return False path.parent.mkdir(parents=True, exist_ok=True) account = entry["account"] with open(path, "a", encoding="utf-8-sig") as f: _ensure_account_defined(path, account, f) f.write(line + "\n") ``` The same vulnerable containment pattern is used by other scripts. For example, `scripts/generate_report.py` uses it before writing a report: ```python if args.output: out_path = Path(args.output) if not _safe_write_path(out_path): print(f"❌ Not permitted to write to: {args.output}") sys.exit(1) out_path.write_text(report, encoding="utf-8") ``` ### Technical Analysis The validation compares canonical path strings with `str.startswith()`. A string-prefix check does not establish a filesystem directory boundary. For example, if the authorized root is: ```text /home/user/.openclaw/workspace ``` then this unauthorized sibling also passes the check: ```text /home/user/.openclaw/workspace-evil/output.csv ``` Its canonical string starts with the authorized root string even though it is not a descendant of that directory. The checks are also broader than the permission declared in `SKILL.md`, which limits wr ...[truncated 1504 chars]- Remediation
View remediation
Path | None: try: resolved = candidate.expanduser().resolve() if resolved == ALLOWED_ROOT or resolved.is_relative_to(ALLOWED_ROOT): return resolved except (OSError, RuntimeError): pass return None ``` 3. For older Python versions, use `os.path.commonpath()`: ```python if os.path.commonpath([str(resolved), str(ALLOWED_ROOT)]) != str(ALLOWED_ROOT): return None ``` 4. Apply the same helper consistently to all read, write, append, delete, and migration operations. 5. Where practical, reject symlinks for destination files and parent directories, and use safe file-opening patterns that reduce time-of-check/time-of-use races. 6. Add regression tests covering: - `workspace-evil/file.csv` - `ledger-backup/file.csv` - `..` traversal - symlinks resolving outside the ledger directory - the exact authorized root - valid descendants of the authorized root ]]>
