diff --git a/backend/open_webui/utils/webhook.py b/backend/open_webui/utils/webhook.py index 616d2611aa..8a65f348a8 100644 --- a/backend/open_webui/utils/webhook.py +++ b/backend/open_webui/utils/webhook.py @@ -3,7 +3,13 @@ import logging import aiohttp from open_webui.config import WEBUI_FAVICON_URL -from open_webui.env import AIOHTTP_CLIENT_SESSION_SSL, AIOHTTP_CLIENT_TIMEOUT, VERSION +from open_webui.env import ( + AIOHTTP_CLIENT_ALLOW_REDIRECTS, + AIOHTTP_CLIENT_SESSION_SSL, + AIOHTTP_CLIENT_TIMEOUT, + VERSION, +) +from open_webui.retrieval.web.utils import validate_url log = logging.getLogger(__name__) @@ -13,6 +19,10 @@ log = logging.getLogger(__name__) async def post_webhook(name: str, url: str, message: str, event_data: dict) -> bool: try: log.debug(f'post_webhook: {url}, {message}, {event_data}') + # Block private-IP / loopback / cloud-metadata targets — the URL is + # caller-controlled (user notification settings under + # ENABLE_USER_WEBHOOKS, automation notification triggers). + validate_url(url) payload = {} # Slack and Google Chat Webhooks @@ -53,7 +63,12 @@ async def post_webhook(name: str, url: str, message: str, event_data: dict) -> b async with aiohttp.ClientSession( trust_env=True, timeout=aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT) ) as session: - async with session.post(url, json=payload, ssl=AIOHTTP_CLIENT_SESSION_SSL) as r: + async with session.post( + url, + json=payload, + ssl=AIOHTTP_CLIENT_SESSION_SSL, + allow_redirects=AIOHTTP_CLIENT_ALLOW_REDIRECTS, + ) as r: r_text = await r.text() r.raise_for_status() log.debug(f'r.text: {r_text}')