diff --git a/backend/open_webui/routers/skills.py b/backend/open_webui/routers/skills.py index 490d1706d5..ede5afd814 100644 --- a/backend/open_webui/routers/skills.py +++ b/backend/open_webui/routers/skills.py @@ -176,6 +176,19 @@ async def create_new_skill( detail=ERROR_MESSAGES.ID_TAKEN, ) + # Strip public/user grants the requesting user is not permitted to assign + # (matches the channel/notes/calendar pattern). Without this, a user with + # workspace.skills permission could attach principal_id='*' read/write + # grants in the create payload, bypassing the sharing.public_skills gate + # that the dedicated /access/update endpoint already enforces. + form_data.access_grants = await filter_allowed_access_grants( + request.app.state.config.USER_PERMISSIONS, + user.id, + user.role, + form_data.access_grants, + 'sharing.public_skills', + ) + try: skill = await Skills.insert_new_skill(user.id, form_data, db=db) if skill: @@ -276,6 +289,19 @@ async def update_skill_by_id( detail=ERROR_MESSAGES.UNAUTHORIZED, ) + # Strip public/user grants the requesting user is not permitted to assign + # (matches the channel/notes/calendar pattern). The access check above only + # restricts WHO can write to the skill; this filter restricts WHICH grants + # they may set, so a non-admin owner cannot make their own skill publicly + # readable/writable without sharing.public_skills permission. + form_data.access_grants = await filter_allowed_access_grants( + request.app.state.config.USER_PERMISSIONS, + user.id, + user.role, + form_data.access_grants, + 'sharing.public_skills', + ) + try: updated = { **form_data.model_dump(exclude={'id'}),