* feat: add support for Valkey vector database
Signed-off-by: Riley Des <riley.desserre@improving.com>
* feat: add CLIENT SETNAME to Valkey vector store connections
Set client_name on GlideClientConfiguration for both the main client
and batch client so connections are identifiable in CLIENT LIST,
monitoring dashboards, and CloudWatch metrics.
Signed-off-by: Riley Des <riley.desserre@improving.com>
---------
Signed-off-by: Riley Des <riley.desserre@improving.com>
The history diff, delete, and version-restore routes authorize the URL
prompt_id but then act on a caller-supplied history/version id without
checking it belongs to that prompt (IDOR). Filter by prompt_id in
compute_diff and delete_history_entry, and reject a cross-prompt version_id
in update_prompt_version.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Firecrawl /search returns either `{"data": [...]}` (flat list — v1, and
what frost19k reported on #23966) or `{"data": {"web": [...]}}` (v2,
current production). The parser only handled the dict shape:
data = response.get('data') or {}
results = data.get('web') or []
On a list-shape response, `data.get('web')` raised AttributeError,
caught by the function's outer try/except, and `search_firecrawl`
silently returned []. Web search worked against v2 endpoints but is one
upstream-format-change away from failing closed again. Accept either.
The native function-calling tool resolver in utils/tools.py applies five
gates before exposing execute_code as a builtin tool: builtin-category
enable, ENABLE_CODE_INTERPRETER global config, model capability,
features.code_interpreter request flag, and the per-user
features.code_interpreter permission.
The legacy XML-tag detection path in streaming_chat_response_handler
applied only the request-flag gate. Brings the legacy path to parity by
running the same five-gate check before activating tag detection.
Behaviour change is limited to deployments that previously relied on
the asymmetry — admins who set ENABLE_CODE_INTERPRETER=False or revoked
the per-user permission, on the legacy tool-calling mode, with the
client supplying features.code_interpreter=true. Any of those three
conditions met now correctly disables tag detection.
Co-authored-by: sfwani <sfwani@users.noreply.github.com>
Open WebUI's collection ACL accepted any unknown name as a
legacy/ephemeral collection. In Milvus multi-tenancy mode that name
becomes the `resource_id` and is interpolated unescaped into a SQL-like
Milvus expression — `resource_id == '<name>'` — so a name like
x' or resource_id != '' or resource_id == 'x
turns the filter into a tautology and returns every tenant's chunks
from the shared collection.
All collection names Open WebUI generates are UUIDs, SHA-256 hex
digests, or fixed-prefix variants of those — they all fit
[A-Za-z0-9_-]. Add a strict format check in
filter_accessible_collections (utils.py) that drops any name outside
that set before any ACL or vector-store lookup, applied even on the
admin bypass path. _validate_collection_access then surfaces the dropped
name as a 403.
As defense in depth, MilvusClient now validates resource_id at every
expression-construction site and escapes single quotes / backslashes in
any other string interpolated into a filter (delete ids, metadata
filter values). Non-string filter values are typed-checked instead of
str()-formatted.
Co-authored-by: Claude <noreply@anthropic.com>
update_chat_by_id re-derived assistant content from `output` on every
save (serialize_output) so frontend edits to output items reflect in
content. But it ran unconditionally, so content set independently of
output — an __event_emitter__ {"type":"replace"} from an Action, or an
outlet filter footer — was reverted to the original output-derived text
on the next save. The reload reads chat.chat directly, so the change
vanished after navigating away (regression vs 0.9.2, which predates the
output mechanism).
Re-derive only when the message's `output` actually changed versus what's
stored, which still reflects genuine output edits but leaves
independently-set content intact.
Fixes#24585
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Symptom
On a fresh install (zero users) the frontend shows the mandatory
"Create Admin Account" onboarding screen, but POST
/api/v1/auths/signup returns 403 ACCESS_PROHIBITED ("You do not have
permission to access this resource."). Wiping the database does not
help when the config layer is backed by Redis (the value survives in
the Redis/valkey volume), or when only the user table is cleared (the
config row survives in Postgres). The instance is then unrecoverable
through the UI.
Root cause
signup_handler() auto-sets request.app.state.config.ENABLE_SIGNUP =
False immediately after the first admin is created. That value is
persisted by the config layer (the Postgres config table, and Redis
when REDIS_URL is set). On a later zero-user database the persisted
False is read back, so ENABLE_SIGNUP resolves False even though no
users exist. The old gate was:
if WEBUI_AUTH:
if not ENABLE_SIGNUP or not ENABLE_LOGIN_FORM:
if has_users or not ENABLE_INITIAL_ADMIN_SIGNUP:
403
ENABLE_INITIAL_ADMIN_SIGNUP defaults to False, so with zero users the
inner test (has_users or not ENABLE_INITIAL_ADMIN_SIGNUP) is True, and
a stale ENABLE_SIGNUP=False trips the outer test, producing a 403 on
the only UI path that can create the first admin. The frontend decides
to show onboarding purely from user_count == 0, so frontend and
backend disagree and the instance bricks.
Change
Split the gate by has_users. Subsequent signups (has_users True) are
unchanged: still gated by ENABLE_SIGNUP and ENABLE_LOGIN_FORM. The
first user (has_users False, the bootstrap admin the onboarding screen
invites) is gated only by the admin-chosen ENABLE_LOGIN_FORM (the
documented SSO-only hard-disable) unless ENABLE_INITIAL_ADMIN_SIGNUP is
set. It is no longer gated by ENABLE_SIGNUP, which in the zero-user
state is never an admin decision but the post-first-admin auto-disable
leaking across a database reset.
Why this is safe (full case analysis)
For WEBUI_AUTH the gate has 16 input combinations over (has_users,
ENABLE_SIGNUP, ENABLE_LOGIN_FORM, ENABLE_INITIAL_ADMIN_SIGNUP). Old and
new are identical in 15 of them:
* All 8 has_users=True cases: both reduce to "403 iff not
ENABLE_SIGNUP or not ENABLE_LOGIN_FORM". Unchanged.
* 7 of the 8 has_users=False cases: identical.
The only changed case is has_users=False, ENABLE_SIGNUP=False,
ENABLE_LOGIN_FORM=True, ENABLE_INITIAL_ADMIN_SIGNUP=False: old
behaviour 403, new behaviour allow. The new condition is a strict
subset of the old (new-403 implies old-403), so the change never newly
blocks any request that previously succeeded; it only stops blocking
that one bootstrap state.
That state has no legitimate deployment. With the login form enabled
and zero users the onboarding form is already served, and the only
operator-configurable way to keep the first signup closed (SSO-only:
ENABLE_LOGIN_FORM=False, optionally with ENABLE_INITIAL_ADMIN_SIGNUP)
is preserved byte for byte. ENABLE_SIGNUP=False with zero users is not
an operator choice, it is the automatic post-first-admin disable, so
the old behaviour there was purely a brick with no recovery path. No
security control is weakened: ENABLE_LOGIN_FORM and
ENABLE_INITIAL_ADMIN_SIGNUP keep their exact meaning, and the
WEBUI_AUTH=False path is untouched.
This is not Redis-specific: it reproduces with Redis disabled through
the Postgres config table alone (clear the user table, keep the config
row).
Verification
Drove the real signup endpoint across a 10-case matrix on freshly
migrated databases, including the full end-to-end first-admin creation
(returns role=admin, row persisted as admin) and the preserved
SSO-only, subsequent-signup and no-auth behaviours. All pass.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
aiohttp's ClientResponse.json() validates the Content-Type header against
'application/json' by default and raises ContentTypeError for any other
value — including 'application/x-ndjson', which Ollama returns for its
OpenAI-compatible /v1/images/generations endpoint.
Pass content_type=None to skip this check while keeping all other parsing
behaviour unchanged. The fix covers image generation (openai, gemini,
automatic1111 engines) and image editing (openai, gemini engines).
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>
The proxy gathered _client_to_upstream and _upstream_to_client with
return_exceptions=True. When upstream sends a graceful CLOSE,
_upstream_to_client returns but gather keeps waiting on
_client_to_upstream, which is blocked in ws.receive() until the browser
disconnects. The handler stays pending and the finally: session.close()
cleanup is deferred, leaking a ClientSession and an open browser socket.
Use asyncio.wait(return_when=FIRST_COMPLETED) and cancel the pending
sibling, so the proxy unwinds as soon as either direction finishes. The
pumps' bare except Exception already lets CancelledError (a BaseException)
propagate, so cancellation is clean and they need no change.
Fixes#25464
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: cap profile image data URI size to bound model/avatar bloat
validate_profile_image_url() validated data-URI format (MIME allowlist,
SVG rejection, scheme checks) but never its length, so a valid
data:image/...;base64,<huge> passed for both custom-model icons and user
avatars. Large inline images bloat Postgres and the Redis MODELS hash and
degrade model-list latency.
Add PROFILE_IMAGE_MAX_DATA_URI_SIZE (default 256 KiB, 0 disables) and
reject oversized data URIs in the shared validator, so both model meta
(ModelMeta.profile_image_url) and user avatars (UpdateProfileForm) are
bounded at one chokepoint. ModelMeta already clears invalid values to
None on read, so existing oversized icons stop propagating into the
MODELS hash on the next refresh.
Fixes#25468
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: default PROFILE_IMAGE_MAX_DATA_URI_SIZE to None (no cap)
Per review: opt-in rather than a 256 KiB default. Unset leaves data URIs
uncapped; the validator already skips the check on a falsy value.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
getChatEventEmitter starts a setInterval emitting a `usage` socket event
every second; clearInterval ran only after sendMessageSocket resolved, so
any throw/reject left the interval firing for the page lifetime. Each
failed send added another orphaned interval, inflating server-side usage
accounting and growing CPU/memory over a session.
Wrap the send in try/finally so the interval is always cleared, on both
the happy path and any thrown/rejected path. The exception still
propagates unchanged.
Fixes#25465
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
_sanitize_proxy_path decoded the proxy path once before the '..' check, so a double-encoded payload (%252e%252e) survived the check as %2e%2e and was then re-decoded into '..' by the upstream terminal server, defeating the traversal guard. Decode until stable so no encoded traversal sequence can reach the upstream. Single-encoded payloads were already rejected; this closes the double (and deeper) encoding bypass.
Co-authored-by: sermikr0 <230672901+sermikr0@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add missing custom={true} prop to AdvancedParams in General.svelte and
ModelSettingsModal.svelte so the 'Add Custom Parameter' option appears
consistently across all advanced parameter surfaces.
Also forward custom_params in the General.svelte save handler so custom
parameters are persisted instead of silently dropped on save.
Both presence_penalty and repeat_penalty in the saveHandler read from
params.frequency_penalty instead of their own values due to a
copy-paste error. This causes users adjusting either parameter to
silently save the frequency_penalty value instead.
Thread.svelte: Add null check for messagesContainerElement in scrollToBottom()
to match the existing pattern in Channel.svelte. Prevents potential TypeError
when the DOM element is not yet bound during rapid thread switches.
PinnedMessagesModal.svelte: Move res.length check inside the if (res) block.
Previously, res.length was accessed unconditionally after a guarded block,
causing TypeError when the API call fails and the .catch() returns null.
The SVG-XSS hardening introduced in f5f4b5895 correctly rejects
data:image/svg+xml URIs on new input, but also caused a
pydantic_core.ValidationError when reading pre-existing models from
the database that had SVG data URIs stored as their profile images.
This ValidationError propagated unhandled through _to_model_model and
get_all_models, crashing the entire /api/models endpoint with HTTP 500
and leaving users with no models available in the UI.
Fix:
- Wrap validate_profile_image_url() in a try/except ValueError inside
ModelMeta.check_profile_image_url. Legacy entries are cleared to None
with a warning log instead of raising — the /model/profile/image API
endpoint already falls back to /static/favicon.png when the value is
empty.
- Default ModelMeta.profile_image_url to None instead of hardcoding
/static/favicon.png, since the serving endpoint handles the fallback.
- Add a per-model try/except in ModelsTable.get_all_models so that any
future unexpected validation failure on a single record skips that
model with an error log rather than aborting the entire list.
bypass_system_prompt is an internal flag used by utils/middleware.py and utils/chat.py to skip applying the model system prompt on recursive base-model calls, but it was still declared as a positional argument on the openai/ollama chat-completion route handlers, so FastAPI bound it from the query string. Move it to request.state so external clients cannot set it, matching how bypass_filter is handled.
Drop the argument from both route signatures and read getattr(request.state, 'bypass_system_prompt', False); utils/chat.py sets request.state.bypass_system_prompt alongside bypass_filter and drops the kwarg from the two route-handler calls (the recursive self-calls keep it). Mirrors c0385f60b.
Co-authored-by: anishgirianish <161533316+anishgirianish@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The 't0p-s3cr3t' default was dead code on every supported startup path:
start.sh, start_windows.bat and `open-webui serve` all set or
auto-generate WEBUI_SECRET_KEY before the backend imports env.py. It was
only ever reachable by invoking uvicorn directly, which is unsupported
and unsafe (the app would then sign tokens/cookies with a public,
hardcoded key). It also keeps getting reported as a vulnerability because
it looks dangerous, even though it is unreachable in practice.
Drop the fallback (default to '') so an unset key is caught by the
existing WEBUI_AUTH guard, and replace the vague error with a clear,
actionable message explaining that the key is a hard requirement and how
the supported start methods provide it. Exit cleanly via SystemExit
instead of raising a ValueError traceback.
WEBUI_AUTH=False keeps working unchanged (key defaults to '').
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>