* 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>
* fix: replace brittle profile_image_url allowlist with safe-scheme validation
The previous validation used a hardcoded allowlist of specific static
paths and a single Gravatar prefix. This rejected OWUI's own internal
API paths (e.g. /api/v1/users/{id}/profile/image) and external OAuth
avatar URLs, making it impossible to save user profiles from the admin
panel.
Replace with scheme-based validation that allows relative paths,
HTTP(S) URLs, and data:image URIs while blocking dangerous schemes
like javascript:, file:, and ftp:.
Fixes open-webui#23387
* fix: harden profile image URL validation per review feedback
- Restrict data URIs to safe raster formats (png/jpeg/gif/webp);
SVG is excluded because it can carry embedded scripts.
- Block scheme-relative URLs (//host/path) which browsers resolve
against the current protocol, bypassing the relative-path check.
* fix: use structural validation instead of prefix checks
- Use urlparse for HTTP(S) URLs: gives case-insensitive scheme
matching and rejects bare schemes with no host (e.g. https://).
- Use a compiled regex for data URIs: enforces the ;base64, boundary,
restricts to safe raster formats, and is case-insensitive per spec.
- Removes the startswith-based prefix tuple in favour of proper
URL and data URI parsing.
* fix: validate hostname not netloc, fix misleading comment
- Use parsed.hostname instead of parsed.netloc so URLs like
http://:80/path (non-empty netloc but no actual host) are rejected.
- Update data URI comment to accurately state we validate MIME type
and structure, not base64 payload integrity.
* fix: constrain relative paths to known-safe prefixes
Accepting any relative path starting with / allowed a user to set
their profile_image_url to an arbitrary internal GET endpoint. When
another user (e.g. an admin) views that profile, the browser fires
the GET with the viewer's session cookies — an authenticated GET
trigger surface.
Constrain to known-safe prefixes (/api/v1/users/, /static/) and
exact matches (/user.png, /favicon.png) which are the only relative
paths OWUI itself generates.
* fix: use exact matches and anchored regex, eliminate all prefix wildcarding
Replace all startswith-based path checks with:
- frozenset exact matches for static assets (/user.png, /favicon.png,
/static/favicon.png)
- Anchored regex for the OWUI profile image API route that accepts
only /api/v1/users/{id}/profile/image (no trailing components,
no path traversal across segments)
This eliminates every prefix-based attack surface:
- /api/v1/users/{id}/anything-else is rejected
- /static/../../etc/passwd is rejected
- /api/v1/users/../../admin/config is rejected
- Arbitrary internal GET triggers are no longer possible
* fix: exclude query/fragment delimiters from user-ID regex segment
Change [^/]+ to [^/?#]+ so that inputs like
/api/v1/users/alice?x=1/profile/image are rejected — the browser
would interpret ? as the query string start, making the actual
request target /api/v1/users/alice instead of the intended route.