T09 · Insecure Skill Coding Practices
- Location
xiaolongxia.py:232- Finding
Arbitrary File Overwrite Through Unvalidated Save Path
- Content
View full analysis
Vulnerability Details
File Location:
xiaolongxia.py, lines 232–238 and 315–321
Vulnerability Type: Path traversal and arbitrary file overwrite
Risk Level: HighVulnerable Code
python def save_code_to_file(code, filename=None): code_blocks = extract_code_blocks(code) if not code_blocks: return "未找到可保存的代码" ensure_dir() saved_files = [] for i, (lang, content) in enumerate(code_blocks): if not filename: ext_map = { 'python': 'py', 'js': 'js', 'typescript': 'ts', 'java': 'java', 'go': 'go', 'rust': 'rs', 'cpp': 'cpp', 'c': 'c', 'csharp': 'cs', 'sql': 'sql', 'bash': 'sh', 'shell': 'sh', 'html': 'html', 'css': 'css', 'json': 'json', 'yaml': 'yaml', 'yml': 'yml', } ext = ext_map.get(lang.lower(), 'txt') filename = f"code_{datetime.now().strftime('%H%M%S')}_{i+1}.{ext}" filepath = SKILL_DIR / filename with open(filepath, 'w', encoding='utf-8') as f: f.write(content.strip()) saved_files.append(str(filepath))python elif cmd == "--save": filename = sys.argv[2] if len(sys.argv) >= 3 else None history = load_history() if history["messages"] and len(history["messages"]) > 1: last_response = history["messages"][-1]["content"] result = save_code_to_file(last_response, filename) print(result) else: print("没有可保存的代码") returnTechnical Analysis
The
--saveargument is passed directly tosave_code_to_file()and joined withSKILL_DIRwithout validation or canonical containment checks.A value containing parent-directory components, such as
../../target, can escape the intended skill directory. In addition, Python'spathlibbehavior means an absolutefilenamereplaces the precedingSKILL_DIRcomponent entirely. The ...[truncated 2090 chars]- Remediation
View remediation
Remediation Suggestions
-
Reject absolute filenames and any path containing parent-directory traversal:
python def safe_destination(filename: str) -> Path: candidate = Path(filename) if candidate.is_absolute() or ".." in candidate.parts: raise ValueError("Absolute paths and parent traversal are not allowed") root = SKILL_DIR.resolve() destination = (root / candidate).resolve() if destination.parent != root: raise ValueError("Destination must be directly inside the skill directory") return destination -
If subdirectories are intentionally supported, use a robust containment test:
python destination.relative_to(root)Handle
ValueErroras an attempted directory escape. -
Disallow or safely handle symbolic links. Before writing, verify that the destination and relevant parent components are not symbolic links. Where supported, use operating-system flags that prevent following symlinks.
-
Avoid silently truncating existing files. Use exclusive creation mode (
'x') by default and require explicit user confirmation before an overwrite. -
Generate server-side filenames instead of accepting arbitrary paths when a caller only needs to select a display name.
-
Add tests covering absolute paths,
../traversal, nested traversal, symbolic links, existing-file overwrite, and platform-specific path forms.
-
