From 920b655f4689e2118de928fbc936f6ebd4fed396 Mon Sep 17 00:00:00 2001 From: Classic298 <27028174+Classic298@users.noreply.github.com> Date: Tue, 16 Jun 2026 22:45:27 +0200 Subject: [PATCH] Gate scheduled automations and fail closed on per-model access for non-user roles (#26047) Two lifecycle/authorization gaps let a deactivated (pending) account keep acting: 1. The background automation scheduler (execute_automation) rehydrated the owner by ID and dispatched the chat pipeline without re-checking the owner. A user later set to pending, or one whose features.automations permission was revoked, kept running scheduled automations on the operator's provider credentials, even though the HTTP create/update/run routes already gate on get_verified_user + features.automations. Re-gate the rehydrated owner before dispatch: require role user/admin and, for non-admins, the features.automations permission; otherwise record an error and skip the run. 2. check_model_access enforced per-model ACLs only for exactly role == 'user', so any other non-admin role (a pending principal) fell through and was granted access. Enforce for every non-admin role (admins still bypass), so the check fails closed (CWE-862, CWE-863). Co-authored-by: rexpository <30176934+rexpository@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 (1M context) --- backend/open_webui/utils/access_control/__init__.py | 3 ++- backend/open_webui/utils/automations.py | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/backend/open_webui/utils/access_control/__init__.py b/backend/open_webui/utils/access_control/__init__.py index 1cc2f086f5..d513a5c671 100644 --- a/backend/open_webui/utils/access_control/__init__.py +++ b/backend/open_webui/utils/access_control/__init__.py @@ -321,7 +321,8 @@ async def check_model_access( return if model_info: - if user.role == 'user': + # Enforce for every non-admin role (including pending); never fail open. + if user.role != 'admin': from open_webui.models.access_grants import AccessGrants user_group_ids = {group.id for group in await Groups.get_groups_by_member_id(user.id)} diff --git a/backend/open_webui/utils/automations.py b/backend/open_webui/utils/automations.py index 10f90da795..7db4d6e3ca 100644 --- a/backend/open_webui/utils/automations.py +++ b/backend/open_webui/utils/automations.py @@ -359,6 +359,16 @@ async def execute_automation(app, automation: AutomationModel) -> None: await _record_run(automation.id, 'error', error='User not found') return + # Re-gate the rehydrated owner: a demoted/deactivated or de-permissioned owner must not run. + from open_webui.utils.access_control import has_permission + + if user.role not in ('user', 'admin') or ( + user.role != 'admin' + and not await has_permission(user.id, 'features.automations', app.state.config.USER_PERMISSIONS) + ): + await _record_run(automation.id, 'error', error='Owner no longer permitted to run automations') + return + prompt = await prompt_template(automation.data['prompt'], user) model_id = automation.data['model_id'] terminal_config = automation.data.get('terminal')