From 773787c74c2f57e5779095c9026e1223047198ee Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Wed, 11 Feb 2026 16:16:41 -0600 Subject: [PATCH] refac --- backend/open_webui/models/auths.py | 10 ++++++- backend/open_webui/models/users.py | 13 +++++++++- backend/open_webui/utils/validate.py | 39 ++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 backend/open_webui/utils/validate.py diff --git a/backend/open_webui/models/auths.py b/backend/open_webui/models/auths.py index 2d795cd2b..0b4639fc6 100644 --- a/backend/open_webui/models/auths.py +++ b/backend/open_webui/models/auths.py @@ -5,7 +5,8 @@ from typing import Optional from sqlalchemy.orm import Session from open_webui.internal.db import Base, JSONField, get_db, get_db_context from open_webui.models.users import User, UserModel, UserProfileImageResponse, Users -from pydantic import BaseModel +from open_webui.utils.validate import validate_profile_image_url +from pydantic import BaseModel, field_validator from sqlalchemy import Boolean, Column, String, Text log = logging.getLogger(__name__) @@ -74,6 +75,13 @@ class SignupForm(BaseModel): password: str profile_image_url: Optional[str] = "/user.png" + @field_validator("profile_image_url") + @classmethod + def check_profile_image_url(cls, v: Optional[str]) -> Optional[str]: + if v is not None: + return validate_profile_image_url(v) + return v + class AddUserForm(SignupForm): role: Optional[str] = "pending" diff --git a/backend/open_webui/models/users.py b/backend/open_webui/models/users.py index 4a526d03f..7e398c1e4 100644 --- a/backend/open_webui/models/users.py +++ b/backend/open_webui/models/users.py @@ -12,9 +12,10 @@ from open_webui.models.groups import Groups, GroupMember from open_webui.models.channels import ChannelMember from open_webui.utils.misc import throttle +from open_webui.utils.validate import validate_profile_image_url -from pydantic import BaseModel, ConfigDict +from pydantic import BaseModel, ConfigDict, field_validator from sqlalchemy import ( BigInteger, JSON, @@ -154,6 +155,11 @@ class UpdateProfileForm(BaseModel): gender: Optional[str] = None date_of_birth: Optional[datetime.date] = None + @field_validator("profile_image_url") + @classmethod + def check_profile_image_url(cls, v: str) -> str: + return validate_profile_image_url(v) + class UserGroupIdsModel(UserModel): group_ids: list[str] = [] @@ -237,6 +243,11 @@ class UserUpdateForm(BaseModel): profile_image_url: str password: Optional[str] = None + @field_validator("profile_image_url") + @classmethod + def check_profile_image_url(cls, v: str) -> str: + return validate_profile_image_url(v) + class UsersTable: def insert_new_user( diff --git a/backend/open_webui/utils/validate.py b/backend/open_webui/utils/validate.py new file mode 100644 index 000000000..9b50c3b79 --- /dev/null +++ b/backend/open_webui/utils/validate.py @@ -0,0 +1,39 @@ +"""Validation utilities for user-supplied input.""" + +# Known static asset paths used as default profile images +_ALLOWED_STATIC_PATHS = ( + "/user.png", + "/static/favicon.png", +) + + +def validate_profile_image_url(url: str) -> str: + """ + Pydantic-compatible validator for profile image URLs. + + Allowed formats: + - Empty string (falls back to default avatar) + - data:image/* URIs (base64-encoded uploads from the frontend) + - Known static asset paths (/user.png, /static/favicon.png) + + Returns the url unchanged if valid, raises ValueError otherwise. + """ + if not url: + return url + + _ALLOWED_DATA_PREFIXES = ( + "data:image/png", + "data:image/jpeg", + "data:image/gif", + "data:image/webp", + ) + if any(url.startswith(prefix) for prefix in _ALLOWED_DATA_PREFIXES): + return url + + if url in _ALLOWED_STATIC_PATHS: + return url + + raise ValueError( + "Invalid profile image URL: only data URIs and default avatars are allowed." + ) +