Commit Graph

5836 Commits

Author SHA1 Message Date
Timothy Jaeryang Baek 39ea7bf63d chore: dep bump 2026-04-13 21:45:17 -05:00
Timothy Jaeryang Baek cf4218e688 refac 2026-04-13 21:29:03 -05:00
Algorithm5838 33a4d1b412 fix: image url to base64 conversion (#23685) 2026-04-13 19:15:22 -05:00
Timothy Jaeryang Baek c767bcaa73 refac 2026-04-13 18:20:46 -05:00
Timothy Jaeryang Baek 715cf9797a refac 2026-04-13 16:25:44 -05:00
Classic298 8979987eed fix: drop extra='allow' on FolderForm and FolderUpdateForm (#23648)
* fix: drop extra='allow' on FolderForm and FolderUpdateForm

These request models were configured to accept arbitrary extra fields,
which were then merged into the folder row via form_data.model_dump().
In insert_new_folder the server-assigned user_id is placed before the
form spread, so a client-supplied user_id in the request body would
override it and the folder would be persisted against another account.

Strictly typed inputs are the correct shape for these endpoints — the
client has no legitimate reason to send fields beyond the declared
ones, and dropping extra='allow' closes the mass-assignment sink at
the validation layer instead of relying on every callsite to merge
fields in the right order.

* fix: reject unknown fields on FolderForm and FolderUpdateForm

Address review feedback: dropping extra='allow' fell back to Pydantic
v2's default extra='ignore', which only silently drops unknown fields
instead of rejecting them. The intent for these request models is a
strict input contract — fail fast when a client sends anything the
server does not expect — so explicitly set extra='forbid'. This also
makes the hardening visible in the form definition rather than implicit
in the default.
2026-04-13 16:14:00 -05:00
Timothy Jaeryang Baek 8dba798cce refac 2026-04-13 16:03:36 -05:00
Timothy Jaeryang Baek 611fe0c8a9 refac 2026-04-13 15:14:55 -05:00
Timothy Jaeryang Baek 31406caa79 refac 2026-04-13 15:13:14 -05:00
Timothy Jaeryang Baek 9c64d84ad9 refac 2026-04-13 15:03:22 -05:00
Timothy Jaeryang Baek 40f5b3d135 refac 2026-04-13 14:51:09 -05:00
Timothy Jaeryang Baek 869cf9e848 refac 2026-04-13 14:33:23 -05:00
Timothy Jaeryang Baek 2ddcb30b9a refac 2026-04-13 14:29:27 -05:00
Timothy Jaeryang Baek 96265cf042 refac 2026-04-13 14:19:15 -05:00
Timothy Jaeryang Baek 050c4b97a9 refac 2026-04-13 14:13:03 -05:00
Timothy Jaeryang Baek d0188f3fe1 refac 2026-04-13 14:08:58 -05:00
Timothy Jaeryang Baek 45f45f5bba chore: bump dep 2026-04-13 13:49:28 -05:00
Timothy Jaeryang Baek 8936721414 refac 2026-04-13 13:44:44 -05:00
Timothy Jaeryang Baek d1a0fbe292 refac 2026-04-13 13:36:54 -05:00
Timothy Jaeryang Baek 22cfb3c673 refac 2026-04-13 13:26:13 -05:00
Timothy Jaeryang Baek 51765b619c refac 2026-04-13 13:13:45 -05:00
Timothy Jaeryang Baek 20544d412e chore: format 2026-04-12 22:11:10 -05:00
Timothy Jaeryang Baek cb6e77be3e refac 2026-04-12 22:10:43 -05:00
Timothy Jaeryang Baek d4b90f93bd refac 2026-04-12 22:08:27 -05:00
Timothy Jaeryang Baek 26b8ca5b5e refac 2026-04-12 19:41:02 -05:00
Timothy Jaeryang Baek 57784706e4 refac 2026-04-12 19:34:45 -05:00
Timothy Jaeryang Baek 8172c7e3d5 refac 2026-04-12 19:08:30 -05:00
Timothy Jaeryang Baek 498ff8cdc3 refac 2026-04-12 19:05:25 -05:00
Timothy Jaeryang Baek facb194a07 refac 2026-04-12 19:02:51 -05:00
Timothy Jaeryang Baek c3c8c605d7 refac 2026-04-12 18:56:04 -05:00
Timothy Jaeryang Baek 3c2c611ba9 refac 2026-04-12 18:49:34 -05:00
Timothy Jaeryang Baek a359262616 refac 2026-04-12 18:48:06 -05:00
Classic298 d59b933bf2 fix: apply REDIS_KEY_PREFIX to tool_servers and terminal_servers cache keys (#23649)
These four Redis calls were the only places in the backend still using
bare cache keys. Every other call already namespaces through
REDIS_KEY_PREFIX, which is how multiple Open WebUI instances sharing a
Redis database stay isolated. Two instances with different
TOOL_SERVER_CONNECTIONS or TERMINAL_SERVER_CONNECTIONS would otherwise
clobber each other's cached OpenAPI specs.
2026-04-12 18:29:21 -05:00
Timothy Jaeryang Baek 25898116ea chore: format 2026-04-12 18:12:59 -05:00
Classic298 4292358bd5 feat: log provider errors to console for better insights (#23379)
* fix: log provider errors that were silently swallowed

* Update main.py

* fix: wrap non-JSON SSE error responses in JSON so middleware handles them
2026-04-12 18:07:20 -05:00
Classic298 67023037f8 fix: replace brittle profile_image_url allowlist with safe-scheme validation (#23389)
* fix: replace brittle profile_image_url allowlist with safe-scheme validation

The previous validation used a hardcoded allowlist of specific static
paths and a single Gravatar prefix. This rejected OWUI's own internal
API paths (e.g. /api/v1/users/{id}/profile/image) and external OAuth
avatar URLs, making it impossible to save user profiles from the admin
panel.

Replace with scheme-based validation that allows relative paths,
HTTP(S) URLs, and data:image URIs while blocking dangerous schemes
like javascript:, file:, and ftp:.

Fixes open-webui#23387

* fix: harden profile image URL validation per review feedback

- Restrict data URIs to safe raster formats (png/jpeg/gif/webp);
  SVG is excluded because it can carry embedded scripts.
- Block scheme-relative URLs (//host/path) which browsers resolve
  against the current protocol, bypassing the relative-path check.

* fix: use structural validation instead of prefix checks

- Use urlparse for HTTP(S) URLs: gives case-insensitive scheme
  matching and rejects bare schemes with no host (e.g. https://).
- Use a compiled regex for data URIs: enforces the ;base64, boundary,
  restricts to safe raster formats, and is case-insensitive per spec.
- Removes the startswith-based prefix tuple in favour of proper
  URL and data URI parsing.

* fix: validate hostname not netloc, fix misleading comment

- Use parsed.hostname instead of parsed.netloc so URLs like
  http://:80/path (non-empty netloc but no actual host) are rejected.
- Update data URI comment to accurately state we validate MIME type
  and structure, not base64 payload integrity.

* fix: constrain relative paths to known-safe prefixes

Accepting any relative path starting with / allowed a user to set
their profile_image_url to an arbitrary internal GET endpoint. When
another user (e.g. an admin) views that profile, the browser fires
the GET with the viewer's session cookies — an authenticated GET
trigger surface.

Constrain to known-safe prefixes (/api/v1/users/, /static/) and
exact matches (/user.png, /favicon.png) which are the only relative
paths OWUI itself generates.

* fix: use exact matches and anchored regex, eliminate all prefix wildcarding

Replace all startswith-based path checks with:
- frozenset exact matches for static assets (/user.png, /favicon.png,
  /static/favicon.png)
- Anchored regex for the OWUI profile image API route that accepts
  only /api/v1/users/{id}/profile/image (no trailing components,
  no path traversal across segments)

This eliminates every prefix-based attack surface:
- /api/v1/users/{id}/anything-else is rejected
- /static/../../etc/passwd is rejected
- /api/v1/users/../../admin/config is rejected
- Arbitrary internal GET triggers are no longer possible

* fix: exclude query/fragment delimiters from user-ID regex segment

Change [^/]+ to [^/?#]+ so that inputs like
/api/v1/users/alice?x=1/profile/image are rejected — the browser
would interpret ? as the query string start, making the actual
request target /api/v1/users/alice instead of the intended route.
2026-04-12 17:57:49 -05:00
Classic298 e7ff4768f8 fix: Add ownership checks to global task endpoints (#23454)
* Add ownership checks to global task endpoints

- Restrict GET /api/tasks and POST /api/tasks/stop/{task_id} to admin-only
- Add new scoped POST /api/tasks/chat/{chat_id}/stop endpoint with ownership
  check so regular users can stop their own chat tasks
- Allow admins to access the scoped chat task endpoints alongside owners
- Update frontend to use the new scoped stop endpoint when a chatId is available

https://claude.ai/code/session_01K7zPDvvjRu8AxJ4Br2HhZc

* Handle temporary (local:) chat IDs in scoped task endpoints

Temporary chats use local:<socketId> as chat_id which doesn't exist in
the DB. The scoped endpoints now skip ownership checks for local: IDs
(they aren't enumerable) and use {chat_id:path} to handle the colon in
the URL path.

https://claude.ai/code/session_01K7zPDvvjRu8AxJ4Br2HhZc

* Verify session ownership for local: chat IDs and URL-encode chat_id

- For local:<socketId> chat IDs, look up the socket's owner in
  SESSION_POOL and verify it matches the requesting user (or admin)
- URL-encode chat_id in frontend fetch calls to handle special
  characters (colon in local: IDs) safely

https://claude.ai/code/session_01K7zPDvvjRu8AxJ4Br2HhZc

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-12 17:56:43 -05:00
Timothy Jaeryang Baek c47dd7b771 refac 2026-04-12 17:22:06 -05:00
Timothy Jaeryang Baek 4498e6faf2 refac 2026-04-12 16:59:05 -05:00
Timothy Jaeryang Baek 47d413ce7b refac 2026-04-12 16:47:23 -05:00
Classic298 0753409e7b fix: use ipaddress stdlib for IPv6 SSRF protection (#23453)
The validators.ipv6(ip, private=True) call always returns a falsy ValidationError because validators==0.35.0 does not support the private kwarg for IPv6. This means any hostname resolving to a private IPv6 address (::1, fd00::*, ::ffff:169.254.169.254) bypasses SSRF protection entirely, circumventing the fix for CVE-2025-65958.

Replace both the IPv4 and IPv6 validators-based private checks with Python's stdlib ipaddress module using an allowlist approach (not addr.is_global). This blocks all non-globally-routable addresses — private, loopback, link-local, reserved, multicast, and unspecified — for both IPv4 and IPv6, including IPv4-mapped IPv6 addresses.
2026-04-12 16:34:13 -05:00
Classic298 b78dabb442 fix: reject empty passwords in LDAP authentication to prevent unauthenticated binds (#23633)
Per RFC 4513, a Simple Bind with a non-empty DN but empty password is unauthenticated simple authentication. Many LDAP servers (OpenLDAP default, some AD configs) accept these binds, allowing account takeover without valid credentials.

Rejects empty and whitespace-only passwords before attempting the LDAP bind.
2026-04-12 16:33:57 -05:00
Classic298 83024d00bb fix: enforce API key endpoint restrictions at the auth layer, not middleware (#23637)
The APIKeyRestrictionMiddleware only inspected the Authorization header for sk- tokens, but get_current_user also reads API keys from cookies and x-api-key headers. This allowed complete bypass of endpoint restrictions by sending the key via an alternate transport.

Moves the restriction check into get_current_user_by_api_key so it runs regardless of how the API key was delivered. Removes the now-redundant middleware.
2026-04-12 16:33:41 -05:00
Classic298 4f94d21780 fix: enforce filter_allowed_access_grants on channel create and update (#23638)
Unlike all other resource routers (knowledge, models, notes, prompts, tools, skills), the channel router did not call filter_allowed_access_grants. This allowed any user to set wildcard access grants on group channels, bypassing the admin's public sharing permission framework.

Adds filter_allowed_access_grants with the sharing.public_channels permission key to both create and update endpoints, matching the pattern used by all other resource routers.
2026-04-12 16:27:44 -05:00
Timothy Jaeryang Baek 5ee791d5d2 refac 2026-04-12 16:25:01 -05:00
Classic298 fb5ef978bf fix: enforce OAUTH_ALLOWED_DOMAINS on token exchange endpoint (#23639)
The OAuth token exchange endpoint skipped the domain allowlist check that the normal OAuth callback enforces. An attacker with a valid OAuth token from a non-allowed domain (e.g. gmail.com) could bypass the admin's domain restriction policy entirely.

Adds the same domain validation check used in the OAuth callback, denying access when the email domain is not in the allowed list.
2026-04-12 16:19:58 -05:00
Classic298 977d638afe fix: invalidate stale Socket.IO sessions on role change and user deletion (#23642)
SESSION_POOL caches user.role at connection time and never refreshes it. When an admin demotes or deletes a user, their socket sessions retain the old cached role until voluntary disconnect, allowing continued use of admin-gated socket features (ydoc editing, channel access).

Adds disconnect_user_sessions() helper that disconnects all sockets for a user ID. Called from update_user_by_id (on role change) and delete_user_by_id. The client auto-reconnects and re-authenticates with fresh DB data.
2026-04-12 16:19:38 -05:00
Timothy Jaeryang Baek de27a12151 refac 2026-04-12 14:39:23 -05:00
Classic298 f6b85700ea fix: gate OpenAI catch-all proxy behind ENABLE_OPENAI_API_PASSTHROUGH toggle (#23640)
The catch-all /{path:path} proxy forwards any request to the upstream OpenAI-compatible API with the admin's API key and no access control. This is an intentional proxy but should be opt-in.

Adds ENABLE_OPENAI_API_PASSTHROUGH env var (defaults to False). When disabled, the catch-all returns 403. No other routers (Ollama, responses) have catch-all proxies.
2026-04-12 14:26:12 -05:00
Timothy Jaeryang Baek d40f31982b refac 2026-04-12 14:24:08 -05:00