mirror of
https://github.com/open-webui/open-webui.git
synced 2026-08-28 05:27:35 +00:00
feat: cap profile image data URI size to bound model/avatar bloat (#25476)
* feat: cap profile image data URI size to bound model/avatar bloat validate_profile_image_url() validated data-URI format (MIME allowlist, SVG rejection, scheme checks) but never its length, so a valid data:image/...;base64,<huge> passed for both custom-model icons and user avatars. Large inline images bloat Postgres and the Redis MODELS hash and degrade model-list latency. Add PROFILE_IMAGE_MAX_DATA_URI_SIZE (default 256 KiB, 0 disables) and reject oversized data URIs in the shared validator, so both model meta (ModelMeta.profile_image_url) and user avatars (UpdateProfileForm) are bounded at one chokepoint. ModelMeta already clears invalid values to None on read, so existing oversized icons stop propagating into the MODELS hash on the next refresh. Fixes #25468 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: default PROFILE_IMAGE_MAX_DATA_URI_SIZE to None (no cap) Per review: opt-in rather than a 256 KiB default. Unset leaves data URIs uncapped; the validator already skips the check on a falsy value. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e2502ec80f
commit
83890f18b9
2 changed files with 17 additions and 1 deletions
|
|
@ -780,6 +780,13 @@ PROFILE_IMAGE_ALLOWED_MIME_TYPES = frozenset(
|
|||
if t.strip()
|
||||
)
|
||||
|
||||
# Max stored length (bytes) of a data:image profile URI; bounds Postgres/Redis
|
||||
# bloat from inline avatars and model icons. Unset (default) disables the cap.
|
||||
_profile_image_max_data_uri_size = os.getenv('PROFILE_IMAGE_MAX_DATA_URI_SIZE', '').strip()
|
||||
PROFILE_IMAGE_MAX_DATA_URI_SIZE = (
|
||||
int(_profile_image_max_data_uri_size) if _profile_image_max_data_uri_size else None
|
||||
)
|
||||
|
||||
####################################
|
||||
# Forward Headers
|
||||
####################################
|
||||
|
|
|
|||
|
|
@ -3,7 +3,10 @@
|
|||
import re
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from open_webui.env import PROFILE_IMAGE_ALLOWED_MIME_TYPES
|
||||
from open_webui.env import (
|
||||
PROFILE_IMAGE_ALLOWED_MIME_TYPES,
|
||||
PROFILE_IMAGE_MAX_DATA_URI_SIZE,
|
||||
)
|
||||
|
||||
_USER_PROFILE_IMAGE_RE = re.compile(r'^/api/v1/users/[^/?#]+/profile/image$')
|
||||
|
||||
|
|
@ -40,6 +43,7 @@ def validate_profile_image_url(url: str) -> str:
|
|||
- SVG data URIs (can contain embedded scripts)
|
||||
- Arbitrary relative paths (prevents authenticated GET triggers)
|
||||
- Scheme-relative URLs (``//host/path``)
|
||||
- data URIs larger than PROFILE_IMAGE_MAX_DATA_URI_SIZE bytes
|
||||
"""
|
||||
if not url:
|
||||
return url
|
||||
|
|
@ -70,6 +74,11 @@ def validate_profile_image_url(url: str) -> str:
|
|||
# The regex enforces the ;base64, boundary and is case-insensitive
|
||||
# per the data-URI / MIME-type specs.
|
||||
if _SAFE_DATA_URI_RE.match(url):
|
||||
if PROFILE_IMAGE_MAX_DATA_URI_SIZE and len(url) > PROFILE_IMAGE_MAX_DATA_URI_SIZE:
|
||||
raise ValueError(
|
||||
f'Invalid profile image URL: data URI exceeds the '
|
||||
f'{PROFILE_IMAGE_MAX_DATA_URI_SIZE}-byte limit.'
|
||||
)
|
||||
return url
|
||||
|
||||
raise ValueError(
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue