T09 · Insecure Skill Coding Practices
- Location
scripts/generate_tts_kokoro.sh:46- Finding
Arbitrary Filesystem Write and Deletion Through an Unvalidated Manifest Output Path
- Content
View full analysis
Vulnerability Details
File Location:
scripts/generate_tts_kokoro.sh, lines 46-71
Vulnerability Type: Unrestricted path traversal and arbitrary file overwrite
Risk Level: HighVulnerable Code
python for row in rows: slide = row['slide'].strip() rel_out = row['audio_file'].strip() text = row['text'].strip() mp3_out = module_dir / 'tts' / rel_out wav_out = mp3_out.with_suffix('.wav') mp3_out.parent.mkdir(parents=True, exist_ok=True) print(f"Generating slide {slide} -> {mp3_out}") chunks = [] for _, _, audio in pipeline(text, voice=voice, speed=speed): chunks.append(audio) if not chunks: raise RuntimeError(f"No audio generated for slide {slide}") combined = np.concatenate(chunks) sf.write(str(wav_out), combined, 24000) subprocess.run([ 'ffmpeg', '-y', '-i', str(wav_out), '-codec:a', 'libmp3lame', '-b:a', '192k', str(mp3_out) ], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) wav_out.unlink(missing_ok=True)Technical Analysis
The
audio_filefield is read fromtts/manifest.csvand used directly to construct an output path without validation or containment enforcement.Although the code appears to place output beneath the module's
ttsdirectory,pathlibpermits the supplied value to escape that directory:- A value containing traversal components, such as
../../outside/output.mp3, resolves outside the intended directory. - An absolute value, such as
/tmp/output.mp3, causespathlibto discard the preceding module andttspath components. - Existing writable files can be overwritten because FFmpeg is invoked with
-y. - The corresponding path with a
.wavsuffix is created bysf.write()and subsequently removed withunlink(). mkdir(parents=True)can create attacker-selected directory trees wherever the executing account has permission.
...[truncated 2041 chars]
- A value containing traversal components, such as
- Remediation
View remediation
Remediation Suggestions
Treat
audio_fileas an untrusted filename rather than an unrestricted path.- Establish a fixed output root such as
module_dir / "tts" / "audio". - Reject absolute paths,
..components, empty names, and unexpected extensions. - Resolve the candidate output path and verify that it remains beneath the resolved output root.
- Restrict filenames to a conservative allowlist, such as zero-padded numeric scene identifiers followed by
.mp3. - Validate the derived WAV path independently before writing or deleting it.
- Reject symlinked output directories or use race-resistant filesystem operations where the module may be writable by an untrusted party.
- Avoid unconditional overwrite behavior unless explicitly required.
Example containment validation:
python output_root = (module_dir / 'tts' / 'audio').resolve() supplied = Path(rel_out) if supplied.is_absolute() or '..' in supplied.parts: raise ValueError(f'Invalid audio output path: {rel_out}') if supplied.suffix.lower() != '.mp3': raise ValueError(f'Audio output must use the .mp3 extension: {rel_out}') mp3_out = (output_root / supplied).resolve() try: mp3_out.relative_to(output_root) except ValueError: raise ValueError(f'Audio output escapes the output directory: {rel_out}') wav_out = mp3_out.with_suffix('.wav')For stronger hardening, derive output filenames solely from a strictly validated
slideidentifier instead of allowing the manifest to select filesystem paths.- Establish a fixed output root such as
