T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/set-expectation.py:13
- Finding
- Unvalidated Skill Names Allow Filesystem Path Traversal and Out-of-Scope Writes## Vulnerability Details **File Location**: `scripts/collect-feedback.py:18-23, 53-66`; `scripts/set-expectation.py:13-18, 31-38` **Vulnerability Type**: Filesystem path traversal and arbitrary file write **Risk Level**: High ### Vulnerable Code `scripts/collect-feedback.py:18-23, 53-66` ```python def get_feedback_dir(skill_name: str) -> Path: """Get the feedback storage directory for a Skill.""" base_dir = Path.home() / ".openclaw/workspace/.skill-polisher/feedback" skill_dir = base_dir / skill_name skill_dir.mkdir(parents=True, exist_ok=True) return skill_dir def save_feedback(feedback: dict) -> Path: """Save feedback to a file.""" skill_name = feedback["skill"] feedback_dir = get_feedback_dir(skill_name) # Generate the filename timestamp = datetime.now().strftime("%Y%m%d-%H%M%S") filename = f"{timestamp}.json" filepath = feedback_dir / filename with open(filepath, "w", encoding="utf-8") as f: json.dump(feedback, f, ensure_ascii=False, indent=2) return filepath ``` `scripts/set-expectation.py:13-18, 31-38` ```python def get_expectation_path(skill_name: str) -> Path: """Get the Skill expectation file path.""" base_dir = Path.home() / ".openclaw/workspace/.skill-polisher/expectations" base_dir.mkdir(parents=True, exist_ok=True) return base_dir / f"{skill_name}.json" def save_expectation(skill_name: str, expectation: dict): """Save Skill expectations.""" path = get_expectation_path(skill_name) expectation["skill"] = skill_name expectation["updated_at"] = datetime.now().isoformat() with open(path, "w", encoding="utf-8") as f: json.dump(expectation, f, ensure_ascii=False, indent=2) ``` ### Technical Analysis User-controlled Skill names are incorporated directly into filesystem paths without validating their syntax or confirming that the resolved destination remain ...[truncated 2791 chars]
- Remediation
- ## Remediation Suggestions 1. Apply one centralized validator to every externally supplied or persisted Skill name: ```python import re SKILL_NAME_RE = re.compile(r"^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$") def validate_skill_name(value: str) -> str: if not SKILL_NAME_RE.fullmatch(value): raise ValueError("Invalid Skill name") return value ``` 2. Explicitly reject absolute paths, path separators, empty values, `.` components, and `..` components. 3. Resolve and verify every destination before accessing it: ```python base = ( Path.home() / ".openclaw/workspace/.skill-polisher/expectations" ).resolve() destination = (base / f"{validate_skill_name(skill_name)}.json").resolve() if destination.parent != base: raise ValueError("Expectation path escapes its storage directory") ``` 4. Apply equivalent confinement checks to feedback directories and all read paths in `check-spec.py`, `health-report.py`, `polish-suggest.py`, and `tracking.py`. 5. Remove `--force` if it is unnecessary. Otherwise, ensure it bypasses only tracking membership and never bypasses name validation or path confinement. 6. Consider rejecting symbolic-link destinations and using secure atomic file replacement to reduce symlink and race-condition risks.
