T05 · Unauthorized Access and Privilege Escalation
Error
- Location
- bridge/server.py:81
- Finding
- Authentication Is Deliberately Fail-Open<![CDATA[ ## Vulnerability Details **File Location**: `bridge/server.py:81-95`, `bridge/server.py:146-155` **Vulnerability Type**: Broken authentication / fail-open access control **Risk Level**: Critical ### Vulnerable Code ```python async def verify_token(request: Request) -> str: auth = request.headers.get("authorization", "") token = auth.replace("Bearer ", "").strip() # Also check x-api-key header (ElevenLabs sometimes uses this) x_api_key = request.headers.get("x-api-key", "") if not token and x_api_key: token = x_api_key.strip() if token and token != LLM_BRIDGE_TOKEN: logger.warning(f"Invalid auth token (accepting anyway): {token[:15]}...") return token or "anonymous" ``` The result is called but never checked: ```python raw_body = await request.body() body_str = raw_body.decode("utf-8", errors="replace") await verify_token(request) # Parse body body = {} ``` ### Technical Analysis `verify_token()` accepts all three authentication states: 1. A valid bridge token. 2. An invalid bridge token. 3. No token at all, represented as `"anonymous"`. The explicit `"accepting anyway"` branch makes the documented Bearer-token control nonfunctional. The caller does not inspect the returned identity or reject anonymous access. Because the bridge invokes Anthropic and BlueColumn with credentials held by the server, remote clients effectively gain indirect use of those privileged credentials even though the credentials themselves are not returned. The warning also logs the first 15 characters of an attacker-supplied token. Token values should not be logged because users may accidentally submit real credentials for another service. ### Attack Path 1. The operator exposes port 8013 through the documented Cloudflare or ngrok tunnel. 2. An attacker sends `POST /v1/chat/completions` with no `Authorization` header, or with any arbitrary Bearer token. 3. `verify_token()` returns `"anonymous"` or logs the mi ...[truncated 911 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions - Reject missing credentials with HTTP 401. - Reject invalid credentials with HTTP 401 or 403. - Compare secrets with `secrets.compare_digest()` to reduce timing leakage. - Authenticate every non-public endpoint, not only chat completion requests. - Never log any portion of a submitted credential. - Configure a strong, randomly generated bridge token and refuse startup when the default placeholder is still configured. - Add request rate limits, body-size limits, and API usage quotas. - Where supported, verify ElevenLabs, Deepgram, or telephony-provider signatures rather than relying only on a shared static token. Example: ```python import secrets async def verify_token(request: Request) -> None: auth = request.headers.get("authorization", "") token = auth.removeprefix("Bearer ").strip() if not token: token = request.headers.get("x-api-key", "").strip() if not LLM_BRIDGE_TOKEN or not secrets.compare_digest(token, LLM_BRIDGE_TOKEN): raise HTTPException(status_code=401, detail="Invalid authentication") ``` ]]>
