diff --git a/backend/open_webui/routers/models.py b/backend/open_webui/routers/models.py index f3737cb66d..5200a99a54 100644 --- a/backend/open_webui/routers/models.py +++ b/backend/open_webui/routers/models.py @@ -36,6 +36,7 @@ from open_webui.models.models import ( Models, ) from open_webui.utils.access_control import filter_allowed_access_grants, has_permission +from open_webui.utils.access_control.files import has_access_to_file from open_webui.utils.auth import get_admin_user, get_verified_user from pydantic import BaseModel from sqlalchemy.ext.asyncio import AsyncSession @@ -77,6 +78,32 @@ def is_valid_model_id(model_id: str) -> bool: return model_id and len(model_id) <= 256 +async def _verify_knowledge_file_access( + knowledge_items: list | None, + user, + db: AsyncSession, +) -> None: + """Raise 403 if any knowledge item references a file the caller cannot read.""" + if not knowledge_items or user.role == 'admin': + return + for item in knowledge_items: + if not isinstance(item, dict) or item.get('type') != 'file': + continue + file_id = item.get('id') + if not file_id: + continue + if not await has_access_to_file(file_id, 'read', user, db=db): + log.warning( + 'knowledge file access denied: user %s cannot read file %s', + user.id, + file_id, + ) + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail=ERROR_MESSAGES.ACCESS_PROHIBITED, + ) + + ########################### # GetModels # Let each model here be judged by what it does and not @@ -219,6 +246,11 @@ async def create_new_model( ) else: + await _verify_knowledge_file_access( + getattr(form_data.meta, 'knowledge', None) if form_data.meta else None, + user, db, + ) + form_data.access_grants = await filter_allowed_access_grants( request.app.state.config.USER_PERMISSIONS, user.id, @@ -325,6 +357,20 @@ async def import_models( model_id = model_data.get('id') if model_id and is_valid_model_id(model_id): + # Defense-in-depth: skip models referencing inaccessible files + try: + await _verify_knowledge_file_access( + (model_data.get('meta') or {}).get('knowledge'), + user, db, + ) + except HTTPException: + log.warning( + 'import_models: user %s skipped model %s ' + '(knowledge file access denied)', + user.id, model_id, + ) + continue + existing_model = existing_models.get(model_id) if existing_model: # Enforce ownership/write-access before allowing overwrite @@ -614,6 +660,11 @@ async def update_model_by_id( detail=ERROR_MESSAGES.ACCESS_PROHIBITED, ) + await _verify_knowledge_file_access( + getattr(form_data.meta, 'knowledge', None) if form_data.meta else None, + user, db, + ) + form_data.access_grants = await filter_allowed_access_grants( request.app.state.config.USER_PERMISSIONS, user.id,