From 05098d25a58d03738e01c4e85e8852c3b4ad849c Mon Sep 17 00:00:00 2001 From: Classic298 <27028174+Classic298@users.noreply.github.com> Date: Tue, 16 Jun 2026 22:44:11 +0200 Subject: [PATCH] Fail closed when the proxy/redirect path decode cap is exceeded (#26050) The decode-until-stable loops in _sanitize_proxy_path (terminals.py, cap 8) and _safe_static_redirect_path (models.py, cap 2) proceed with whatever remains after the cap instead of rejecting it. A path encoded more times than the cap therefore exits the loop still percent-encoded, passes the literal '..' / prefix checks (the dots are still %2E, not '..'), and is forwarded to the upstream terminal server, or emitted as a redirect Location, which then decodes it once more and resolves the traversal. This is the residual of the decode-until-stable hardening added for CVE-2026-54017: the cap is a fixed depth, not a true stability guarantee. Reject when the value is still not stable after the cap (unquote(x) != x), so anything encoded more deeply than the cap fails closed rather than being forwarded. Legitimate paths stabilize within a pass or two and are unaffected. Co-authored-by: DavidCarliez <271374756+DavidCarliez@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 (1M context) --- backend/open_webui/routers/models.py | 3 +++ backend/open_webui/routers/terminals.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/backend/open_webui/routers/models.py b/backend/open_webui/routers/models.py index 75ee4e723b..75c37b0eb9 100644 --- a/backend/open_webui/routers/models.py +++ b/backend/open_webui/routers/models.py @@ -60,6 +60,9 @@ def _safe_static_redirect_path(url: str) -> str | None: if decoded == path: break path = decoded + # Fail closed: a value still encoded after the cap would be decoded further downstream. + if unquote(path) != path: + return None if '\x00' in path or '\\' in path: return None if not path.startswith('/'): diff --git a/backend/open_webui/routers/terminals.py b/backend/open_webui/routers/terminals.py index 6a942cf9b2..4c71322bfb 100644 --- a/backend/open_webui/routers/terminals.py +++ b/backend/open_webui/routers/terminals.py @@ -43,6 +43,9 @@ def _sanitize_proxy_path(path: str) -> str | None: if once == decoded: break decoded = once + # Fail closed: still encoded after the cap means the upstream would decode further into traversal. + if unquote(decoded) != decoded: + return None had_trailing_slash = decoded.endswith('/') normalized = posixpath.normpath(decoded) # Remove any leading slashes that would reset the base