T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/speech_generate.py:106
- Finding
- Unvalidated Server-Controlled Audio Download URL## Vulnerability Details **File Location**: `scripts/speech_generate.py:106-110, 133-138, 320-337` **Vulnerability Type**: Unrestricted remote resource retrieval and unsafe file download **Risk Level**: Medium ```python if error_code == 0: result = data.get("result", {}) return { "success": True, "orderid": result.get("orderid", ""), "audio_url": result.get("audio_url", ""), } ``` ```python def download_audio(url: str, save_path: str) -> bool: """Download the audio file locally and return whether it succeeded.""" try: with urllib.request.urlopen(url, timeout=30) as resp: content = resp.read() Path(save_path).write_bytes(content) return True except Exception as e: print(f"Download failed: {e}") return False ``` ```python audio_url = result["audio_url"] orderid = result["orderid"] print(f"Audio synthesis succeeded.") print(f"Order ID: {orderid}") print(f"Audio URL:") print(f"{audio_url}") if parsed["download"] or parsed["output"]: if parsed["output"]: save_path = parsed["output"] else: timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") save_path = f"speech_{timestamp}.wav" if download_audio(audio_url, save_path): size_kb = Path(save_path).stat().st_size // 1024 print(f"Download completed. File size: {size_kb} KB") ``` ### Technical Analysis The remote Juhe API controls the `audio_url` value. When downloading is enabled, the script passes this value directly to `urllib.request.urlopen` without validating: - The URL scheme - The destination hostname - Redirect destinations - Response size - Response content type - Whether the response is actually an audio file The script also reads the complete response into memory before writing it. A compromised provider, compromised API response path, or provider-side de ...[truncated 2040 chars]
- Remediation
- ## Remediation Suggestions 1. Parse the URL before making the request and require the `https` scheme. 2. Allowlist the exact expected audio-storage hostname or a narrowly defined set of provider-owned hostnames. 3. Disable automatic redirects or validate the scheme and hostname of every redirect target. 4. Reject URLs containing credentials, unexpected ports, or ambiguous hostname representations. 5. Check `Content-Type` against expected audio media types, while recognizing that this is only a supplementary control. 6. Enforce a conservative maximum download size using `Content-Length` when available and a strict byte counter while streaming. 7. Stream the response in bounded chunks rather than calling `resp.read()` without a limit. 8. Write to a securely created temporary file and atomically move it into place after validation. 9. Refuse to overwrite an existing destination by default and reject symbolic-link destinations. 10. Use a restrictive file mode for generated files and report validation failures without attempting the write.
