T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/reset-session.py:170
- Finding
- Path Traversal in Agent Scope Enables Deletion of JSONL Files Outside the Agent Directory<![CDATA[ ## Vulnerability Details **File Location**: `scripts/reset-session.py:170-175`, `scripts/reset-session.py:640-647`, `scripts/reset-session.py:323-334`, and `scripts/reset-session.py:399-403` **Vulnerability Type**: Path traversal and insufficient input validation **Risk Level**: High ### Vulnerable Code ```python def get_session_files(agent_id: str) -> List[Path]: """获取指定 agent 的所有 session 文件""" sessions_dir = AGENTS_DIR / agent_id / "sessions" if not sessions_dir.exists(): return [] return list(sessions_dir.glob("*.jsonl")) ``` ```python if "," in scope: # 多 agent 模式 sessions = {} for agent_id in scope.split(","): agent_id = agent_id.strip() agent_sessions = get_session_files(agent_id) if agent_sessions: sessions[agent_id] = [parse_session_file(f) for f in agent_sessions] ``` ```python for agent_id, agent_sessions in sessions.items(): agent_backup_dir = backup_path / agent_id agent_backup_dir.mkdir(exist_ok=True) manifest["agents"][agent_id] = [] for session in agent_sessions: src_file = session["file"] dst_file = agent_backup_dir / src_file.name shutil.copy2(src_file, dst_file) ``` ```python for agent_id, agent_sessions in sessions.items(): for session in agent_sessions: try: session_file = session["file"] session_file.unlink() print(f" {Colors.GREEN}✓{Colors.END} {agent_id}/{session['session_id']}") success += 1 except Exception as e: print(f" {Colors.RED}✗{Colors.END} {agent_id}/{session['session_id']}: {e}") failed += 1 ``` ### Technical Analysis Comma-separated `--scope` values are treated as agent identifiers and passed directly into filesystem path construction. The code does not reject absolute paths, path separators, or `..` traversal components. In `pathlib`, joining a base path with an absolute path discards the original base. ...[truncated 2072 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Accept only existing agent names returned by `discover_agents()`. 2. Reject empty names, absolute paths, path separators, `.` components, and `..` components. 3. Resolve every constructed path and verify containment before reading, copying, or deleting: ```python def require_descendant(candidate: Path, root: Path) -> Path: resolved_root = root.resolve(strict=True) resolved_candidate = candidate.resolve(strict=True) if not resolved_candidate.is_relative_to(resolved_root): raise ValueError("Path escapes the permitted root") return resolved_candidate ``` 4. Validate identifiers with a restrictive format such as: ```python AGENT_ID_PATTERN = re.compile(r"^[A-Za-z0-9_-]+$") ``` 5. Reject symbolic links for agent directories, session directories, and session files, or safely resolve and validate their final targets. 6. Repeat the containment check immediately before `copy2()` and `unlink()` to reduce time-of-check/time-of-use risk. 7. Construct backup directory names from validated identifiers rather than raw CLI input. 8. Consider disabling `--force` for scopes that are not explicit built-in values, or require an additional non-interactive safety token. ]]>
