T09 · Insecure Skill Coding Practices
- Location
scripts/common.py:316- Finding
User-Controlled Output Name Permits Path Traversal and Writes Outside the Intended Directory
- Content
View full analysis
Path: timestamp = datetime.now().strftime("%Y%m%d%H%M") stem = f"{name}_{timestamp}" if name else f"{item_id}_{timestamp}" return ensure_scene_output_dir(scene) / f"{stem}{suffix}" ``` The generated path is then used without containment validation: ```python # scripts/generate_music.py audio_url = extract_audio_url(item) local_path = default_output_path("music", item_id, ".mp3", name=args.name) download_file(audio_url, local_path) ``` ```python # scripts/generate_song.py audio_url = extract_audio_url(item) local_path = default_output_path("songs", item_id, ".mp3", name=args.name) download_file(audio_url, local_path) ``` ```python # scripts/generate_lyrics.py local_path = default_output_path("lyrics", item_id, ".md", name=args.name) local_path.write_text(f"# {title}\n\n{lyric}", encoding="utf-8") ``` For audio files, the final write is performed by: ```python def download_file(url: str, output_path: Path) -> Path: output_path.parent.mkdir(parents=True, exist_ok=True) log_params("文件下载开始", url=url, output_path=str(output_path.name)) request = Request(url, method="GET") with urlopen(request) as response: output_path.write_bytes(response.read()) size = output_path.stat().st_size log_params("文件下载完成", url=url, output_path=str(output_path.name), size=size) return output_path ``` ### Technical Analysis The `--name` command-line argument is directly incorporated into the destination path. Although the documentation describes it as a short filename description, the implementation does not enforce ...[truncated 3094 chars]- Remediation
View remediation
Path: target_dir = ensure_scene_output_dir(scene).resolve() raw_name = name.strip() if raw_name: candidate = Path(raw_name) if ( candidate.is_absolute() or candidate.name != raw_name or raw_name in {".", ".."} or "/" in raw_name or "\\" in raw_name ): raise ValueError("Invalid output name") if len(raw_name) > 30: raise ValueError("Output name is too long") stem = raw_name else: stem = item_id timestamp = datetime.now().strftime("%Y%m%d%H%M") output_path = (target_dir / f"{stem}_{timestamp}{suffix}").resolve() if not output_path.is_relative_to(target_dir): raise ValueError("Output path escapes the configured directory") return output_path ``` 3. Perform the containment check immediately before every write as defense in depth, particularly after creating parent directories. 4. Consider exclusive file creation or explicit overwrite controls to prevent replacement of an existing destination. 5. Apply filesystem permission hardening to `OUTPUT_ROOT`, and avoid running the Skill with elevated operating-system privileges. ]]>
