From 03547759179672d216d2e1376dd1ae4fdad76a94 Mon Sep 17 00:00:00 2001 From: Classic298 <27028174+Classic298@users.noreply.github.com> Date: Mon, 1 Jun 2026 00:11:19 +0200 Subject: [PATCH] fix: decode terminal proxy path until stable to block multi-encoded traversal (#25157) _sanitize_proxy_path decoded the proxy path once before the '..' check, so a double-encoded payload (%252e%252e) survived the check as %2e%2e and was then re-decoded into '..' by the upstream terminal server, defeating the traversal guard. Decode until stable so no encoded traversal sequence can reach the upstream. Single-encoded payloads were already rejected; this closes the double (and deeper) encoding bypass. Co-authored-by: sermikr0 <230672901+sermikr0@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 (1M context) --- backend/open_webui/routers/terminals.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/backend/open_webui/routers/terminals.py b/backend/open_webui/routers/terminals.py index d2ef5f0ea3..4c61d6ec1d 100644 --- a/backend/open_webui/routers/terminals.py +++ b/backend/open_webui/routers/terminals.py @@ -35,7 +35,14 @@ def _sanitize_proxy_path(path: str) -> str | None: Trailing slashes are preserved — many upstream frameworks treat ``/path`` and ``/path/`` differently. """ - decoded = unquote(path) + # Decode until stable: a single unquote pass leaves %252e%252e as %2e%2e, + # which the upstream then re-decodes into '..', bypassing the check below. + decoded = path + for _ in range(8): + once = unquote(decoded) + if once == decoded: + break + decoded = once had_trailing_slash = decoded.endswith('/') normalized = posixpath.normpath(decoded) # Remove any leading slashes that would reset the base