Commit Graph
16854 Commits
Author SHA1 Message Date
G30andGitHub b2809e6293 perf(ui): hoist marked.use() to module scope to prevent extension stacking (#25837) 2026-06-17 03:09:09 +02:00
G30andGitHub beb9bf60e4 feat(memory): default user memory toggle to admin's global ENABLE_MEMORIES config (#25909) 2026-06-17 03:08:43 +02:00
G30andGitHub 2f9b28a57d fix(ui): run initChatList API calls concurrently in Promise.all (#25838) 2026-06-17 03:08:02 +02:00
Classic298andGitHub d501e3d6b5 Update milvus_multitenancy.py (#25857) 2026-06-17 03:07:27 +02:00
G30andGitHub 819ad1d904 fix(chat): convert recursive findClosingDelimiter to iterative loop to prevent stack overflow (#25845) 2026-06-17 03:07:12 +02:00
G30andGitHub 9fe3a00dba perf(chat): cache KaTeX rendered HTML in reactive statement to avoid redundant renders (#25847) 2026-06-17 03:06:56 +02:00
G30andGitHub 4584adf900 feat(ui): add 'Unshare All Shared Chats' button to Shared Chats modal (#25848) 2026-06-17 03:06:41 +02:00
G30andGitHub dfdb76cc46 fix(ui): prevent Code Editor drawer from collapsing when content is empty (#25855) 2026-06-17 03:06:15 +02:00
17df026492 Confer object-derived file write only for files the object owner owns (#26032)
has_access_to_file() derives file access from the objects a file is attached to
(knowledge bases, workspace models). Those branches returned True for any access_type
whenever the user held that permission on the object, write/delete included. Since a
user can create their own KB or model and attach any file they can merely READ (KB
attach and the model meta.knowledge validator both gate on read access only), a user
with read access to a victim file could launder it into write/delete: attach it to an
object they own, then rename, overwrite or delete it via the write-gated file routes
(POST /files/{id}/rename, /data/content/update, DELETE /files/{id}). This is the
residual of GHSA-vjqm-6gcc-62cr (CVE-2026-54012) left open by the read-only attach
validator (CWE-863).

An object now confers write/delete on a file only when the object's owner owns that
file, so delegation originates from the file's own owner. Read is unchanged (RAG and
shared-object reads still work), and legitimate delegation is preserved: a write grant
on an object whose owner owns the attached file still confers write. Applied to all
three object branches: knowledge base, file home collection, and workspace model.

Co-authored-by: rexpository <30176934+rexpository@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 03:05:57 +02:00
e038bab66d Authorize POST /api/v1/images/edit (enforce ENABLE_IMAGE_EDIT + image-gen permission) (#26009)
The direct image-edit route was the only image-edit surface with no authorization: it ran
on get_verified_user alone, while POST /generations enforces ENABLE_IMAGE_GENERATION +
features.image_generation and the built-in edit_image tool enforces ENABLE_IMAGE_EDIT +
features.image_generation. A verified non-admin user could therefore reach the configured
image-edit provider (spending IMAGES_EDIT_OPENAI_API_KEY) even when the administrator had
globally disabled image editing (ENABLE_IMAGE_EDIT=False) or denied the user image
generation.

Split the route from the shared impl (mirroring generate_images/image_generations): the new
/edit wrapper enforces ENABLE_IMAGE_EDIT and the per-user image-generation permission, then
delegates to image_edits(). The internal callers (the edit_image tool and the chat
middleware) already gate themselves and call image_edits() directly, so they are unaffected.

Co-authored-by: jagstack <52110932+jagstack@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 03:05:41 +02:00
Classic298andGitHub c39be0e2d6 Update milvus.py (#25858) 2026-06-17 03:05:27 +02:00
6cd0ba0b6b implement date picker and additional columns for analytics (#25922)
Co-authored-by: Tim Baek <tim@openwebui.com>
2026-06-17 03:03:18 +02:00
Timothy Jaeryang Baek e473ab1231 refac 2026-06-17 03:01:11 +02:00
Timothy Jaeryang Baek 4dbb2f94a6 refac 2026-06-17 02:58:11 +02:00
G30andGitHub dee07d8a30 feat(ui): show total archived chat count in ChatsModal title (#25872) 2026-06-17 02:57:27 +02:00
Algorithm5838andGitHub e031fadc35 perf: skip highlight.js auto-detection in read-only code blocks (#25824) 2026-06-17 02:57:02 +02:00
G30andGitHub b4aef82401 feat(ui): show total file count in FilesModal title (#25873) 2026-06-17 02:55:13 +02:00
Timothy Jaeryang Baek 5cdcdbaeec refac 2026-06-17 02:52:35 +02:00
Timothy Jaeryang Baek e8d55c0a8b refac 2026-06-17 02:36:20 +02:00
G30andGitHub 57a5e43696 feat(permissions): add per-group features.user_webhooks permission (#25923) 2026-06-17 00:47:44 +02:00
Timothy Jaeryang Baek 7b29834d42 refac 2026-06-17 00:43:59 +02:00
G30andGitHub 2b9b956dad fix(notes): sync pinned note state in sidebar and editor menu (#25918) 2026-06-17 00:42:20 +02:00
G30andGitHub 25090dbf17 fix(chat): prevent false-positive unread indicators from title generation, pin/archive/folder actions, and new chat creation (#25912) 2026-06-17 00:41:44 +02:00
G30andGitHub bb2663aad1 fix(ui): gate all chat export formats by chat.export permission (#25914) 2026-06-17 00:41:18 +02:00
G30andGitHub 3571db34e2 fix(ui): enforce allow_users permission in share chat modal (#25915) 2026-06-17 00:40:57 +02:00
Timothy Jaeryang Baek 7d1f941580 refac 2026-06-17 00:40:04 +02:00
Timothy Jaeryang Baek ed4cb358a0 refac 2026-06-17 00:39:11 +02:00
Timothy Jaeryang Baek caedcbae49 refac 2026-06-17 00:36:34 +02:00
Timothy Jaeryang Baek 9ccda6715c refac 2026-06-17 00:35:08 +02:00
Timothy Jaeryang Baek edf3ae9209 refac 2026-06-17 00:33:30 +02:00
G30andGitHub 0726db7217 fix(analytics): remove duplicate onMount causing double API requests on dashboard load (#25935) 2026-06-17 00:29:07 +02:00
G30andGitHub 9c6c375dfe fix(images): add null guard for config in onMount to prevent crash on API failure (#25933) 2026-06-17 00:28:42 +02:00
G30andGitHub 3e3c5b6d78 fix(permissions): add admin bypass to memories permission checks (#25924) 2026-06-17 00:28:17 +02:00
G30andGitHub 2bc91e8f52 fix(ui): add missing skills permission to workspace menu visibility check (#25925) 2026-06-17 00:27:25 +02:00
G30andGitHub e5ed45fb20 fix(ui): handle null chat in folder drop handler for cross-account drag (#25928) 2026-06-17 00:26:35 +02:00
Timothy Jaeryang Baek 232421f40b refac 2026-06-17 00:25:35 +02:00
G30andGitHub 6b2d962cd6 feat(permissions): add reset to defaults button in permissions modals (#25931)
Adds a 'Reset to Defaults' button to both the Edit Default Permissions
modal and the Edit User Group permissions modal.

- Default permissions modal: resets to stock/env-var configuration
- User group modal: resets to current global default permissions

Backend: new GET /api/v1/users/default/permissions/defaults endpoint
that returns DEFAULT_USER_PERMISSIONS (env-var-based initial defaults).
2026-06-17 00:25:20 +02:00
Timothy Jaeryang Baek 7ee75a0c04 refac 2026-06-17 00:17:48 +02:00
Timothy Jaeryang Baek fc9c2ea191 refac 2026-06-17 00:17:14 +02:00
G30andGitHub 6f2e97aa58 fix(evaluations): prevent duplicate leaderboard API requests on navigation (#25934) 2026-06-17 00:16:27 +02:00
5f3a628a8d Encode terminal ws session_id to block upstream user_id query injection (#26042)
ws_terminal() interpolated the path parameter session_id directly into the upstream
terminal WebSocket URL and then appended ?user_id=<caller>, with no encoding or
validation (the HTTP sibling proxy_terminal runs _sanitize_proxy_path; this path ran
nothing). An encoded '?'/'&' smuggled through session_id survives Open WebUI's single
decode and is re-decoded by the upstream, injecting an attacker-chosen user_id ahead
of the appended one. Query parsing binds the first occurrence, so the orchestrator
resolves the spoofed user's terminal scope, letting a normal authenticated user
present another user's identity to the upstream (CWE-116/863).

Encode session_id as an opaque path segment with urllib.parse.quote(session_id,
safe=''). This neutralises '?'/'#'/'&' at any decode depth (the upstream's single
decode reverses only the quote, leaving the original delimiters inert as path
content), while legitimate UUID session ids pass through unchanged. The appended
user_id is then the only query parameter the upstream binds.

The separate concern that the forwarded identity is a bearer claim with no integrity
binding spans Open WebUI and the upstream terminal server and is not addressed here.

Co-authored-by: rexpository <30176934+rexpository@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 00:15:39 +02:00
Timothy Jaeryang Baek 8b35ce924b refac 2026-06-17 00:15:04 +02:00
G30andGitHub 966b3fdb57 fix(ui): deduplicate layout-level API requests (#25942) 2026-06-17 00:09:50 +02:00
G30andGitHub ee3a49a88d fix(ui): deduplicate store fetches with null-guards and mutex (#25943) 2026-06-17 00:07:11 +02:00
22f2fe1ffb Require auth on ydoc:awareness:update and ydoc:document:leave handlers (CWE-306) (#25946)
These were the only two ydoc Socket.IO handlers missing the SESSION_POOL guard that
every sibling (join/state/update) enforces. With always_connect=True admitting
unauthenticated sockets, a client that knew a note's document_id could broadcast
spoofed awareness (cursors/presence) and fake ydoc:user:left events into a live
editing room, attributed to any client-supplied user_id.

Both handlers now require an authenticated SESSION_POOL session; the awareness handler
additionally requires prior ydoc:document:join (room membership); and the broadcast
user_id is fixed to the authenticated identity instead of the client-supplied value,
removing the impersonation. Document content was never reachable (ydoc:document:update
already enforces write permission).

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 00:06:13 +02:00
Timothy Jaeryang Baek 993e749121 refac 2026-06-17 00:05:45 +02:00
4c06b392da Don't let SCIM's active flag demote an admin (defense-in-depth) (#25948)
The SCIM update_user (PUT) and patch_user (PATCH) handlers mapped the SCIM active field
unconditionally onto the role column (role = 'user' if active else 'pending'), so a routine
IdP sync or a misconfigured IdP that marked a locally-provisioned admin inactive would
silently strip that admin's role and could lock an instance out of its own administration.

Gate both active->role assignments on user.role != 'admin' so SCIM provisioning can
activate/deactivate ordinary users but never demotes an existing admin; admin role changes
continue to go through the dedicated admin endpoints. SCIM already cannot promote to admin
(active only maps to user/pending), so this is symmetric.

Credit to @HOHK0923 for surfacing the admin-demotion footgun.

Co-authored-by: HOHK0923 <118590749+HOHK0923@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 23:59:55 +02:00
Timothy Jaeryang Baek fbcdcf146b refac 2026-06-16 23:59:24 +02:00
ec86ce5cf7 fix(oauth): use Protected Resource Metadata scopes in MCP DCR flow (#25958)
* fix(oauth): use Protected Resource Metadata scopes in MCP DCR flow

The Dynamic Client Registration flow seeded the registration request
`scope` from the Authorization Server's `scopes_supported` (RFC 8414),
which is a full catalog of every scope the AS can grant across all
resources. Per RFC 9728 and the MCP Scope Selection Strategy, the
resource-specific Protected Resource Metadata `scopes_supported` is the
correct, least-privilege source.

The PRM is already fetched in this function; this change prefers its
`scopes_supported` and keeps the AS `scopes_supported` only as a
fallback when the PRM advertises none. Mirrors the static-credentials
fix in #24690.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(oauth): trim DCR scope comment per review feedback

Shortens the inline comment per review feedback on #25958.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 23:53:38 +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