Commit Graph
591 Commits
Author SHA1 Message Date
Timothy Jaeryang Baek 223f484ded refac 2026-06-22 16:10:19 +02:00
Classic298 d501e3d6b5 Update milvus_multitenancy.py (#25857) 2026-06-17 03:07:27 +02:00
Classic298 c39be0e2d6 Update milvus.py (#25858) 2026-06-17 03:05:27 +02:00
Timothy Jaeryang Baek 5cdcdbaeec refac 2026-06-17 02:52:35 +02:00
087878ce84 Match WEB_FETCH_FILTER_LIST on hostnames with label boundaries, not URL suffix (CWE-693) (#25949)
is_string_allowed does endswith() matching and was called with the full URL
(retrieval/web/utils.py) against WEB_FETCH_FILTER_LIST, so a blocklisted host with any
path (https://blocked.example/x) ended with /x, not the host, and slipped through; the
allowlist direction false-rejected legitimate URLs and admitted attacker URLs ending in
an allowed string. The same endswith caused label confusion at the hostname call site
(retrieval/web/main.py): corp.com matched evilcorp.com, 10.0.0.1 matched 110.0.0.1.

Add is_host_allowed(host, ...) matching on DNS label boundaries (host == pattern or
host.endswith('.' + pattern)), called with the parsed hostname at both web-fetch call
sites. is_string_allowed is left unchanged for the unrelated function-name filters
(utils/middleware.py, utils/tools.py).

The separate is_global guard (validate_url / _ssrf_safe_new_conn, active when
ENABLE_RAG_LOCAL_WEB_FETCH is off) already blocks RFC1918/loopback/link-local, so this
restores the admin's intended blocking of specific public hosts.

Co-authored-by: addcontent <59762500+addcontent@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 23:53:08 +02:00
de8ea08f5c Route user-supplied image-URL fetches through an SSRF-safe session (DNS rebinding, CWE-918) (#25960)
The connection-layer DNS-rebinding guard (_SSRFSafeResolver / _SSRFSafeAdapter, PR #24759) was
mounted only on SafeWebBaseLoader. Two user-reachable image fetches validate the URL then fetch
it through the shared get_session() pool with the default resolver, so a TTL-0 rebinding answer
that passed validate_url reaches an internal address at connect:

- get_image_base64_from_url (utils/files.py): user image_url on every chat completion.
- load_url_image (routers/images.py, POST /api/v1/images/edit): user-supplied image field.

Add get_ssrf_safe_session() (a one-off aiohttp session mounting _SSRFSafeResolver) and use it
for both fetches, so the connect-time IP is re-validated and a rebound loopback / RFC1918 /
metadata address is rejected. The shared pool is left untouched for the admin-configured
image-generation callers, which legitimately reach internal hosts.

Co-authored-by: dhyabi2 <32069256+dhyabi2@users.noreply.github.com>
Co-authored-by: geo-chen <2404584+geo-chen@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 23:36:53 +02:00
Classic298andClaude Opus 4.8 eb38389636 fix: delete Qdrant points by ID so memory deletions don't orphan vectors (#25495)
The Qdrant backends implemented delete(ids=...) as a payload filter on
metadata.id, but points are stored with the item id as the Qdrant point id
(see _create_points), and not every point carries an id in its payload.
Memory points store only {created_at} in metadata (KB metadata embeddings
likewise), so deleting a single memory matched nothing and left an orphaned
vector that kept being injected into RAG context.

Delete by point id instead: PointIdsList for the standard backend, and a
tenant-scoped HasIdCondition for multitenancy (point ids are unique, so tenant
isolation is preserved). Filter-based deletion is unchanged.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 14:13:46 -07:00
Timothy Jaeryang Baek 6fce92aa12 chore: format 2026-06-01 13:56:55 -07:00
James Liounis 69c88e163d feat(retrieval): add Perplexity attribution header (#24833)
Signed-off-by: James Liounis <james.liounis@perplexity.ai>
2026-06-01 13:40:52 -07:00
Lukáš Kucharczykandrusselg 42c2393f8e Update Kagi API endpoint and request method (#25015)
Co-authored-by: russelg <russelg@users.noreply.github.com>
2026-06-01 12:48:44 -07:00
Timothy Jaeryang Baek c0f1aa2919 refac 2026-06-01 12:24:45 -07:00
rileydes-improving 567c4aabe9 feat: add support for Valkey vector database (#24769)
* feat: add support for Valkey vector database

Signed-off-by: Riley Des <riley.desserre@improving.com>

* feat: add CLIENT SETNAME to Valkey vector store connections

Set client_name on GlideClientConfiguration for both the main client
and batch client so connections are identifiable in CLIENT LIST,
monitoring dashboards, and CloudWatch metrics.

Signed-off-by: Riley Des <riley.desserre@improving.com>

---------

Signed-off-by: Riley Des <riley.desserre@improving.com>
2026-06-01 12:20:01 -07:00
Classic298 e623081b2b fix: handle list-shape data in Firecrawl /search response (#24712)
Firecrawl /search returns either `{"data": [...]}` (flat list — v1, and
what frost19k reported on #23966) or `{"data": {"web": [...]}}` (v2,
current production). The parser only handled the dict shape:

    data = response.get('data') or {}
    results = data.get('web') or []

On a list-shape response, `data.get('web')` raised AttributeError,
caught by the function's outer try/except, and `search_firecrawl`
silently returned []. Web search worked against v2 endpoints but is one
upstream-format-change away from failing closed again. Accept either.
2026-06-01 12:07:31 -07:00
Timothy Jaeryang Baek c93f071700 refac 2026-06-01 11:58:16 -07:00
Classic298andClaude 76947ff926 fix: reject collection names with unsafe characters in RAG ACL (#24982)
Open WebUI's collection ACL accepted any unknown name as a
legacy/ephemeral collection. In Milvus multi-tenancy mode that name
becomes the `resource_id` and is interpolated unescaped into a SQL-like
Milvus expression — `resource_id == '<name>'` — so a name like
  x' or resource_id != '' or resource_id == 'x
turns the filter into a tautology and returns every tenant's chunks
from the shared collection.

All collection names Open WebUI generates are UUIDs, SHA-256 hex
digests, or fixed-prefix variants of those — they all fit
[A-Za-z0-9_-]. Add a strict format check in
filter_accessible_collections (utils.py) that drops any name outside
that set before any ACL or vector-store lookup, applied even on the
admin bypass path. _validate_collection_access then surfaces the dropped
name as a 403.

As defense in depth, MilvusClient now validates resource_id at every
expression-construction site and escapes single quotes / backslashes in
any other string interpolated into a filter (delete ids, metadata
filter values). Non-string filter values are typed-checked instead of
str()-formatted.

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-01 11:48:43 -07:00
Timothy Jaeryang Baek 1bbb2b933d refac 2026-06-01 11:08:58 -07:00
Timothy Jaeryang Baek 6f0277db52 refac 2026-06-01 09:53:04 -07:00
Timothy Jaeryang Baek 55ca719bbf refac 2026-06-01 09:48:29 -07:00
Timothy Jaeryang Baek 9e3e24e304 refac 2026-06-01 09:42:54 -07:00
Timothy Jaeryang Baek ee47c9c833 refac 2026-05-31 18:34:37 -07:00
Timothy Jaeryang Baek d4030a8aa5 refac 2026-05-31 15:10:48 -07:00
Timothy Jaeryang Baek b94245d2ee refac 2026-05-21 16:44:36 +04:00
Classic298andClaude Opus 4.6 854440f703 fix: mitigate DNS rebinding in web loader fetch paths (#24759)
validate_url() resolves DNS to check IPs but discards the result; the
HTTP client resolves again independently.  Between those two lookups an
attacker can swap the DNS record from a public IP to an internal one
(DNS rebinding).

Push the IP-is-global check into the actual connection layer so the
validated resolution is the one used for the TCP connect:

- aiohttp (_fetch): _SSRFSafeResolver wraps DefaultResolver and rejects
  non-global IPs at resolve time (zero TOCTOU window).
- requests (_scrape): _SSRFSafeAdapter mounts custom urllib3 connection
  classes whose _new_conn resolves, validates, and connects to the
  validated IP in one shot (zero TOCTOU window).

Both paths respect ENABLE_RAG_LOCAL_WEB_FETCH (skip validation when on).

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-19 23:57:12 +04:00
Classic298 a803372805 fix: log expected fetch/transcript/tool-server failures as warnings (#24903) 2026-05-19 21:55:40 +04:00
Timothy Jaeryang Baek 56c0d00e13 enh: linkup 2026-05-19 21:54:38 +04:00
f02aeea0bb 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>
2026-05-19 21:27:43 +04:00
Classic298andClaude Opus 4.7 c48ac5163c fix: pass subscription_key and endpoint in bing.py CLI search_bing() call (#24768)
The __main__ block called search_bing() with 4 positional arguments, but
the function requires 5 (subscription_key, endpoint, locale, query,
count). Running `python -m open_webui.retrieval.web.bing` raised a
TypeError and, before failing, silently misrouted every argument. Read
the key/endpoint from environment variables, matching config.py defaults.

Closes #24765

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 21:26:10 +04:00
Timothy Jaeryang Baek 1e36a20600 refac 2026-05-19 20:55:04 +04:00
Timothy Jaeryang Baek f607337582 refac 2026-05-14 02:56:44 +09:00
Timothy Jaeryang Baek 6d0295588e refac: modernize type annotations (PEP 604 / PEP 585) 2026-05-12 17:10:15 +09:00
Timothy Jaeryang Baek a59c967d7e refac: modernize imports, standardize type hints and docstrings 2026-05-12 06:30:38 +09:00
Timothy Jaeryang Baek 1413ce4a52 refac 2026-05-12 05:30:25 +09:00
Timothy Jaeryang Baek 90a02e623f refac 2026-05-12 03:04:35 +09:00
e7ba8978c6 fix: reject parser-confusing chars in validate_url to close SSRF bypass (#24534)
urllib.parse.urlparse and requests/aiohttp disagree on how to split URLs
containing backslash, tab, CR, or LF in or around the netloc. urlparse
treats backslash as part of userinfo and uses what follows '@' as the
host; requests treats backslash as the start of the path and connects
to whatever precedes it. The same URL therefore passes the private-IP
filter (urlparse sees a public host) but reaches an internal target
(requests connects to e.g. 127.0.0.1). End result is an SSRF that the
existing IP block list cannot catch because it's evaluating the wrong
host.

PoC: http://127.0.0.1:6666\@1.1.1.1 — urlparse hostname is 1.1.1.1
(global, passes), requests reaches 127.0.0.1 (loopback).

Reject up front any URL containing one of the four documented parser-
confusing characters before either parser gets a chance to interpret
it. None of these characters is valid in an unencoded URL (\ should
always be %5C, whitespace should be %09 / %0A / %0D), so this is a
pure defensive rejection with no legitimate-input false positives.

Reported by Fushuling and RacerZ-fighting in GHSA-8w7q-q5jp-jvgx.

Co-authored-by: Fushuling <Fushuling@users.noreply.github.com>
Co-authored-by: RacerZ-fighting <RacerZ-fighting@users.noreply.github.com>
2026-05-11 00:57:48 +09:00
Timothy Jaeryang Baek df42d96c95 refac 2026-05-09 21:05:49 +09:00
Classic298 8854541508 fix: prevent redirect-based SSRF in web-fetch and image-load call sites (#24491)
validate_url() in retrieval/web/utils.py only validates the initial URL.
The HTTP clients used downstream (sync requests, sync requests via the
parent WebBaseLoader._scrape, aiohttp via load_url_image) followed 3xx
redirects by default and did not re-validate the redirect target against
the private-IP / metadata-IP block list. An authenticated user could
submit a public URL that 302-redirected to an internal address (RFC1918,
127.0.0.1, 169.254.169.254, etc.) and the redirected response was returned
to them, enabling SSRF reads of internal services and cloud metadata.

Three call sites needed allow_redirects=False to match the policy already
enforced on the async _fetch() path:

- SafeWebBaseLoader: override requests_kwargs in __init__ so that the
  inherited synchronous _scrape() path passes allow_redirects=False to
  self.session.get() (the parent WebBaseLoader uses requests' default
  allow_redirects=True).
- get_content_from_url (retrieval/utils.py): pass allow_redirects=False
  on the streamed requests.get(...) call.
- load_url_image (routers/images.py, image-edits endpoint): pass
  allow_redirects=False on the aiohttp session.get(...) call.

Reports consolidated under GHSA-rh5x-h6pp-cjj6:
- GHSA-rh5x-h6pp-cjj6 (tenbbughunters / Tenable) - sync _scrape
- GHSA-5vxg-6gmv-m2qr (YLChen-007) - load_url_image
- GHSA-hf76-c83f-63w2 (tempcollab) - aiohttp _fetch (already fixed)
- GHSA-h55f-h5fh-mvm4 (sneaXOR) - get_content_from_url
2026-05-09 21:01:45 +09:00
Timothy Jaeryang Baek 7bcc0e2e5c chore: format 2026-05-09 15:25:27 +09:00
Timothy Jaeryang Baek bb0e6cb108 refac 2026-05-09 06:41:42 +09:00
Timothy Jaeryang Baek 6700f7bb72 feat: brave search llm context 2026-05-09 06:34:25 +09:00
Timothy Jaeryang Baek 9386fc83a3 refac 2026-05-09 04:49:18 +09:00
Timothy Jaeryang Baek 6dff85b9d2 refac 2026-05-09 02:08:08 +09:00
Timothy Jaeryang Baek 9adc0c442a refac 2026-05-09 02:02:02 +09:00
Timothy Jaeryang Baek 8ff7ff459b chore: format 2026-04-24 18:48:21 +09:00
RomualdYTandTim Baek e0d6074cd2 refactor(firecrawl): use v2 API directly (#23934)
Co-authored-by: Tim Baek <tim@openwebui.com>
2026-04-24 18:32:08 +09:00
Timothy Jaeryang Baek 1cea8ec7d4 refac 2026-04-24 17:59:45 +09:00
Timothy Jaeryang Baek 60f67c7c17 refac 2026-04-24 17:07:23 +09:00
58bc254809 feat: add PaddleOCR-vl loader support and implement retrieval router infrastructure (#23945)
Co-authored-by: Tim Baek <tim@openwebui.com>
Co-authored-by: joaoback <156559121+joaoback@users.noreply.github.com>
2026-04-24 15:19:37 +09:00
Timothy Jaeryang Baek 0e311a95a7 refac 2026-04-24 15:16:37 +09:00
Timothy Jaeryang Baek 65f55847a1 refac 2026-04-21 16:04:48 +09:00
Timothy Jaeryang Baek 6cc799b1bb chore: format 2026-04-21 15:52:00 +09:00