{"id":"CVE-2026-59213","aliases":["GHSA-3wp3-xxj9-5jqq"],"title":"Open WebUI: Cross-user model-list exposure via static cache key in get_all_models (aiocache key= vs key_builder= misuse)","summary":"Open WebUI: Cross-user model-list exposure via static cache key in get_all_models (aiocache key= vs key_builder= misuse)","severity":"low","cvss":3.5,"cwe":["CWE-524"],"vendor":"open-webui","product":"open-webui","ecosystem":"pip","affected":["open-webui >= 0.6.27, < 0.10.0"],"patched":["open-webui 0.10.0"],"published":"2026-07-24","updated":"2026-07-24","source":"GHSA","sourceUrl":"https://github.com/advisories/GHSA-3wp3-xxj9-5jqq","references":[{"url":"https://github.com/open-webui/open-webui/security/advisories/GHSA-3wp3-xxj9-5jqq"},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-59213"},{"url":"https://github.com/open-webui/open-webui/pull/25783"},{"url":"https://github.com/open-webui/open-webui/commit/0fc630b34b2899599dabffffa012afd47599aa75"},{"url":"https://github.com/open-webui/open-webui/releases/tag/v0.10.0"},{"url":"https://github.com/advisories/GHSA-3wp3-xxj9-5jqq"}],"tags":["ghsa","pip"],"epss":0.00373,"epssPercentile":0.28416,"ingestedAt":"2026-07-24T17:34:27.197Z","slug":"CVE-2026-59213","body":"## Overview\n\n## Summary\n\nThe `get_all_models` handlers in `routers/openai.py` and `routers/ollama.py` intended to cache their **permission-filtered** model lists per user, but the `@cached` decorator was misconfigured: it passed a `key=` lambda instead of `key_builder=`. In aiocache 0.12.3 (the pinned version), `key=` is a **static** cache key — a callable passed there is used as a constant object, not invoked per call. As a result the per-user key was never computed, and all callers collided onto a single shared cache entry within the TTL window. During that window, one user's permission-filtered model list could be served to a different authenticated user, crossing the per-user authorization boundary.\n\n## Impact\n\n- **Boundary crossed:** Confidentiality (cross-user). A caller can receive the model list scoped to a *different* security principal than themselves.\n- A user (or admin, or — depending on endpoint reachability — anonymous caller) who populates the cache causes the next caller within the TTL to receive *that* list rather than their own permission-filtered one.\n- What's disclosed is the set of models another principal can access, including potentially the existence and naming of models restricted from the receiving user.\n- Exposure is **incidental and timing-dependent**, not attacker-controlled: the leaked entry is whatever the most recent caller populated within `MODELS_CACHE_TTL` (default 1 second), and the attacker cannot select the victim or force a target's list into the cache.\n\n## Affected component\n\n- `backend/open_webui/routers/openai.py` — `get_all_models` (~line 488)\n- `backend/open_webui/routers/ollama.py` — `get_all_models` (~line 302)\n\nBoth decorated with `@cached(ttl=MODELS_CACHE_TTL, key=lambda ...)`. No other `@cached(... key=lambda ...)` misuse was found elsewhere in the backend.\n\n## Root cause\n\naiocache 0.12's `@cached` treats `key=` as a static key; the per-call hook is `key_builder=` with signature `key_builder(func, *args, **kwargs)`. Passing a callable to `key=` uses the callable object itself as a constant key, so every invocation resolved to the same entry and the intended per-`user.id` namespacing never occurred.\n\n## Reproduction (default config)\n\n1. On a default deployment, configure at least two users with *different* model-access permissions (e.g. one model restricted to user A).\n2. As user A, request the model list (populates the shared cache entry).\n3. Within `MODELS_CACHE_TTL` (default 1s), as user B, request the model list.\n4. User B receives user A's permission-filtered list, including models B is not permitted to see.\n\n## Remediation\n\nReplace `key=` with `key_builder=` at both call sites and adjust the lambda to take the function as its first argument:\n\n```python\n@cached(\n    ttl=MODELS_CACHE_TTL,\n    key_builder=lambda _func, request, user=None: (\n        f'openai_all_models_{user.id}' if user else 'openai_all_models'\n    ),\n)\n```\n\n## Affected packages\n\n- `open-webui >= 0.6.27, < 0.10.0`\n\n## Remediation\n\nUpgrade to a patched release:\n\n- `open-webui 0.10.0`","depth":"sunlit","depthScore":19,"depthScoreParts":{"impact":19.3,"likelihood":0.1,"exploitation":0,"ransomware":0},"changes":[]}