T09 · Insecure Skill Coding Practices
- Location
scripts/speech_generate.py:106- Finding
Unvalidated Server-Controlled Audio Download URL
- Content
View full analysis
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: Mediumpython 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 Falsepython 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_urlvalue. When downloading is enabled, the script passes this value directly tourllib.request.urlopenwithout 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
View remediation
Remediation Suggestions
- Parse the URL before making the request and require the
httpsscheme. - Allowlist the exact expected audio-storage hostname or a narrowly defined set of provider-owned hostnames.
- Disable automatic redirects or validate the scheme and hostname of every redirect target.
- Reject URLs containing credentials, unexpected ports, or ambiguous hostname representations.
- Check
Content-Typeagainst expected audio media types, while recognizing that this is only a supplementary control. - Enforce a conservative maximum download size using
Content-Lengthwhen available and a strict byte counter while streaming. - Stream the response in bounded chunks rather than calling
resp.read()without a limit. - Write to a securely created temporary file and atomically move it into place after validation.
- Refuse to overwrite an existing destination by default and reject symbolic-link destinations.
- Use a restrictive file mode for generated files and report validation failures without attempting the write.
- Parse the URL before making the request and require the
