From df42d96c95784b8abe430b935902e42c9ed15205 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Sat, 9 May 2026 21:05:49 +0900 Subject: [PATCH] refac --- backend/open_webui/env.py | 7 +++++++ backend/open_webui/retrieval/utils.py | 3 ++- backend/open_webui/retrieval/web/utils.py | 6 +++--- backend/open_webui/routers/images.py | 4 ++-- backend/open_webui/utils/code_interpreter.py | 4 +++- backend/open_webui/utils/oauth.py | 3 ++- backend/open_webui/utils/tools.py | 5 +++-- 7 files changed, 22 insertions(+), 10 deletions(-) diff --git a/backend/open_webui/env.py b/backend/open_webui/env.py index 8a9b3af365..1ab18fe1c7 100644 --- a/backend/open_webui/env.py +++ b/backend/open_webui/env.py @@ -824,6 +824,13 @@ else: AIOHTTP_CLIENT_SESSION_SSL = os.environ.get('AIOHTTP_CLIENT_SESSION_SSL', 'True').lower() == 'true' +# When False (default), outbound HTTP requests do not follow 3xx redirects. +# This prevents redirect-based SSRF where a public URL 302-redirects to an +# internal address (RFC 1918, loopback, cloud-metadata 169.254.169.254). +# Set to True only if your deployment requires redirect following and you +# have other SSRF protections in place (e.g. egress firewall). +AIOHTTP_CLIENT_ALLOW_REDIRECTS = os.environ.get('AIOHTTP_CLIENT_ALLOW_REDIRECTS', 'False').lower() == 'true' + AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST = os.environ.get( 'AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST', os.environ.get('AIOHTTP_CLIENT_TIMEOUT_OPENAI_MODEL_LIST', '10'), diff --git a/backend/open_webui/retrieval/utils.py b/backend/open_webui/retrieval/utils.py index 4c676705f0..8e672b7a8f 100644 --- a/backend/open_webui/retrieval/utils.py +++ b/backend/open_webui/retrieval/utils.py @@ -43,6 +43,7 @@ from open_webui.retrieval.loaders.youtube import YoutubeLoader from open_webui.env import ( AIOHTTP_CLIENT_TIMEOUT, + AIOHTTP_CLIENT_ALLOW_REDIRECTS, OFFLINE_MODE, ENABLE_FORWARD_USER_INFO_HEADERS, AIOHTTP_CLIENT_SESSION_SSL, @@ -185,7 +186,7 @@ def get_content_from_url(request, url: str) -> str: # re-validation would let an attacker reach private IPs (RFC1918, loopback, # cloud-metadata 169.254.169.254) via a public host that redirects internally. try: - response = requests.get(url, stream=True, timeout=30, allow_redirects=False) + response = requests.get(url, stream=True, timeout=30, allow_redirects=AIOHTTP_CLIENT_ALLOW_REDIRECTS) response.raise_for_status() content_type = response.headers.get('Content-Type', '') except Exception: diff --git a/backend/open_webui/retrieval/web/utils.py b/backend/open_webui/retrieval/web/utils.py index f745c296e1..633f4bba5e 100644 --- a/backend/open_webui/retrieval/web/utils.py +++ b/backend/open_webui/retrieval/web/utils.py @@ -48,7 +48,7 @@ from open_webui.config import ( WEB_FETCH_FILTER_LIST, ) from open_webui.utils.misc import is_string_allowed -from open_webui.env import AIOHTTP_CLIENT_SESSION_SSL +from open_webui.env import AIOHTTP_CLIENT_SESSION_SSL, AIOHTTP_CLIENT_ALLOW_REDIRECTS log = logging.getLogger(__name__) @@ -494,7 +494,7 @@ class SafeWebBaseLoader(WebBaseLoader): # re-validation. Matches the policy enforced on the async _fetch() path below. self.requests_kwargs = { **(self.requests_kwargs or {}), - 'allow_redirects': False, + 'allow_redirects': AIOHTTP_CLIENT_ALLOW_REDIRECTS, } async def _fetch(self, url: str, retries: int = 3, cooldown: int = 2, backoff: float = 1.5) -> str: @@ -513,7 +513,7 @@ class SafeWebBaseLoader(WebBaseLoader): async with session.get( url, **(self.requests_kwargs | kwargs), - allow_redirects=False, + allow_redirects=AIOHTTP_CLIENT_ALLOW_REDIRECTS, ) as response: if self.raise_for_status: response.raise_for_status() diff --git a/backend/open_webui/routers/images.py b/backend/open_webui/routers/images.py index f1559bc034..e79b12dabb 100644 --- a/backend/open_webui/routers/images.py +++ b/backend/open_webui/routers/images.py @@ -22,7 +22,7 @@ from open_webui.config import ( ) from open_webui.constants import ERROR_MESSAGES from open_webui.retrieval.web.utils import validate_url -from open_webui.env import AIOHTTP_CLIENT_SESSION_SSL, ENABLE_FORWARD_USER_INFO_HEADERS +from open_webui.env import AIOHTTP_CLIENT_SESSION_SSL, AIOHTTP_CLIENT_ALLOW_REDIRECTS, ENABLE_FORWARD_USER_INFO_HEADERS from open_webui.utils.session_pool import get_session from open_webui.models.chats import Chats @@ -814,7 +814,7 @@ async def image_edits( # public host that redirects internally (e.g. cloud-metadata exfil). validate_url(data) session = await get_session() - async with session.get(data, ssl=AIOHTTP_CLIENT_SESSION_SSL, allow_redirects=False) as r: + async with session.get(data, ssl=AIOHTTP_CLIENT_SESSION_SSL, allow_redirects=AIOHTTP_CLIENT_ALLOW_REDIRECTS) as r: r.raise_for_status() image_data = base64.b64encode(await r.read()).decode('utf-8') diff --git a/backend/open_webui/utils/code_interpreter.py b/backend/open_webui/utils/code_interpreter.py index 3e30c419ae..52ddea24a7 100644 --- a/backend/open_webui/utils/code_interpreter.py +++ b/backend/open_webui/utils/code_interpreter.py @@ -8,6 +8,8 @@ import aiohttp import websockets from pydantic import BaseModel +from open_webui.env import AIOHTTP_CLIENT_ALLOW_REDIRECTS + logger = logging.getLogger(__name__) @@ -88,7 +90,7 @@ class JupyterCodeExecuter: async with self.session.post( 'login', data={'_xsrf': xsrf_token, 'password': self.password}, - allow_redirects=False, + allow_redirects=AIOHTTP_CLIENT_ALLOW_REDIRECTS, ) as response: response.raise_for_status() self.session.cookie_jar.update_cookies(response.cookies) diff --git a/backend/open_webui/utils/oauth.py b/backend/open_webui/utils/oauth.py index 56341bdca3..320124ba4d 100644 --- a/backend/open_webui/utils/oauth.py +++ b/backend/open_webui/utils/oauth.py @@ -71,6 +71,7 @@ from open_webui.config import ( from open_webui.constants import ERROR_MESSAGES, WEBHOOK_MESSAGES from open_webui.env import ( AIOHTTP_CLIENT_SESSION_SSL, + AIOHTTP_CLIENT_ALLOW_REDIRECTS, WEBUI_NAME, WEBUI_AUTH_COOKIE_SAME_SITE, WEBUI_AUTH_COOKIE_SECURE, @@ -740,7 +741,7 @@ class OAuthClientManager: async with aiohttp.ClientSession(trust_env=True) as session: async with session.get( authorization_url, - allow_redirects=False, + allow_redirects=AIOHTTP_CLIENT_ALLOW_REDIRECTS, ssl=AIOHTTP_CLIENT_SESSION_SSL, ) as resp: if resp.status < 400: diff --git a/backend/open_webui/utils/tools.py b/backend/open_webui/utils/tools.py index 20e8ce365c..6489443285 100644 --- a/backend/open_webui/utils/tools.py +++ b/backend/open_webui/utils/tools.py @@ -47,6 +47,7 @@ from open_webui.utils.access_control import has_access, has_connection_access from open_webui.config import BYPASS_ADMIN_ACCESS_CONTROL from open_webui.env import ( AIOHTTP_CLIENT_SESSION_SSL, + AIOHTTP_CLIENT_ALLOW_REDIRECTS, AIOHTTP_CLIENT_TIMEOUT, AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER, AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER_DATA, @@ -1433,7 +1434,7 @@ async def execute_tool_server( headers=headers, cookies=cookies, ssl=AIOHTTP_CLIENT_SESSION_TOOL_SERVER_SSL, - allow_redirects=False, + allow_redirects=AIOHTTP_CLIENT_ALLOW_REDIRECTS, ) as response: if response.status >= 400: text = await response.text() @@ -1458,7 +1459,7 @@ async def execute_tool_server( headers=headers, cookies=cookies, ssl=AIOHTTP_CLIENT_SESSION_TOOL_SERVER_SSL, - allow_redirects=False, + allow_redirects=AIOHTTP_CLIENT_ALLOW_REDIRECTS, ) as response: if response.status >= 400: text = await response.text()