mirror of
https://github.com/open-webui/open-webui.git
synced 2026-07-10 12:35:55 +02:00
fix(models): gracefully handle legacy svg profile_image_url in ModelMeta validator (#25173)
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.
This commit is contained in:
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user