From 076a84e3f0720beb4ab0b4ad7bab5bc072ac8f17 Mon Sep 17 00:00:00 2001 From: Classic298 <27028174+Classic298@users.noreply.github.com> Date: Sun, 26 Jul 2026 23:55:56 +0200 Subject: [PATCH] fix: enforce automation limits in the builtin automation tools (#27523) The `create_automation` and `update_automation` builtin tools wrote straight to `Automations.insert` / `Automations.update_by_id`, skipping the limit checks that `/api/v1/automations/create` and `/api/v1/automations/{id}/update` run through `check_automation_limits`. A non-admin user could therefore ask the model to create automations indefinitely, ignoring `AUTOMATION_MAX_COUNT`, and could schedule them below `AUTOMATION_MIN_INTERVAL`, on both create and update. Both tools now call the same `check_automation_limits` helper the routers use, so the limits and the admin bypass cannot drift between the chat path and the HTTP path. A rejection is returned to the model as a plain error message instead of raising. `update_automation` also gained the missing user lookup guard, since the helper needs the user's role. The `automations.enable` toggle and the `features.automations` user permission were already enforced when the tool set is assembled, so they are unaffected. Fixes #27121 --- backend/open_webui/tools/builtin.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/backend/open_webui/tools/builtin.py b/backend/open_webui/tools/builtin.py index 1d1bf8572d..cd136d947f 100644 --- a/backend/open_webui/tools/builtin.py +++ b/backend/open_webui/tools/builtin.py @@ -14,7 +14,7 @@ import logging import time from typing import Literal, Optional -from fastapi import Request +from fastapi import HTTPException, Request from open_webui.models.channels import Channel, ChannelMember, Channels from open_webui.models.chats import Chats @@ -3292,6 +3292,7 @@ async def create_automation( try: from open_webui.models.automations import AutomationData, AutomationForm, Automations from open_webui.models.users import Users + from open_webui.routers.automations import check_automation_limits from open_webui.utils.automations import next_n_runs_ns, next_run_ns, validate_rrule user_id = __user__.get('id') @@ -3313,6 +3314,11 @@ async def create_automation( except ValueError as e: return json.dumps({'error': f'Invalid schedule: {e}'}) + try: + await check_automation_limits(__request__, user, rrule, None, is_create=True) + except HTTPException as e: + return json.dumps({'error': e.detail}) + tz = user.timezone form = AutomationForm( name=name, @@ -3370,10 +3376,13 @@ async def update_automation( try: from open_webui.models.automations import AutomationData, AutomationForm, Automations from open_webui.models.users import Users + from open_webui.routers.automations import check_automation_limits from open_webui.utils.automations import next_n_runs_ns, next_run_ns, validate_rrule user_id = __user__.get('id') user = await Users.get_user_by_id(user_id) + if not user: + return json.dumps({'error': 'User not found'}) automation = await Automations.get_by_id(automation_id) if not automation: @@ -3390,11 +3399,16 @@ async def update_automation( # Validate RRULE if changed if rrule is not None: try: - validate_rrule(new_rrule, tz=user.timezone if user else None) + validate_rrule(new_rrule, tz=user.timezone) except ValueError as e: return json.dumps({'error': f'Invalid schedule: {e}'}) - tz = user.timezone if user else None + try: + await check_automation_limits(__request__, user, new_rrule, None) + except HTTPException as e: + return json.dumps({'error': e.detail}) + + tz = user.timezone form = AutomationForm( name=new_name, data=AutomationData(