Commit graph

670 commits

Author SHA1 Message Date
Timothy Jaeryang Baek
b81627b2c9 refac 2026-07-26 18:46:39 -04:00
G30
79695a1d14
fix: persist modelIdx so duplicate side-by-side models don't collapse on reload (#26980)
When the same model is selected multiple times in a side-by-side chat,
each response is created with a distinct modelIdx (0,1,2,3) that
identifies its column. The backend now owns message persistence, but it
built the assistant placeholders without modelIdx, so the field was
never saved. On reload MultiResponseMessages groups responses by
modelIdx and falls back to grouping by model id when modelIdx is
missing; with duplicate models that fallback lumps every response into
each column, so all columns render the first response (and show a bogus
'1/N' pager).

Send modelIdx with each message_ids entry from the frontend and persist
it on the assistant placeholders in both the new-chat and existing-chat
paths. The message_ids list is now forwarded for every send (not just
multi-model ones) so single-column regenerations in a duplicate-model
chat also keep their column identity across reloads.
2026-07-26 18:44:03 -04:00
G30
71f8b6d5b4
feat: add a master OAuth / OIDC enable toggle in Authentication settings (#26988)
The OAuth / OIDC section in Admin Settings > Authentication had no
enable/disable switch, unlike the LDAP section above it. Add one that
persists via the existing Save flow and actually gates OAuth sign-in,
mirroring how the LDAP toggle works.

- config: new ENABLE_OAUTH persistent config ('oauth.enable'), defaulting
  to True so existing deployments with a provider configured keep working.
- oauth: expose ENABLE_OAUTH via the OAuth runtime config and reject the
  login and callback handlers with 404 when it is disabled.
- /api/config: report no OAuth providers when disabled so the login page
  hides the OAuth buttons (and cannot auto-redirect), without clearing the
  admin's provider configuration.
- auths: expose ENABLE_OAUTH through the admin OAuth config get/update
  endpoints (OAuthConfigForm + OAUTH_CONFIG_KEYS).
- Authentication.svelte: bind the OAuth / OIDC header Switch to the
  persisted oauthConfig.ENABLE_OAUTH and collapse the section when off,
  matching the LDAP header (size, weight, alignment).
2026-07-26 18:43:21 -04:00
Classic298
e140d8f3cc
fix: scope timer cancellation to the timer's owner (#27472)
The events:chat socket handler called the ownership-checked update for last_read_at, discarded the boolean it returns, and then cancelled the chat's pending timers regardless of the answer. cancel_timers_for_chat selected on the internal marker, the type, the parent chat id and the status, and never on the owner, so it matched rows belonging to any user. An authenticated user who knew another user's chat id could mark that chat read over their own socket session and silently cancel the owner's pending timers, and the owner got no notification: the scheduled action simply never fired.

The missing owner predicate also cut the other way in ordinary use. Because the query matched every timer sharing a parent chat id, one user reading a chat cancelled the timers of anyone else holding one on the same chat, so this was collateral damage as much as an attack.

cancel_timers_for_chat now requires a user_id and filters on it, which is the durable fix, and the socket handler returns early unless the ownership-checked update reports that the caller owns the chat. The parameter is required rather than defaulted so a later caller cannot reintroduce the unscoped query by omission. Both existing call sites already know the acting user. Timer rows are created with the same owner as the parent chat and the execution path already refuses to run one whose owner does not match, so scoping the cancellation the same way cannot strand a timer that would otherwise have fired.

One behaviour change worth noting: an administrator posting into another user's chat no longer cancels that user's chat.user_message timers, because the acting user is the administrator. The timer fires instead of being cancelled, which is the safe direction.
2026-07-26 18:12:44 -04:00
Classic298
c882222f68
fix: verify chat ownership on /api/chat/completed and /api/chat/actions (#27486)
Both routes read `chat_id` from the request body and passed it into `get_event_emitter` without checking the caller owns that chat. The emitter persists through `upsert_message_to_chat_by_id_and_message_id`, which resolves by primary key and takes no owner argument, so an invoked filter or action wrote into whichever chat the caller named. `/api/chat/completions` already performs this check; these two routes did not.

Adds `verify_chat_ownership`, called at the top of both handlers. It runs before the existing try block because the `except Exception` there catches HTTPException and would rewrite the 404 into a 400. Admins are exempt, matching the completions path, so deliberate cross-user operations keep working.

`local:` chat ids are allowed through: they are per-socket, the emitter suppresses database writes for them, and the socket emit targets the caller's own room. `channel:` chat ids are rejected instead. They reach the channel emitter, whose write only checks that the message belongs to the channel and never that the caller may write it, and the membership and write-access gate for channels exists solely on `/api/chat/completions`. No caller sends a `channel:` id to these two routes: the only frontend callers are in the regular chat UI, and the backend channel path dispatches through the completions handler.

Co-authored-by: manus-use <213290975+manus-use@users.noreply.github.com>
2026-07-26 17:39:11 -04:00
Timothy Jaeryang Baek
8cbb7f765c refac 2026-07-24 00:47:12 -04:00
Classic298
d0f759ce40
fix: capture uncompressed response bodies in audit logs (#27369)
CompressMiddleware was registered before AuditLoggingMiddleware. Starlette prepends on add_middleware, so the audit layer ended up outside compression and, at the REQUEST_RESPONSE level, recorded the zstd/brotli/gzip bytes of every response, decoded with errors='replace'. Any client that sent Accept-Encoding (i.e. every browser) therefore produced audit entries whose response_object was unreadable mojibake.

Registering the audit middleware before the compression middleware places it inside compression, so it observes the response body exactly as the route produced it while the client still receives the compressed stream.

Verified with a stacked ASGI harness: in the old order the captured body is not parseable; in the new order the captured body round-trips as the original JSON and the client response stays compressed.
2026-07-23 18:09:40 -05:00
Classic298
6b655689cc perf: cut repeated per-model work out of model list assembly
get_all_models runs on every models refresh and, without the base-models cache (off by default), on every /api/models request. Several of its costs multiplied by the model count for no reason:

- The active action and filter id sets were derived from get_functions_by_type, which loads full function rows including plugin source and validates them, only for the ids and is_global flags. A generalized column-only query now returns (id, is_global) tuples; the existing filter-specific helper delegates to it.
- Action priorities were computed inside the per-model sort key, constructing a pydantic Valves object per action per model; with global actions in every model's list that was models x actions constructions per refresh. Priorities are now memoized per action.
- Global action and filter item dicts were rebuilt per model from the same modules. The item lists are now built once per function and shallow-copied per model, keeping per-model dicts independent exactly as before (nested values were already shared).
- Deactivated base-model overrides were dropped with models.remove, a linear scan and shift per removal; removals are now collected and filtered out in one identity-based pass, preserving list.remove's exact object semantics.
- RedisDict.set fingerprinted the payload by serializing the already-serialized mapping a second time plus a sha256; a direct dict comparison against the last written mapping has the same skip semantics without re-serializing anything.
- /api/models did tag normalization and profile-image stripping for every model before access filtering discarded the invisible ones, and always evaluated a json.dumps debug f-string; the work now runs only on visible models and the debug line is gated on the log level. The duplicate-id dedup keeps its position before filtering so the effective-model semantics are unchanged.

Benchmark:

| metric | before | after |
| --- | --- | --- |
| model-cache fingerprint, 200 models | 45 us | 1.4 us |
| action priority Valves builds, 200 models x 4 global actions | 0.37 ms (800 builds) | 0.002 ms (4 builds) |
| function-table payload for id sets | full rows incl. source | (id, is_global) tuples |

Functionally verified: the column-only id query matches the full-row query for actions and filters including inactive exclusion, and the fingerprint skip logic writes on first set, skips identical payloads, updates plus deletes stale keys on change and clears on empty, against a scripted fake Redis.
2026-07-23 19:05:32 -04:00
Timothy Jaeryang Baek
bb12b1a18b refac 2026-07-23 04:16:14 -04:00
Timothy Jaeryang Baek
49e57f4e7e chore: format 2026-07-20 22:11:42 -04:00
Timothy Jaeryang Baek
2e857a82d7 refac 2026-07-16 02:47:51 -04:00
Timothy Jaeryang Baek
75894161e4 refac 2026-07-16 02:35:44 -04:00
Timothy Jaeryang Baek
c55e373b99 refac 2026-07-16 00:58:34 -04:00
Timothy Jaeryang Baek
b23ddeb280 refac 2026-07-16 00:30:44 -04:00
Timothy Jaeryang Baek
8f77533317 refac 2026-07-14 01:00:24 -04:00
Timothy Jaeryang Baek
7088d245bb refac 2026-07-14 00:10:28 -04:00
Timothy Jaeryang Baek
08dacd19da refac 2026-07-13 23:01:10 -04:00
Timothy Jaeryang Baek
51ff386fd6 refac 2026-07-13 23:00:54 -04:00
Timothy Jaeryang Baek
8e46450acd refac 2026-07-09 17:28:34 -05:00
Timothy Jaeryang Baek
90eca2ac25 refac 2026-07-01 03:37:35 -05:00
Timothy Jaeryang Baek
7f182ea063 refac 2026-07-01 03:35:27 -05:00
Timothy Jaeryang Baek
ff5cec43bd refac 2026-06-29 11:56:00 -05:00
Timothy Jaeryang Baek
754787f43d refac 2026-06-29 11:51:45 -05:00
Timothy Jaeryang Baek
815446d5bb refac 2026-06-29 11:33:49 -05:00
Classic298
75df948f34
feat: forward client User-Agent to model backends via {{USER_AGENT}} placeholder (#26333)
Adds a {{USER_AGENT}} custom-header placeholder that relays the inbound
client's User-Agent to upstream model backends, so providers see the real
client instead of Open WebUI's internal aiohttp UA. This makes upstream
usage/cost attribution and backend telemetry possible, and is opt-in
per-connection (no global flag): admins add {{USER_AGENT}} to a connection's
custom headers in Admin > Settings > Connections.

The placeholder is sourced from the live inbound request (with a metadata
fallback for detached RAG/tool calls), so it resolves on every prompt-sending
path, not just chat completions:

- OpenAI completions, Responses API, and proxy — all route through
  get_headers_and_cookies, which now passes the request into get_custom_headers.
- Anthropic Messages API (/api/v1/messages) — already covered, it delegates
  to the chat completion handler.
- Ollama (/api/chat, /v1/completions, /v1/chat/completions, /v1/messages,
  /v1/responses) — previously had no custom-header support at all; send_request
  now applies per-connection custom headers (with templating) for every
  Ollama prompt endpoint.

Custom headers are applied after the built-in user-info headers so explicit
admin-configured headers take precedence. The other existing placeholders
({{CHAT_ID}}, {{USER_ID}}, ...) now also work on the newly covered paths.

Frontend: the connection editor's Headers field is now shown for Ollama
connections too (previously gated to non-Ollama), so the placeholder can be
configured there.

Ref: open-webui/open-webui#26159
2026-06-29 05:58:19 -05:00
Timothy Jaeryang Baek
8927c9bb3d refac 2026-06-29 05:14:34 -05:00
Timothy Jaeryang Baek
67c9de8efd refac 2026-06-29 04:42:59 -05:00
Timothy Jaeryang Baek
7be009649a refac 2026-06-29 02:57:58 -05:00
Timothy Jaeryang Baek
ac3449cac9 refac 2026-06-29 02:26:27 -05:00
Timothy Jaeryang Baek
bc70696f4f refac 2026-06-29 00:40:28 -05:00
Timothy Jaeryang Baek
ef8c9c063c refac 2026-06-28 23:22:10 -05:00
Timothy Jaeryang Baek
464e703e47 refac 2026-06-28 22:50:31 -05:00
Timothy Jaeryang Baek
516051304e refac 2026-06-28 22:33:59 -05:00
Timothy Jaeryang Baek
1a8e1a9939 refac 2026-06-25 15:22:31 +01:00
Timothy Jaeryang Baek
5576e6ed8a refac 2026-06-25 14:34:22 +01:00
Timothy Jaeryang Baek
7453968678 refac 2026-06-25 14:19:30 +01:00
Timothy Jaeryang Baek
b5c43968db refac 2026-06-25 03:31:45 +01:00
Timothy Jaeryang Baek
19db873603 refac 2026-06-23 23:35:40 +02:00
Timothy Jaeryang Baek
7f08376f0c refac 2026-06-23 23:18:05 +02:00
Timothy Jaeryang Baek
91762ed807 refac
Some checks are pending
Python CI / Ruff Format (3.11) (push) Waiting to run
Python CI / Ruff Format (3.12) (push) Waiting to run
Create and publish Docker images with specific build args / build (map[arch:linux/amd64 runner:ubuntu-latest], map[build_args: free_disk:false name:main suffix:]) (push) Waiting to run
Create and publish Docker images with specific build args / build (map[arch:linux/amd64 runner:ubuntu-latest], map[build_args:USE_CUDA=true USE_CUDA_VER=cu126 free_disk:true name:cuda126 suffix:-cuda126]) (push) Waiting to run
Create and publish Docker images with specific build args / build (map[arch:linux/amd64 runner:ubuntu-latest], map[build_args:USE_CUDA=true free_disk:true name:cuda suffix:-cuda]) (push) Waiting to run
Create and publish Docker images with specific build args / build (map[arch:linux/amd64 runner:ubuntu-latest], map[build_args:USE_OLLAMA=true free_disk:false name:ollama suffix:-ollama]) (push) Waiting to run
Create and publish Docker images with specific build args / build (map[arch:linux/amd64 runner:ubuntu-latest], map[build_args:USE_SLIM=true free_disk:false name:slim suffix:-slim]) (push) Waiting to run
Create and publish Docker images with specific build args / build (map[arch:linux/arm64 runner:ubuntu-24.04-arm], map[build_args: free_disk:false name:main suffix:]) (push) Waiting to run
Create and publish Docker images with specific build args / build (map[arch:linux/arm64 runner:ubuntu-24.04-arm], map[build_args:USE_CUDA=true USE_CUDA_VER=cu126 free_disk:true name:cuda126 suffix:-cuda126]) (push) Waiting to run
Create and publish Docker images with specific build args / copy-to-dockerhub (, main) (push) Blocked by required conditions
Create and publish Docker images with specific build args / build (map[arch:linux/arm64 runner:ubuntu-24.04-arm], map[build_args:USE_CUDA=true free_disk:true name:cuda suffix:-cuda]) (push) Waiting to run
Create and publish Docker images with specific build args / build (map[arch:linux/arm64 runner:ubuntu-24.04-arm], map[build_args:USE_OLLAMA=true free_disk:false name:ollama suffix:-ollama]) (push) Waiting to run
Create and publish Docker images with specific build args / build (map[arch:linux/arm64 runner:ubuntu-24.04-arm], map[build_args:USE_SLIM=true free_disk:false name:slim suffix:-slim]) (push) Waiting to run
Create and publish Docker images with specific build args / merge (map[name:cuda suffix:-cuda]) (push) Blocked by required conditions
Create and publish Docker images with specific build args / merge (map[name:cuda126 suffix:-cuda126]) (push) Blocked by required conditions
Create and publish Docker images with specific build args / merge (map[name:main suffix:]) (push) Blocked by required conditions
Create and publish Docker images with specific build args / merge (map[name:ollama suffix:-ollama]) (push) Blocked by required conditions
Create and publish Docker images with specific build args / merge (map[name:slim suffix:-slim]) (push) Blocked by required conditions
Create and publish Docker images with specific build args / copy-to-dockerhub (-cuda, cuda) (push) Blocked by required conditions
Create and publish Docker images with specific build args / copy-to-dockerhub (-cuda126, cuda126) (push) Blocked by required conditions
Create and publish Docker images with specific build args / copy-to-dockerhub (-ollama, ollama) (push) Blocked by required conditions
Create and publish Docker images with specific build args / copy-to-dockerhub (-slim, slim) (push) Blocked by required conditions
2026-06-23 00:25:21 +02:00
Timothy Jaeryang Baek
88901bfa04 refac 2026-06-22 14:42:23 +02:00
Timothy Jaeryang Baek
5cdcdbaeec refac 2026-06-17 02:52:35 +02:00
Timothy Jaeryang Baek
56ae99e96a refac 2026-06-16 23:02:20 +02:00
Timothy Jaeryang Baek
b1d40f3409 refac
Some checks failed
Python CI / Ruff Format (3.11) (push) Has been cancelled
Python CI / Ruff Format (3.12) (push) Has been cancelled
Create and publish Docker images with specific build args / build (map[arch:linux/amd64 runner:ubuntu-latest], map[build_args: free_disk:false name:main suffix:]) (push) Has been cancelled
Create and publish Docker images with specific build args / build (map[arch:linux/amd64 runner:ubuntu-latest], map[build_args:USE_CUDA=true USE_CUDA_VER=cu126 free_disk:true name:cuda126 suffix:-cuda126]) (push) Has been cancelled
Create and publish Docker images with specific build args / build (map[arch:linux/amd64 runner:ubuntu-latest], map[build_args:USE_CUDA=true free_disk:true name:cuda suffix:-cuda]) (push) Has been cancelled
Create and publish Docker images with specific build args / build (map[arch:linux/amd64 runner:ubuntu-latest], map[build_args:USE_OLLAMA=true free_disk:false name:ollama suffix:-ollama]) (push) Has been cancelled
Create and publish Docker images with specific build args / build (map[arch:linux/amd64 runner:ubuntu-latest], map[build_args:USE_SLIM=true free_disk:false name:slim suffix:-slim]) (push) Has been cancelled
Create and publish Docker images with specific build args / build (map[arch:linux/arm64 runner:ubuntu-24.04-arm], map[build_args: free_disk:false name:main suffix:]) (push) Has been cancelled
Create and publish Docker images with specific build args / build (map[arch:linux/arm64 runner:ubuntu-24.04-arm], map[build_args:USE_CUDA=true USE_CUDA_VER=cu126 free_disk:true name:cuda126 suffix:-cuda126]) (push) Has been cancelled
Create and publish Docker images with specific build args / build (map[arch:linux/arm64 runner:ubuntu-24.04-arm], map[build_args:USE_CUDA=true free_disk:true name:cuda suffix:-cuda]) (push) Has been cancelled
Create and publish Docker images with specific build args / build (map[arch:linux/arm64 runner:ubuntu-24.04-arm], map[build_args:USE_OLLAMA=true free_disk:false name:ollama suffix:-ollama]) (push) Has been cancelled
Create and publish Docker images with specific build args / build (map[arch:linux/arm64 runner:ubuntu-24.04-arm], map[build_args:USE_SLIM=true free_disk:false name:slim suffix:-slim]) (push) Has been cancelled
Frontend Build / Format & Build (push) Has been cancelled
Frontend Build / Unit Tests (push) Has been cancelled
Create and publish Docker images with specific build args / merge (map[name:cuda suffix:-cuda]) (push) Has been cancelled
Create and publish Docker images with specific build args / merge (map[name:cuda126 suffix:-cuda126]) (push) Has been cancelled
Create and publish Docker images with specific build args / merge (map[name:main suffix:]) (push) Has been cancelled
Create and publish Docker images with specific build args / merge (map[name:ollama suffix:-ollama]) (push) Has been cancelled
Create and publish Docker images with specific build args / merge (map[name:slim suffix:-slim]) (push) Has been cancelled
Create and publish Docker images with specific build args / copy-to-dockerhub (, main) (push) Has been cancelled
Create and publish Docker images with specific build args / copy-to-dockerhub (-cuda, cuda) (push) Has been cancelled
Create and publish Docker images with specific build args / copy-to-dockerhub (-cuda126, cuda126) (push) Has been cancelled
Create and publish Docker images with specific build args / copy-to-dockerhub (-ollama, ollama) (push) Has been cancelled
Create and publish Docker images with specific build args / copy-to-dockerhub (-slim, slim) (push) Has been cancelled
2026-06-05 16:58:16 -04:00
Timothy Jaeryang Baek
6fce92aa12 chore: format 2026-06-01 13:56:55 -07:00
Classic298
33e4e0dcc4
fix: gate chat_completion channel: branch on channel access + message scoping (#24725)
* fix: gate chat_completion channel: branch on channel access + message scoping

When chat_id starts with 'channel:' the chat-completion handler skips
the chat ownership / storage block below it. Nothing replaced that
gate. The downstream channel emitter in socket/main.py:_make_channel_
emitter writes to Messages.update_message_by_id using a caller-supplied
message_id pulled from form_data['id'], with no membership check, no
write-access check, and no validation that the message_id belongs to
the channel.

Net effect: any authenticated user could submit
chat_id='channel:<any-channel-uuid>' + id='<any-message-uuid>' and
overwrite that message with attacker-controlled LLM output. Cross-
channel writes worked too — private channels, DMs, channels the
caller has no access to. Original author attribution stayed intact on
the overwritten row.

Add the missing checks at the channel: branch:

1. Channel must exist (404 otherwise).
2. Non-admin caller must have write access to the channel — membership
   for group/dm channels, AccessGrants permission='write' for others.
3. The message_id (if supplied) must belong to the same channel — a
   caller with write access to channel A cannot use this path to
   overwrite a message in channel B.

Behaviour change is limited to callers who were exploiting the gap:
legitimate flows that supply a message_id under their own channel
membership continue to work unchanged.

Co-authored-by: sfwani <sfwani@users.noreply.github.com>

* chore: trim verbose comment on channel: branch gate

---------

Co-authored-by: sfwani <sfwani@users.noreply.github.com>
2026-06-01 13:37:32 -07:00
Timothy Jaeryang Baek
d64ef1803d refac
Co-Authored-By: Zaid Marji <91486926+zaid-marji@users.noreply.github.com>
2026-06-01 13:07:49 -07:00
Classic298
cc15a01778
fix: don't crash on startup when stdout can't encode the banner (#25482)
The startup banner uses Unicode box-drawing characters. On a stdout that
can't encode them (Windows cp1252, or redirected/headless/pythonw output)
print() raises UnicodeEncodeError and aborts startup. This blocks running
open-webui serve headless on Windows.

Guard the banner print and fall back to a plain ASCII line so startup
always proceeds regardless of the console encoding.

Fixes #24965

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 10:41:43 -07:00
Classic298
b0fa4384ea
fix: cache path traversal via sibling-prefix bypass in serve_cache_file (#25086)
serve_cache_file gated the resolved path with file_path.startswith(os.path.abspath(CACHE_DIR)) without a trailing os.sep, so any path resolving to a sibling whose name starts with the cache-dir basename (e.g. cache_backup, cached_models) passed the prefix check. Authenticated users could read files from such siblings via /cache/../<sibling>/<file>. Appending os.sep to the prefix closes the bypass; deep traversal and absolute paths were already correctly blocked.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-01 10:17:02 -07:00
Timothy Jaeryang Baek
d4030a8aa5 refac 2026-05-31 15:10:48 -07:00