T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/generate_audio.py:50
- Finding
- Predictable Shared Temporary Files Allow Local File Overwrite and Cross-Run Interference<![CDATA[ ## Vulnerability Details **File Location**: `scripts/generate_audio.py`, lines 50–72 **Vulnerability Type**: Unsafe predictable temporary files **Risk Level**: Medium ### Vulnerable Code ```python tmps = [] for i, s in enumerate(segs): if not s["text"].strip(): continue tmp = f"/tmp/pod_{i:03d}.mp3" v = voice_for(s["speaker"], custom_voice) try: subprocess.run( ["edge-tts", "--voice", v, "--text", s["text"], "--write-media", tmp], capture_output=True, timeout=60, check=True ) tmps.append(tmp) except Exception as e: print(f"⚠️ 片段{i}失败: {e}") if not tmps: return {"segments": len(segs), "output": None, "error": "no segments generated"} has_ffmpeg = os.system("which ffmpeg >/dev/null 2>&1") == 0 if has_ffmpeg and len(tmps) > 1: lst = "/tmp/pod_list.txt" with open(lst, "w") as f: for t in tmps: f.write(f"file '{t}'\n") ``` ### Technical Analysis The audio generation process creates temporary media files using predictable names such as `/tmp/pod_000.mp3` and writes the FFmpeg input list to the fixed path `/tmp/pod_list.txt`. The system temporary directory is commonly writable by all local users. The code does not: - Create a private, randomly named temporary directory. - Use exclusive file creation. - Reject symbolic links. - Verify file ownership or type before writing. - Isolate files belonging to concurrent executions. The call to `open("/tmp/pod_list.txt", "w")` follows symbolic links and truncates the resolved target. A local attacker who can write to `/tmp` can pre-create that path as a symbolic link to another file writable by the victim. Predictable media names also allow concurrent or malicious executions to overwrite, replace, or mix audio segments. The temporary list file is not included in the cleanup routine, leaving stale state in the shared temporary directory. ### Attack Path 1. A local attacker identifies that the ...[truncated 1546 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions Use Python’s `tempfile` module to create a private, randomly named directory for every invocation: ```python import tempfile from pathlib import Path with tempfile.TemporaryDirectory(prefix="podcast-generator-") as temp_dir: temp_path = Path(temp_dir) tmps = [] for i, segment in enumerate(segs): if not segment["text"].strip(): continue media_file = temp_path / f"segment_{i:03d}.mp3" voice = voice_for(segment["speaker"], custom_voice) subprocess.run( [ "edge-tts", "--voice", voice, "--text", segment["text"], "--write-media", str(media_file), ], capture_output=True, timeout=60, check=True, ) tmps.append(media_file) concat_file = temp_path / "concat.txt" with concat_file.open("x", encoding="utf-8") as handle: for media_file in tmps: handle.write(f"file '{media_file}'\n") ``` Additional hardening should include: 1. Keep all temporary artifacts inside the private temporary directory. 2. Use exclusive creation mode where practical. 3. Do not run the Skill with elevated privileges. 4. Check the FFmpeg return code by using `check=True`. 5. Write the final output to a temporary file in the destination directory and atomically rename it after successful generation. 6. Ensure cleanup covers every temporary artifact, including the FFmpeg concat list. ]]>
