This commit is contained in:
Timothy Jaeryang Baek 2026-05-09 21:05:49 +09:00
parent 2fa3b84241
commit df42d96c95
7 changed files with 22 additions and 10 deletions

View file

@ -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'),

View file

@ -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:

View file

@ -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()

View file

@ -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')

View file

@ -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)

View file

@ -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:

View file

@ -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()