mirror of
https://github.com/open-webui/open-webui.git
synced 2026-09-28 02:07:05 -04:00
fix:image url validation and signout post (#24420)
* refac(routers): reject external URLs in profile/model image handlers * refac(ui): centralize image URL validation in safeImageUrl helper * refac(auths): make signout POST-only * refac: gate external profile image redirect behind ENABLE_PROFILE_IMAGE_URL_FORWARDING Restore the 302 redirect for external http(s) profile image URLs in the user and model profile-image endpoints, but gate it behind a new ENABLE_PROFILE_IMAGE_URL_FORWARDING env flag (default: True). Existing deployments that rely on external profile image forwarding continue to work unchanged. Operators who want to suppress the redirect (to prevent client-side IP/UA/Referer leaks) can set the flag to False.
This commit is contained in:
@@ -249,6 +249,19 @@ ENABLE_STAR_SESSIONS_MIDDLEWARE = os.environ.get('ENABLE_STAR_SESSIONS_MIDDLEWAR
|
||||
|
||||
ENABLE_EASTER_EGGS = os.environ.get('ENABLE_EASTER_EGGS', 'True').lower() == 'true'
|
||||
|
||||
####################################
|
||||
# ENABLE_PROFILE_IMAGE_URL_FORWARDING
|
||||
####################################
|
||||
|
||||
# When True (default), the user and model profile-image endpoints
|
||||
# honour external http(s) URLs stored in profile_image_url by issuing a
|
||||
# 302 redirect to the original origin. Set to False to suppress the
|
||||
# redirect (prevents client-side IP/UA/Referer leaks to attacker-
|
||||
# controlled origins) and fall through to the default image instead.
|
||||
ENABLE_PROFILE_IMAGE_URL_FORWARDING = os.environ.get(
|
||||
'ENABLE_PROFILE_IMAGE_URL_FORWARDING', 'True'
|
||||
).lower() == 'true'
|
||||
|
||||
####################################
|
||||
# WEBUI_BUILD_HASH
|
||||
####################################
|
||||
|
||||
@@ -785,7 +785,7 @@ async def signup(
|
||||
raise HTTPException(500, detail='An internal error occurred during signup.')
|
||||
|
||||
|
||||
@router.get('/signout')
|
||||
@router.post('/signout')
|
||||
async def signout(request: Request, response: Response, db: AsyncSession = Depends(get_async_session)):
|
||||
# get auth token from headers or cookies
|
||||
token = None
|
||||
|
||||
@@ -28,8 +28,8 @@ from fastapi import (
|
||||
Depends,
|
||||
HTTPException,
|
||||
Request,
|
||||
status,
|
||||
Response,
|
||||
status,
|
||||
)
|
||||
from fastapi.responses import RedirectResponse, StreamingResponse
|
||||
|
||||
@@ -37,6 +37,7 @@ from fastapi.responses import RedirectResponse, StreamingResponse
|
||||
from open_webui.utils.auth import get_admin_user, get_verified_user
|
||||
from open_webui.utils.access_control import has_permission, filter_allowed_access_grants
|
||||
from open_webui.config import BYPASS_ADMIN_ACCESS_CONTROL
|
||||
from open_webui.env import ENABLE_PROFILE_IMAGE_URL_FORWARDING
|
||||
from open_webui.internal.db import get_async_session
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
@@ -484,10 +485,14 @@ async def get_model_profile_image(
|
||||
|
||||
if profile_image_url:
|
||||
if profile_image_url.startswith('http'):
|
||||
return Response(
|
||||
status_code=status.HTTP_302_FOUND,
|
||||
headers={'Location': profile_image_url},
|
||||
)
|
||||
if ENABLE_PROFILE_IMAGE_URL_FORWARDING:
|
||||
return Response(
|
||||
status_code=status.HTTP_302_FOUND,
|
||||
headers={'Location': profile_image_url},
|
||||
)
|
||||
# When forwarding is disabled, fall through to the
|
||||
# default image to prevent client-side IP/UA/Referer
|
||||
# leaks via 302 redirect to external origins.
|
||||
elif profile_image_url.startswith('data:image'):
|
||||
try:
|
||||
header, base64_data = profile_image_url.split(',', 1)
|
||||
|
||||
@@ -29,7 +29,7 @@ from open_webui.models.users import (
|
||||
)
|
||||
|
||||
from open_webui.constants import ERROR_MESSAGES
|
||||
from open_webui.env import STATIC_DIR
|
||||
from open_webui.env import ENABLE_PROFILE_IMAGE_URL_FORWARDING, STATIC_DIR
|
||||
from open_webui.internal.db import get_async_session
|
||||
|
||||
|
||||
@@ -478,12 +478,15 @@ async def get_user_profile_image_by_id(user_id: str, user=Depends(get_verified_u
|
||||
user = await Users.get_user_by_id(user_id)
|
||||
if user:
|
||||
if user.profile_image_url:
|
||||
# check if it's url or base64
|
||||
if user.profile_image_url.startswith('http'):
|
||||
return Response(
|
||||
status_code=status.HTTP_302_FOUND,
|
||||
headers={'Location': user.profile_image_url},
|
||||
)
|
||||
if ENABLE_PROFILE_IMAGE_URL_FORWARDING:
|
||||
return Response(
|
||||
status_code=status.HTTP_302_FOUND,
|
||||
headers={'Location': user.profile_image_url},
|
||||
)
|
||||
# When forwarding is disabled, fall through to the
|
||||
# default image to prevent client-side IP/UA/Referer
|
||||
# leaks via 302 redirect to external origins.
|
||||
elif user.profile_image_url.startswith('data:image'):
|
||||
try:
|
||||
header, base64_data = user.profile_image_url.split(',', 1)
|
||||
|
||||
Reference in New Issue
Block a user