mirror of
https://github.com/open-webui/open-webui.git
synced 2026-08-02 21:59:02 -05:00
fix: validate Playwright navigations and gate redirects in web loader (#24756)
SafePlaywrightURLLoader validated only the initially submitted URL and then let the browser follow HTTP redirects and client-side navigations without re-checking them, so a public URL could redirect into the internal network (cloud metadata, RFC1918, loopback). Intercept document-type requests, re-run validate_url on each, and apply the same redirect policy as the requests loader (blocked unless AIOHTTP_CLIENT_ALLOW_REDIRECTS). Sub-resource requests pass through unchanged so page rendering performance is unaffected. Co-authored-by: POV9en <POV9en@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
POV9en
Claude Opus 4.7
parent
5401e8560b
commit
f02aeea0bb
@@ -421,6 +421,62 @@ class SafePlaywrightURLLoader(PlaywrightURLLoader, RateLimitMixin, URLProcessing
|
||||
self.trust_env = trust_env
|
||||
self.playwright_timeout = playwright_timeout
|
||||
|
||||
def _intercept_navigation_sync(self, route, request=None):
|
||||
req = request or route.request
|
||||
|
||||
if req.resource_type != 'document':
|
||||
route.continue_()
|
||||
return
|
||||
|
||||
try:
|
||||
validate_url(req.url)
|
||||
except Exception:
|
||||
route.abort()
|
||||
return
|
||||
|
||||
if AIOHTTP_CLIENT_ALLOW_REDIRECTS:
|
||||
resp = route.fetch()
|
||||
else:
|
||||
try:
|
||||
resp = route.fetch(max_redirects=0)
|
||||
except TypeError:
|
||||
route.abort()
|
||||
return
|
||||
|
||||
if 300 <= resp.status < 400:
|
||||
route.abort()
|
||||
return
|
||||
|
||||
route.fulfill(response=resp)
|
||||
|
||||
async def _intercept_navigation(self, route, request=None):
|
||||
req = request or route.request
|
||||
|
||||
if req.resource_type != 'document':
|
||||
await route.continue_()
|
||||
return
|
||||
|
||||
try:
|
||||
await run_in_threadpool(validate_url, req.url)
|
||||
except Exception:
|
||||
await route.abort()
|
||||
return
|
||||
|
||||
if AIOHTTP_CLIENT_ALLOW_REDIRECTS:
|
||||
resp = await route.fetch()
|
||||
else:
|
||||
try:
|
||||
resp = await route.fetch(max_redirects=0)
|
||||
except TypeError:
|
||||
await route.abort()
|
||||
return
|
||||
|
||||
if 300 <= resp.status < 400:
|
||||
await route.abort()
|
||||
return
|
||||
|
||||
await route.fulfill(response=resp)
|
||||
|
||||
def lazy_load(self) -> Iterator[Document]:
|
||||
"""Safely load URLs synchronously with support for remote browser."""
|
||||
from playwright.sync_api import sync_playwright
|
||||
@@ -436,6 +492,7 @@ class SafePlaywrightURLLoader(PlaywrightURLLoader, RateLimitMixin, URLProcessing
|
||||
try:
|
||||
self._safe_process_url_sync(url)
|
||||
page = browser.new_page()
|
||||
page.route('**/*', self._intercept_navigation_sync)
|
||||
response = page.goto(url, timeout=self.playwright_timeout)
|
||||
if response is None:
|
||||
raise ValueError(f'page.goto() returned None for url {url}')
|
||||
@@ -465,6 +522,7 @@ class SafePlaywrightURLLoader(PlaywrightURLLoader, RateLimitMixin, URLProcessing
|
||||
try:
|
||||
await self._safe_process_url(url)
|
||||
page = await browser.new_page()
|
||||
await page.route('**/*', self._intercept_navigation)
|
||||
response = await page.goto(url, timeout=self.playwright_timeout)
|
||||
if response is None:
|
||||
raise ValueError(f'page.goto() returned None for url {url}')
|
||||
|
||||
Reference in New Issue
Block a user