T09 · Insecure Skill Coding Practices
- Location
scripts/mlx-audio.py:501- Finding
Path Traversal and Arbitrary File Overwrite Through Voice Profile ID
- Content
View full analysis
Vulnerability Details
File Location:
scripts/mlx-audio.py:501-543
Vulnerability Type: Unsanitized path construction and arbitrary file overwrite
Risk Level: HighVulnerable Code
python 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") # Run TTS to generate the audio (always use VoiceDesign model for voice creation) _ensure_mlx_audio() from mlx_audio.tts.utils import load_model as load_tts_model model = load_tts_model(DEFAULT_VOICEDESIGN_MODEL) kwargs = { "text": text, "language": args.language, "instruct": instruct, } results = list(model.generate(**kwargs)) if not results: raise RuntimeError("TTS 生成失败:未返回音频结果") result = results[0] sample_rate = _get_sample_rate(result, model) audio = result.audio sf.write(output_audio, audio, 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.write(instruct)Technical Analysis
The user-controlled
--idargument is used directly as a path component without validation or canonical containment checking. Python'sos.path.join()does not guarantee that the resulting path remains undervoices_dir:- An absolute
voice_idcauses the precedingvoices_dircomponent to be discarded. - A relative value containing
../can traverse outside the intendedvoices/directory. - Existing directories are accepted because `os.makedirs(..., exist_ok ...[truncated 1907 chars]
- An absolute
- Remediation
View remediation
Remediation Suggestions
- Restrict voice IDs to a conservative allowlist, for example:
python import re VOICE_ID_PATTERN = re.compile(r"^[A-Za-z0-9_-]{1,64}$") if not VOICE_ID_PATTERN.fullmatch(voice_id): raise ValueError( "Voice ID may contain only letters, digits, underscores, and hyphens" )- Canonicalize the base and candidate paths and enforce containment:
python voices_dir = os.path.realpath(get_voices_dir()) voice_dir = os.path.realpath(os.path.join(voices_dir, voice_id)) if os.path.commonpath([voices_dir, voice_dir]) != voices_dir: raise ValueError("Voice path escapes the voices directory")- Explicitly reject absolute paths, path separators,
.components, and..components. - Apply the same validation and containment checks to
get_voice_path(). - Refuse to overwrite existing profiles by default. Require an explicit, documented replacement option when replacement is intended.
- Consider using exclusive file creation and restrictive filesystem permissions for stored voice data.
- Add tests covering absolute paths,
../traversal, nested paths, symlink-based escapes, and existing-profile replacement.
