From 34b8844ccf2ba8bf6aba9d10cfdef802677bb51c Mon Sep 17 00:00:00 2001 From: G30 <50341825+silentoplayz@users.noreply.github.com> Date: Sun, 31 May 2026 17:55:07 -0400 Subject: [PATCH] fix(models): gracefully handle legacy svg profile_image_url in ModelMeta validator (#25173) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The SVG-XSS hardening introduced in f5f4b5895 correctly rejects data:image/svg+xml URIs on new input, but also caused a pydantic_core.ValidationError when reading pre-existing models from the database that had SVG data URIs stored as their profile images. This ValidationError propagated unhandled through _to_model_model and get_all_models, crashing the entire /api/models endpoint with HTTP 500 and leaving users with no models available in the UI. Fix: - Wrap validate_profile_image_url() in a try/except ValueError inside ModelMeta.check_profile_image_url. Legacy entries are cleared to None with a warning log instead of raising — the /model/profile/image API endpoint already falls back to /static/favicon.png when the value is empty. - Default ModelMeta.profile_image_url to None instead of hardcoding /static/favicon.png, since the serving endpoint handles the fallback. - Add a per-model try/except in ModelsTable.get_all_models so that any future unexpected validation failure on a single record skips that model with an error log rather than aborting the entire list. --- backend/open_webui/models/models.py | 31 +++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/backend/open_webui/models/models.py b/backend/open_webui/models/models.py index 671fa6c3c9..fa8f05a377 100755 --- a/backend/open_webui/models/models.py +++ b/backend/open_webui/models/models.py @@ -17,6 +17,10 @@ from sqlalchemy.ext.asyncio import AsyncSession log = logging.getLogger(__name__) +# Track invalid profile_image_url values we've already warned about so we +# don't flood the logs on every DB read (the validator fires per-row). +_warned_profile_urls: set[str] = set() + # --- Models DB Schema --- @@ -30,7 +34,7 @@ class ModelParams(BaseModel): class ModelMeta(BaseModel): """Metadata for a workspace model entry (profile, description, tags, capabilities).""" - profile_image_url: str | None = '/static/favicon.png' + profile_image_url: str | None = None description: str | None = Field(default=None, description='User-facing description of the model.') capabilities: dict | None = None @@ -41,7 +45,17 @@ class ModelMeta(BaseModel): def check_profile_image_url(cls, v: str | None) -> str | None: if v is None: return v - return validate_profile_image_url(v) + try: + return validate_profile_image_url(v) + except ValueError: + if v not in _warned_profile_urls: + _warned_profile_urls.add(v) + log.warning( + 'Clearing invalid profile_image_url stored in DB ' + '(likely a legacy SVG data-URI): %.80s…', + v, + ) + return None @model_validator(mode='before') @classmethod @@ -178,10 +192,15 @@ class ModelsTable: all_models = result.scalars().all() model_ids = [model.id for model in all_models] grants_map = await AccessGrants.get_grants_by_resources('model', model_ids, db=db) - return [ - await self._to_model_model(model, access_grants=grants_map.get(model.id, []), db=db) - for model in all_models - ] + models: list[ModelModel] = [] + for model in all_models: + try: + models.append( + await self._to_model_model(model, access_grants=grants_map.get(model.id, []), db=db) + ) + except Exception as exc: + log.error('Skipping model %r during get_all_models due to error: %s', model.id, exc) + return models async def get_models(self, db: AsyncSession | None = None) -> list[ModelUserResponse]: async with get_async_db_context(db) as db: