GHSA-3WP3-XXJ9-5JQQ

Vulnerability from github – Published: 2026-07-24 17:03 – Updated: 2026-07-24 17:03
VLAI
Summary
Open WebUI: Cross-user model-list exposure via static cache key in get_all_models (aiocache key= vs key_builder= misuse)
Details

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.pyget_all_models (~line 488)
  • backend/open_webui/routers/ollama.pyget_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:

@cached(
    ttl=MODELS_CACHE_TTL,
    key_builder=lambda _func, request, user=None: (
        f'openai_all_models_{user.id}' if user else 'openai_all_models'
    ),
)
Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "PyPI",
        "name": "open-webui"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0.6.27"
            },
            {
              "fixed": "0.10.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-59213"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-524"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-07-24T17:03:21Z",
    "nvd_published_at": "2026-07-09T17:17:02Z",
    "severity": "LOW"
  },
  "details": "## 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 \u2014 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\u0027s 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 \u2014 depending on endpoint reachability \u2014 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\u0027s 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\u0027s list into the cache.\n\n## Affected component\n\n- `backend/open_webui/routers/openai.py` \u2014 `get_all_models` (~line 488)\n- `backend/open_webui/routers/ollama.py` \u2014 `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\u0027s `@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\u0027s 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\u0027openai_all_models_{user.id}\u0027 if user else \u0027openai_all_models\u0027\n    ),\n)\n```",
  "id": "GHSA-3wp3-xxj9-5jqq",
  "modified": "2026-07-24T17:03:21Z",
  "published": "2026-07-24T17:03:21Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/open-webui/open-webui/security/advisories/GHSA-3wp3-xxj9-5jqq"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-59213"
    },
    {
      "type": "WEB",
      "url": "https://github.com/open-webui/open-webui/pull/25783"
    },
    {
      "type": "WEB",
      "url": "https://github.com/open-webui/open-webui/commit/0fc630b34b2899599dabffffa012afd47599aa75"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/open-webui/open-webui"
    },
    {
      "type": "WEB",
      "url": "https://github.com/open-webui/open-webui/releases/tag/v0.10.0"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:L/I:N/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Open WebUI: Cross-user model-list exposure via static cache key in get_all_models (aiocache key= vs key_builder= misuse)"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…

Loading…