T09 · Insecure Skill Coding Practices
Warning
- Location
- speakturbo/daemon_streaming.py:71
- Finding
- Unauthenticated Local TTS Endpoint Permits Cross-Site Resource Exhaustion<![CDATA[ ## Vulnerability Details **File Location**: `speakturbo/daemon_streaming.py:71-78, 97-118` **Vulnerability Type**: Unauthenticated local API, cross-site request exposure, and missing resource limits **Risk Level**: Medium ### Vulnerable Code ```python # DNS rebinding protection - only allow localhost @app.middleware("http") async def validate_host(request: Request, call_next): host = request.headers.get("host", "").split(":")[0] if host not in {"127.0.0.1", "localhost"}: return JSONResponse(status_code=403, content={"detail": "Forbidden"}) return await call_next(request) ``` ```python @app.get("/tts") async def tts(text: str, voice: str = "alba"): """Ultra-fast streaming TTS.""" global _last_request_time _last_request_time = time.time() if not text or not text.strip(): raise HTTPException(status_code=400, detail="Text cannot be empty") if voice not in VOICES: raise HTTPException(status_code=400, detail=f"Voice must be one of: {VOICES}") model = get_model() voice_state = get_voice_state(voice) async def generate(): yield wav_header(model.sample_rate) for chunk in model.generate_audio_stream(voice_state, text.strip()): yield (chunk.clamp(-1, 1) * 32767).short().numpy().tobytes() await asyncio.sleep(0) yield bytes(int(model.sample_rate * 0.15) * 2) # Trailing silence return StreamingResponse(generate(), media_type="audio/wav") ``` ### Technical Analysis The daemon binds to localhost, but the `/tts` endpoint does not require an authentication token and does not validate the request's `Origin` or other proof that the request came from the trusted CLI. The Host-header middleware only verifies that the request targets `127.0.0.1` or `localhost`. It provides partial DNS-rebinding protection but does not authenticate the caller. A hostile webpage can attempt to send requests directly to a loopback address. Brow ...[truncated 2283 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Generate a cryptographically random authentication token whenever the daemon starts. 2. Store the token in a user-owned file under `~/.speakturbo/` with permissions restricted to the current user. 3. Require the token in an HTTP header, such as `Authorization: Bearer <token>`, on every endpoint. 4. Change `/tts` from GET to POST and accept text through a request body. 5. Reject browser-originated requests unless their `Origin` is explicitly trusted. Do not rely solely on CORS response headers as authentication. 6. Impose a strict maximum text length appropriate for the intended TTS workload. 7. Add per-client rate limiting and a small global limit on concurrent generations. 8. Apply request and generation timeouts, and cancel model generation when the client disconnects. 9. Continue binding only to `127.0.0.1`, while retaining Host validation as defense in depth. 10. Add tests demonstrating that missing or incorrect tokens, untrusted origins, oversized text, and excessive concurrent requests are rejected. ]]>
