From 5835cd93cf0da2fd0b13c69715708812602047de Mon Sep 17 00:00:00 2001 From: DrMelone <27028174+Classic298@users.noreply.github.com> Date: Mon, 13 Apr 2026 00:58:29 +0200 Subject: [PATCH] fix: isolate admin-mode state, harden deepMerge, disable stale defaults cache - 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. --- backend/open_webui/routers/configs.py | 13 ++++-- .../components/chat/Settings/Interface.svelte | 11 ++++- src/lib/utils/index.ts | 41 +++++++++++-------- 3 files changed, 42 insertions(+), 23 deletions(-) diff --git a/backend/open_webui/routers/configs.py b/backend/open_webui/routers/configs.py index fd2ad14602..5512dba5ed 100644 --- a/backend/open_webui/routers/configs.py +++ b/backend/open_webui/routers/configs.py @@ -713,16 +713,21 @@ async def get_interface_defaults(user=Depends(get_verified_user)): """ Get global interface defaults for all users. Returns empty dict if no defaults are configured. - Cached for 10 minutes to reduce redundant fetches. + + Served with a no-store cache policy: this endpoint is fetched on + authenticated app load and merged into runtime settings, so caching + here would leave users seeing stale defaults for up to the cache TTL + after an admin 'save defaults' or 'reset all users' action. The call + is lightweight (in-memory config read), so the extra hit is cheap. """ from fastapi.responses import JSONResponse - + config = await asyncio.to_thread(get_config) defaults = config.get("ui", {}).get("interface_defaults", {}) - + return JSONResponse( content=defaults, - headers={"Cache-Control": "private, max-age=600"} + headers={"Cache-Control": "no-store"}, ) diff --git a/src/lib/components/chat/Settings/Interface.svelte b/src/lib/components/chat/Settings/Interface.svelte index 6c144db2d6..c1238d28aa 100644 --- a/src/lib/components/chat/Settings/Interface.svelte +++ b/src/lib/components/chat/Settings/Interface.svelte @@ -24,6 +24,13 @@ // In admin mode, we don't apply CSS side effects like text scale $: isAdminMode = initialSettings !== null; + // Reading $settings directly mixes the current admin's personal settings + // into composition (e.g. ...$settings.title) and render branches (e.g. + // {#if !$settings.chatBubble}) when this component is reused by the + // defaults modal. activeSettings resolves to initialSettings in admin + // mode and $settings in the normal user mode, so neither path leaks. + $: activeSettings = isAdminMode ? (initialSettings ?? {}) : $settings; + let backgroundImageUrl = null; let inputFiles = null; let filesInputElement; @@ -135,7 +142,7 @@ const toggleTitleAutoGenerate = async () => { saveSettings({ title: { - ...$settings.title, + ...(activeSettings?.title ?? {}), auto: titleAutoGenerate } }); @@ -716,7 +723,7 @@ - {#if !$settings.chatBubble} + {#if !chatBubble}