T09 · Insecure Skill Coding Practices
- Location
scripts/skill.py:102- Finding
Path Traversal in the Primary Slack Context Loader
- Content
View full analysis
Vulnerability Details
File Location:
scripts/skill.py:102-118(duplicated in the force-reload path atscripts/skill.py:269-278)
Vulnerability Type: Path traversal leading to unauthorized local file disclosure
Risk Level: MediumVulnerable Code
python # Try channel ID first (in contexts folder) if channel_id: context_file = self.contexts_dir / f"{channel_id}.md" if context_file.exists(): content = self._read_file(context_file) self._cache(cache_key, content) return content # Try channel name (in contexts folder) if channel_name: # Normalize channel name (remove #, lowercase) normalized_name = channel_name.lstrip("#").lower() context_file = self.contexts_dir / f"{normalized_name}.md" if context_file.exists(): content = self._read_file(context_file) self._cache(cache_key, content) return contentThe resulting path is passed to the following file-reading sink at
scripts/skill.py:126-133:python def _read_file(self, filepath): """Read and return file contents.""" try: with open(filepath, "r", encoding="utf-8") as f: return f.read() except Exception as e: print(f"Error reading {filepath}: {e}") return NoneTechnical Analysis
The loader treats
channel_idandchannel_nameas path components without validating that they conform to Slack identifier syntax. It does not reject path separators,..components, or absolute paths. It also does not resolve the resulting path and verify that it remains underself.contexts_dir.Consequently, a value such as
../../notesproduces a path equivalent to:text ~/.openclaw/workspace/slack-channel-contexts/../../notes.mdPath.exists()andopen()follow this traversal and read the target if it exists and is accessible to the OpenClaw process. The automatically appended ...[truncated 1736 chars]- Remediation
View remediation
Remediation Suggestions
- Validate channel IDs with a strict allowlist matching expected Slack identifier syntax, such as uppercase alphanumeric identifiers of an appropriate length.
- Validate channel names with a conservative allowlist, such as lowercase letters, digits, hyphens, and underscores.
- Explicitly reject
/,\, null bytes, absolute paths,.components, and..components. - Resolve the context directory and candidate file before opening it, then verify containment:
python base = self.contexts_dir.expanduser().resolve() candidate = (base / f"{validated_identifier}.md").resolve() try: candidate.relative_to(base) except ValueError: raise ValueError("Context path escapes the configured context directory")- Require the resolved target to be a regular file. If symlinks are not needed, reject symbolic links to prevent link-based directory escape.
- Centralize path construction in one validated helper and use it in both normal and force-reload flows.
- Add regression tests for
../, absolute paths, nested separators, symlink escapes, empty identifiers, and valid Slack IDs and names.
