T09 · Insecure Skill Coding Practices
- Location
scripts/run_reminders.py:139- Finding
Path Traversal Through an Unvalidated Reminder Identifier
- Content
View full analysis
Vulnerability Details
File Location:
scripts/run_reminders.py, lines 72–78 and 135–147
Vulnerability Type: Path traversal leading to an out-of-scope file write
Risk Level: MediumVulnerable Code
python def tts_and_save(text: str, out_path: Path, api_key: str, voice_id: str = DEFAULT_VOICE) -> Path: headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"} body = { "model": "SenseAudio-TTS-1.0", "text": text, "stream": False, "voice_setting": {"voice_id": voice_id}, } r = requests.post(TTS_URL, headers=headers, json=body, timeout=30) r.raise_for_status() data = r.json() if data.get("base_resp", {}).get("status_code") != 0: raise RuntimeError(data.get("base_resp", {}).get("status_msg", "TTS failed")) hex_audio = data.get("data", {}).get("audio") if not hex_audio: raise RuntimeError("No audio in response") out_path.parent.mkdir(parents=True, exist_ok=True) with open(out_path, "wb") as f: f.write(bytes.fromhex(hex_audio)) return out_pathpython for r in to_notify: event = r.get("event", "提醒") text = f"提醒:{event}" if dry_run: print(f"[dry-run] would speak: {text}", file=sys.stderr) continue out_path = data_dir / "audio" / f"reminder_{r.get('id', '')}.mp3" try: tts_and_save(text, out_path, api_key, voice_id) play_audio(out_path) except Exception as e: print(f"TTS/play failed for reminder {r.get('id')}: {e}", file=sys.stderr) continue r["status"] = "notified"Technical Analysis
The reminder identifier is loaded from
reminders.jsonand interpolated directly into an output path without validating its characters or checking the resolved destination. Path separators and parent-directory components in the identifier are therefore interpret ...[truncated 1937 chars]- Remediation
View remediation
Remediation Suggestions
-
Generate reminder identifiers internally using UUIDs rather than accepting storage-derived identifiers as filenames.
-
Enforce a strict allowlist before using an identifier, such as
[A-Za-z0-9_-]+, with a reasonable maximum length. -
Construct and resolve the output path, then verify that it remains beneath the resolved audio directory:
python import re from uuid import UUID reminder_id = str(r.get("id", "")) if not re.fullmatch(r"[A-Za-z0-9_-]{1,128}", reminder_id): raise ValueError("Invalid reminder ID") audio_dir = (data_dir / "audio").resolve() audio_dir.mkdir(parents=True, exist_ok=True) out_path = (audio_dir / f"reminder_{reminder_id}.mp3").resolve() if out_path.parent != audio_dir: raise ValueError("Output path escapes audio directory") -
Where supported, use no-follow and exclusive file-opening controls to reduce symlink and overwrite risks.
-
Validate the complete reminder schema when records are created and again when they are loaded from disk.
-
Write reminder data and audio files using restrictive permissions appropriate for potentially sensitive reminder content.
-
