T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/qwen-audio.py:747
- Finding
- Unsanitized Voice IDs Permit Filesystem Path Traversal<![CDATA[ ## Vulnerability Details **File Location**: `scripts/qwen-audio.py:747-766` and `scripts/qwen-audio.py:778-834` **Vulnerability Type**: Path traversal and arbitrary filesystem access **Risk Level**: High ### Vulnerable Code ```python def get_voice_path(voice_id: str) -> dict | None: """Get voice file paths by voice_id. Returns dict with ref_audio, ref_text and instruct, or None if not found.""" voices_dir = get_voices_dir() voice_dir = os.path.join(voices_dir, voice_id) if not os.path.isdir(voice_dir): return None ref_audio = os.path.join(voice_dir, "ref_audio.wav") ref_text_path = os.path.join(voice_dir, "ref_text.txt") instruct_path = os.path.join(voice_dir, "ref_instruct.txt") if not os.path.exists(ref_audio): return None ref_text = "" if os.path.exists(ref_text_path): with open(ref_text_path, "r", encoding="utf-8") as f: ref_text = f.read().strip() ``` ```python def run_voice_create(args: argparse.Namespace) -> None: """Create a new voice by generating audio and saving reference info.""" voices_dir = get_voices_dir() voice_id = args.id or str(uuid.uuid4())[:8] voice_dir = os.path.join(voices_dir, voice_id) os.makedirs(voice_dir, exist_ok=True) # Normalize text: replace newlines with spaces text = _normalize_text(args.text) instruct = _normalize_text(args.instruct) if args.instruct else None if not instruct: raise ValueError("--instruct 参数是必需的,请提供语音风格描述") # Generate audio using TTS output_audio = os.path.join(voice_dir, "ref_audio.wav") ``` ```python sf.write(output_audio, audio_np, sample_rate) # Save reference text ref_text_path = os.path.join(voice_dir, "ref_text.txt") with open(ref_text_path, "w", encoding="utf-8") as f: f.write(text) # Save instruct instruct_path = os.path.join(voice_dir, "ref_instruct.txt") with open(instruct_path, "w", encoding="utf-8") as f: f. ...[truncated 2228 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Restrict voice IDs to a conservative allowlist, for example: ```python import re VOICE_ID_RE = re.compile(r"^[A-Za-z0-9_-]{1,64}$") def validate_voice_id(voice_id: str) -> str: if not VOICE_ID_RE.fullmatch(voice_id): raise ValueError("Invalid voice ID") return voice_id ``` 2. Resolve and verify directory containment before every read or write: ```python voices_root = os.path.realpath(get_voices_dir()) voice_dir = os.path.realpath(os.path.join(voices_root, validate_voice_id(voice_id))) if os.path.commonpath([voices_root, voice_dir]) != voices_root: raise ValueError("Voice path escapes the voices directory") ``` 3. Reject absolute paths, path separators, `.` components, and `..` components even if an allowlist is introduced. 4. Apply the same centralized path-validation function to both `run_voice_create()` and `get_voice_path()`. 5. Refuse to overwrite an existing voice directory unless the user explicitly supplies a dedicated overwrite option. 6. Where supported, protect against symbolic-link traversal by checking each path component and using safe file-opening primitives with no-follow semantics. ]]>
