T09 · Insecure Skill Coding Practices
- Location
memory_manager.py:17- Finding
User-controlled identifier permits path traversal outside the intended data directory
- Content
View full analysis
Vulnerability Details
File Location:
memory_manager.py:17-32, 67-74, 98-123, 145-163, 235-265
Vulnerability Type: Path traversal and arbitrary JSON file access
Risk Level: HighVulnerable Code
python def __init__(self, data_dir: str = None): if data_dir is None: data_dir = Path.home() / ".claude" / "skills" / "fitness-coach" / "data" self.data_dir = Path(data_dir) self.users_dir = self.data_dir / "users" self.logs_dir = self.data_dir / "logs" self.users_dir.mkdir(parents=True, exist_ok=True) self.logs_dir.mkdir(parents=True, exist_ok=True) def _get_user_file(self, user_id: str) -> Path: return self.users_dir / f"{user_id}.json" def _get_logs_file(self, user_id: str) -> Path: return self.logs_dir / f"{user_id}_logs.json"python def load_user_profile(self, user_id: str) -> Optional[UserProfile]: file_path = self._get_user_file(user_id) if not file_path.exists(): return None with open(file_path, 'r', encoding='utf-8') as f: data = json.load(f)python def save_user_profile(self, profile: UserProfile): file_path = self._get_user_file(profile.user_id) data = { "user_id": profile.user_id, "name": profile.name, "target_weight": profile.target_weight, "exercise_frequency": profile.exercise_frequency, "diet_preference": profile.diet_preference, "goal_type": profile.goal_type, "personality_insights": profile.personality_insights, "created_at": profile.created_at.isoformat(), "updated_at": datetime.now().isoformat() } with open(file_path, 'w', encoding='utf-8') as f: json.dump(data, f, ensure_ascii=False, indent=2)python def add_conversation(self, user_id: str, role: str, content: str): memory_file = self.users_dir / f"{user_id}_memory.json" memory = {"sho ...[truncated 2426 chars]- Remediation
View remediation
Remediation Suggestions
- Validate every
user_idat the trust boundary using a strict allowlist, such as ASCII letters, digits, underscores, and hyphens with a short maximum length. - Reject absolute paths, path separators,
.components, and..components. - Resolve the final path and verify that it remains beneath the expected directory before every read or write.
- Use a generated internal identifier instead of using external account identifiers directly as filenames.
- Centralize safe path construction so conversation, profile, and workout-log paths receive identical validation.
- Add tests for absolute paths, nested traversal, encoded separators, repeated separators, and platform-specific Windows path forms.
Example defensive pattern:
python import re USER_ID_PATTERN = re.compile(r"^[A-Za-z0-9_-]{1,64}$") def _safe_user_path(base: Path, user_id: str, suffix: str) -> Path: if not USER_ID_PATTERN.fullmatch(user_id): raise ValueError("Invalid user identifier") base = base.resolve() candidate = (base / f"{user_id}{suffix}").resolve() if candidate.parent != base: raise ValueError("Path escapes the data directory") return candidate- Validate every
