CVE-2026-59213
mediumpriority · track · 20EPSS 0.004 · 30 %ileEUUSEPSS · scored 2026-10-23
Open WebUI is an extensible, feature-rich, and user-friendly self-hosted AI platform. From 0.6.27 before 0.10.0, get_all_models handlers in routers/openai.py and routers/ollama.py passed a lambda to aiocache key instead of key_builder, causing permission-filtered per-user model lists to share a static cache entry and exposing one user’s model list to another caller during the TTL window. This issue is fixed in version 0.10.0.
cvss
Technical severity, 0–10. One row per scoring source.
base score 0–10 · higher = more severe · hover a vector to decode⚠ sources disagree · low–medium · 3.5–5
ssvc
A stakeholder decision — act, attend, or track — from exploitation and impact.
weaknesses
The CWE weakness classes this flaw belongs to.
affected products (3)
Products and version ranges known to be affected.
advisories (2)
Vendor and ecosystem advisories describing this.
- GHSA-3wp3-xxj9-5jqqOpen WebUI: Cross-user model-list exposure via static cache key in get_all_models (aiocache key= vs key_builder= misuse)2026-07-24ghsa · osvUS
## Summary The `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. ## Impact - **Boundary crossed:** Confidentiality (cross-user). A caller can receive the model list scoped to a *different* security principal than themselves. - 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. - 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. - 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. ## Affected component - `backend/open_webui/routers/openai.py` — `get_all_models` (~line 488) - `backend/open_webui/routers/ollama.py` — `get_all_models` (~line 302) Both decorated with `@cached(ttl=MODELS_CACHE_TTL, key=lambda ...)`. No other `@cached(... key=lambda ...)` misuse was found elsewhere in the backend. ## Root cause aiocache 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. ## Reproduction (default config) 1. On a default deployment, configure at least two users with *different* model-access permissions (e.g. one model restricted to user A). 2. As user A, request the model list (populates the shared cache entry). 3. Within `MODELS_CACHE_TTL` (default 1s), as user B, request the model list. 4. User B receives user A's permission-filtered list, including models B is not permitted to see. ## Remediation Replace `key=` with `key_builder=` at both call sites and adjust the lambda to take the function as its first argument: ```python @cached( ttl=MODELS_CACHE_TTL, key_builder=lambda _func, request, user=None: ( f'openai_all_models_{user.id}' if user else 'openai_all_models' ), ) ```
- PYSEC-2026-3589Open WebUI: Cross-user model-list exposure via static cache key in get_all_models (aiocache key= vs key_builder= misuse)2026-08-04vendor · osvUS
## Summary The `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. ## Impact - **Boundary crossed:** Confidentiality (cross-user). A caller can receive the model list scoped to a *different* security principal than themselves. - 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. - 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. - 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. ## Affected component - `backend/open_webui/routers/openai.py` — `get_all_models` (~line 488) - `backend/open_webui/routers/ollama.py` — `get_all_models` (~line 302) Both decorated with `@cached(ttl=MODELS_CACHE_TTL, key=lambda ...)`. No other `@cached(... key=lambda ...)` misuse was found elsewhere in the backend. ## Root cause aiocache 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. ## Reproduction (default config) 1. On a default deployment, configure at least two users with *different* model-access permissions (e.g. one model restricted to user A). 2. As user A, request the model list (populates the shared cache entry). 3. Within `MODELS_CACHE_TTL` (default 1s), as user B, request the model list. 4. User B receives user A's permission-filtered list, including models B is not permitted to see. ## Remediation Replace `key=` with `key_builder=` at both call sites and adjust the lambda to take the function as its first argument: ```python @cached( ttl=MODELS_CACHE_TTL, key_builder=lambda _func, request, user=None: ( f'openai_all_models_{user.id}' if user else 'openai_all_models' ), ) ```
also known as (6)
Other names and identifiers for this entity.
sources
Which sources assert this record, and when.
references (12)
External citations — patches, advisories, analysis.
- https://github.com/open-webui/open-webui/commit/0fc630b34b2899599dabffffa012afd47599aa75cnnvd
- https://github.com/open-webui/open-webui/pull/25783cnnvd
- https://github.com/open-webui/open-webui/releases/tag/v0.10.0cnnvd
- https://github.com/open-webui/open-webui/security/advisories/GHSA-3wp3-xxj9-5jqqcnnvd
- https://github.com/open-webui/open-webui/commit/0fc630b34b2899599dabffffa012afd47599aa75enisa_euvd
- https://github.com/open-webui/open-webui/pull/25783enisa_euvd
- https://github.com/open-webui/open-webui/releases/tag/v0.10.0enisa_euvd
- https://github.com/open-webui/open-webui/security/advisories/GHSA-3wp3-xxj9-5jqqenisa_euvd
Attackers weaponise vulnerabilities like this against internet-exposed services. See what is exposed on your perimeter with a free passive scan.
Free scan