- InterfaceSettings.loadSettingsFromSource now only falls through to
\$config.default_models when !isAdminMode. In admin-defaults mode the
fallback clobbered whatever the admin last saved with the system-
wide default on every reopen, so the modal could show (and
inadvertently re-save) the wrong model.
- Added stripNullValues util and applied it to userUI in +layout.svelte
before deepMerge. deepMerge treats source null as an explicit
override, so common "unset / inherit" shapes in user settings
(textScale, webSearch, etc.) would suppress admin defaults instead
of letting them through. Stripping nulls preserves the PR's stated
behaviour (defaults apply for fields the user hasn't customized)
without changing deepMerge semantics for other callers.
- InterfaceSettings exposes submitSettings() and the admin defaults
modal now binds the component instance via bind:this and calls
submitSettings() through that ref. Replaces the document.getElementById
('tab-interface') lookup, which was ambiguous the moment a second
tab-interface form existed in the DOM.
- InterfaceSettings now reads from a reactive activeSettings alias
(initialSettings in admin mode, $settings otherwise) instead of
reading $settings directly. toggleTitleAutoGenerate spreads
activeSettings.title and the chat-bubble template branch uses the
local chatBubble state, so the admin defaults modal no longer mixes
the current admin's personal settings into the payload or the render
logic.
- deepMerge skips __proto__, constructor, and prototype keys before
copying. Inputs come from API payloads and localStorage, both
untrusted channels, and a crafted payload could otherwise mutate the
object prototype chain or reassign the constructor during app init.
- GET /configs/interface/defaults swaps Cache-Control: private,
max-age=600 for no-store. The endpoint is fetched on every
authenticated app load and merged into runtime settings, so a
10-minute freshness window made admin "save defaults" and "reset all
users" appear to have no effect for up to ten minutes. The read is
cheap (in-memory config), so skipping the cache is fine.
- InterfaceDefaultsModal submitHandler now requestSubmits the embedded
tab-interface form and awaits a Svelte tick before POSTing. That
flushes submit-scoped writes in the InterfaceSettings component
(updateInterfaceHandler -> saveSettings({ models, imageCompressionSize }))
through the saveAdminSettings callback so they land in adminDefaults
instead of being silently dropped on save.
- +layout.svelte only reads localStorage.settings when the server
didn't return user UI, and wraps the JSON.parse in try/catch with a
{} fallback. A stale or hand-edited localStorage value would
otherwise throw and abort the whole authenticated-load path even
when valid userSettings came back from the backend.
- users.py reset_all_users_interface_settings branches explicitly on
postgresql and sqlite; any other dialect now responds 501 with a
clear message naming the unsupported dialect. The previous
"everything that isn't postgres runs SQLite-specific SQL" assumption
would fail on MySQL/MariaDB with an opaque syntax error.
- +layout.svelte always merges adminDefaults with whatever user UI is
available (server-returned settings.ui, or localStorage, or {}). The
previous branch skipped defaults entirely when getUserSettings()
returned null, which is exactly the new-user case the feature
targets — admin defaults never reached users who hadn't customized
anything.
- InterfaceDefaultsForm.validate_chat_direction now accepts any casing
and normalizes to the shape the frontend store expects ('auto'
lowercase, 'LTR' / 'RTL' uppercase). The interface-settings toggle
cycles uppercase values, so admin saves with LTR/RTL were rejected
with 422 by the old lowercase-only validator.
- admin Settings/Interface.svelte adds the missing entry points: a
"Configure Defaults" button that toggles showInterfaceDefaultsModal
and a destructive "Reset All Users Interface Settings" button that
toggles showResetConfirmDialog. The flags and bound components were
already in place; nothing was flipping them.
- getInterfaceDefaults / setInterfaceDefaults / resetAllUsersInterfaceSettings
fall back through err.detail / err.message / stringification so
network-level failures (fetch TypeError, non-JSON body) surface as
thrown errors instead of resolving to null and letting callers treat
failed requests as silent successes.
- Restore the /** opener above displayFileHandler in utils/index.ts that
was dropped during the earlier merge; the orphaned comment body made
the file fail TypeScript parsing and broke the frontend build.
- admin Settings/Interface.svelte now imports InterfaceDefaultsModal
and ConfirmDialog and declares the showInterfaceDefaultsModal /
showResetConfirmDialog flags that were already being bound at the
bottom of the template, resolving the unresolved-identifier compile
errors.
- InterfaceDefaultsModal no longer lowercases chatDirection before
saving. The user-side store is typed 'LTR' | 'RTL' | 'auto' and the
toggle/render logic matches those uppercase values; lowercasing on
the admin side made every saved RTL/LTR default silently render as
"Auto" for end users.
- InterfaceDefaultsModal drops the outer <form> wrapper. InterfaceSettings
already renders its own <form> and nesting forms is invalid HTML; the
Save button is now a plain type="button" with on:click=submitHandler,
which keeps the click-to-save behaviour without the nested-form event
routing surprises.
- +layout.svelte fetches user settings and admin defaults in parallel
via Promise.all, with an inline .catch fallback on the defaults call
so the endpoint being unavailable still lets user settings apply.
This removes an avoidable serial round-trip on every authenticated
load.
Resolve backend/open_webui/routers/users.py conflict.
- Drop the sync sqlalchemy.orm.Session / get_session / get_db imports
kept on this branch; dev has migrated users.py to AsyncSession via
get_async_session and that's now the only DB style in this file.
- Keep dev's async get_user_groups_by_id (await Groups.get_groups_by_member_id).
- Port this branch's POST /interface/settings/reset endpoint to the
async DB pattern: accept AsyncSession from get_async_session, build
the reset with sqlalchemy.update(...) for postgres (JSONB jsonb_set)
and text(...) for sqlite, await db.execute / db.commit / db.rollback,
and read rowcount from the update result instead of a follow-up
SELECT changes() round-trip.
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.
The GET /channels/{id}/members endpoint checked membership for group/dm channels but had no access gate for standard channels, allowing any authenticated user with channels permission to enumerate members of private standard channels by UUID.
The model name from user input was interpolated directly into Azure deployment URL paths without validation. A user could send a model name like '../../management/foo' to traverse the URL path and hit unintended Azure endpoints with the admin's API key.
Adds _sanitize_model_for_url that rejects path separators and traversal sequences, and percent-encodes the name. Applied at convert_to_azure_payload (covers chat completions + proxy) and the responses endpoint's direct URL construction.
These four endpoints checked model existence but never verified the user has read access via AccessGrants, allowing any authenticated user to use restricted models.
Uses the canonical check_model_access helper from utils.access_control.
Both LDAP and OAuth registration checked user count before insert to determine whether to assign admin role. With multiple workers, concurrent first-user registrations could each see zero users and both create admin accounts.
Applies the insert-first-check-after pattern already used by signup_handler: insert with DEFAULT_USER_ROLE, then atomically check get_num_users()==1 and promote only the sole user to admin.
is_user_channel_member and is_user_channel_manager did not filter on is_active, allowing deactivated members to retain read/write access to group channels via direct API calls.
The /responses proxy endpoint only required authentication via
get_verified_user but did not check per-model access grants. This
allowed any authenticated user to access any model through this
endpoint, bypassing the access control system.
Extract a shared check_model_access helper into utils/access_control
and replace all inline access control blocks across openai.py and
ollama.py (7 locations) with calls to this helper. This eliminates
code duplication and prevents future policy drift between endpoints.
CWE-862: Missing Authorization
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:H (6.5 Medium)
Introduces REDIS_HEALTH_CHECK_INTERVAL and wires it through to every
Redis client created by get_redis_connection (plain, cluster and
sentinel paths, sync and async). When set, redis-py will PING any
connection idle longer than the interval on checkout, so dead sockets
are surfaced as reconnectable errors before a real command lands on
them.
Defaults to unset (empty string) so existing deployments see no
behavioural change. Operators who want the protection should set it
shorter than the Redis server `timeout` setting and any firewall/LB
idle timeout on the path to Redis.
Co-authored-by: Claude <noreply@anthropic.com>
Introduces REDIS_SOCKET_KEEPALIVE and wires socket_keepalive=True
through to every Redis client created by get_redis_connection
(plain, cluster and sentinel paths, sync and async). When enabled,
the kernel sends TCP keepalive probes on idle connections so
half-closed sockets (e.g. after a silent firewall/LB reset or a NIC
flap) are detected before the next command lands on them and the
request never sees a "Connection reset by peer" error.
Defaults to off so existing deployments see no behavioural change.
Operators who want the protection set REDIS_SOCKET_KEEPALIVE=true
in their environment.
Co-authored-by: Claude <noreply@anthropic.com>
* fix(redis): honor REDIS_SOCKET_CONNECT_TIMEOUT on non-sentinel clients
Previously only the sentinel path passed REDIS_SOCKET_CONNECT_TIMEOUT
through to the Redis client. Plain redis:// and cluster URLs fell back
to redis-py's default (no explicit connect timeout), so a hung Redis
or a black-holed network path could stall the whole worker until the
kernel gave up. Forwarding the same env var to from_url()/RedisCluster
keeps the behavior consistent across all deployment topologies.
* fix(redis): gate socket_connect_timeout on is-not-None, not truthiness
Addresses review feedback: the truthiness check on REDIS_SOCKET_CONNECT_TIMEOUT
silently dropped an explicit 0 value and was inconsistent with the sentinel
construction path, which forwards the value directly. Switch to `is not None`
so any user-configured value (including 0) is passed through to from_url()
and RedisCluster.from_url().
---------
Co-authored-by: Claude <noreply@anthropic.com>
Differentiate between "Allow File Upload" and "Allow Web Upload"
in Chinese translations to help administrators understand the
distinction:
- "Allow File Upload" = local file, cloud storage uploads
- "Allow Web Upload" = URL, YouTube, web content uploads
Using user.id as client_id causes WebSocket deadlocks when the same
user generates images concurrently (e.g., multi-model chat). ComfyUI
routes messages by clientId, so shared IDs mean only one connection
receives the completion — others hang forever.
Generate a unique UUID per request, matching ComfyUI's own examples.
Ollama recently added Responses API support via its OpenAI-compatible
endpoint (/v1/responses). This adds a proxy endpoint to the Ollama
router that forwards requests to Ollama's /v1/responses, applying
the same model resolution, access control, and prefix_id handling
used by the existing /v1/chat/completions and /v1/messages proxies.
Uses a typed ResponsesForm Pydantic model with required model field
and extra='allow' for forward compatibility, consistent with other
endpoint schemas in the file.
This allows API consumers (Codex, Claude Code, etc.) to use the
Responses API directly with Ollama-hosted models without requiring
a separate OpenAI-compatible connection.
Azure offers two URL formats: the legacy deployment-based format
(/openai/deployments/{model}/...) and the newer v1 format
(/openai/v1/...) where the model stays in the payload body and no
api-version query parameter is needed.
Previously, the code always ran convert_to_azure_payload which
rewrites the URL to the deployment format, causing 404 errors for
users with v1-style base URLs. Now, when the base URL contains
'/openai/v1', we skip deployment URL construction and route
directly.
Applied consistently across all three Azure routing paths:
generate_chat_completion, /responses proxy, and generic proxy.
The built-in search_web tool hardcoded count=5 as the default,
ignoring the admin-configured WEB_SEARCH_RESULT_COUNT setting.
When the LLM did not specify a count, the tool always returned 5
results regardless of admin configuration.
Now the tool defaults to the admin-configured value when the LLM
omits the count parameter, while still capping LLM-requested
values at the admin maximum to prevent abuse.
Closes#23485