T09 · Insecure Skill Coding Practices
- Location
scripts/set-expectation.py:13- Finding
Unvalidated Skill Names Allow Filesystem Path Traversal and Out-of-Scope Writes
- Content
View full analysis
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: HighVulnerable Code
scripts/collect-feedback.py:18-23, 53-66python 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 filepathscripts/set-expectation.py:13-18, 31-38python 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
View remediation
Remediation Suggestions
-
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 -
Explicitly reject absolute paths, path separators, empty values,
.components, and..components. -
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") -
Apply equivalent confinement checks to feedback directories and all read paths in
check-spec.py,health-report.py,polish-suggest.py, andtracking.py. -
Remove
--forceif it is unnecessary. Otherwise, ensure it bypasses only tracking membership and never bypasses name validation or path confinement. -
Consider rejecting symbolic-link destinations and using secure atomic file replacement to reduce symlink and race-condition risks.
-
