Medium severity6.5GHSA Advisory· Published May 15, 2026
CVE-2026-45667
CVE-2026-45667
Description
Open WebUI is a self-hosted artificial intelligence platform designed to operate entirely offline. Prior to 0.8.0, GET /api/v1/memories/ef is accessible without authentication and executes request.app.state.EMBEDDING_FUNCTION(...). This allows any unauthenticated caller to trigger embedding generation which can lead to direct cost exposure if a paid provider is used. This vulnerability is fixed in 0.8.0.
Affected products
1- Range: <= 0.7.2
Patches
16 files changed · +99 −25
backend/open_webui/routers/memories.py+1 −6 modified@@ -18,11 +18,6 @@ router = APIRouter() -@router.get("/ef") -async def get_embeddings(request: Request): - return {"result": await request.app.state.EMBEDDING_FUNCTION("hello world")} - - ############################ # GetMemories ############################ @@ -165,7 +160,7 @@ async def reset_memory_from_vector_db( user=Depends(get_verified_user), ): """Reset user's memory vector embeddings. - + CRITICAL: We intentionally do NOT use Depends(get_session) here. This endpoint generates embeddings for ALL user memories in parallel using asyncio.gather(). A user with 100 memories would trigger 100 embedding API
backend/open_webui/utils/middleware.py+3 −1 modified@@ -2099,9 +2099,10 @@ async def process_chat_payload(request, form_data, user, metadata, model): # Skills: inject manifest only — model uses view_skill tool to load full content on-demand user_skill_ids = form_data.pop("skill_ids", None) or [] - model_skill_ids = model.get("info", {}).get("meta", {}).get("skills", []) + model_skill_ids = model.get("info", {}).get("meta", {}).get("skillIds", []) all_skill_ids = list(set(user_skill_ids + model_skill_ids)) + available_skills = [] if all_skill_ids: from open_webui.models.skills import Skills as SkillsModel @@ -2348,6 +2349,7 @@ async def tool_function(**kwargs): { **extra_params, "__event_emitter__": event_emitter, + "__skill_ids__": [s.id for s in available_skills], }, features, model,
backend/open_webui/utils/tools.py+1 −1 modified@@ -508,7 +508,7 @@ def is_builtin_tool_enabled(category: str) -> bool: ) # Skills tools - view_skill allows model to load full skill instructions on demand - if is_builtin_tool_enabled("skills"): + if extra_params.get("__skill_ids__"): builtin_functions.append(view_skill) for func in builtin_functions:
src/lib/components/workspace/Models/ModelEditor.svelte+15 −0 modified@@ -12,6 +12,7 @@ import Tags from '$lib/components/common/Tags.svelte'; import Knowledge from '$lib/components/workspace/Models/Knowledge.svelte'; import ToolsSelector from '$lib/components/workspace/Models/ToolsSelector.svelte'; + import SkillsSelector from '$lib/components/workspace/Models/SkillsSelector.svelte'; import FiltersSelector from '$lib/components/workspace/Models/FiltersSelector.svelte'; import ActionsSelector from '$lib/components/workspace/Models/ActionsSelector.svelte'; import Capabilities from '$lib/components/workspace/Models/Capabilities.svelte'; @@ -89,6 +90,7 @@ let knowledge = []; let toolIds = []; + let skillIds = []; let filterIds = []; let defaultFilterIds = []; @@ -166,6 +168,14 @@ } } + if (skillIds.length > 0) { + info.meta.skillIds = skillIds; + } else { + if (info.meta.skillIds) { + delete info.meta.skillIds; + } + } + if (filterIds.length > 0) { info.meta.filterIds = filterIds; } else { @@ -293,6 +303,7 @@ }); toolIds = model?.meta?.toolIds ?? []; + skillIds = model?.meta?.skillIds ?? []; filterIds = model?.meta?.filterIds ?? []; defaultFilterIds = model?.meta?.defaultFilterIds ?? []; actionIds = model?.meta?.actionIds ?? []; @@ -736,6 +747,10 @@ <ToolsSelector bind:selectedToolIds={toolIds} tools={$tools ?? []} /> </div> + <div class="my-4"> + <SkillsSelector bind:selectedSkillIds={skillIds} /> + </div> + {#if ($functions ?? []).filter((func) => func.type === 'filter').length > 0 || ($functions ?? []).filter((func) => func.type === 'action').length > 0} <hr class=" border-gray-100/30 dark:border-gray-850/30 my-4" />
src/lib/components/workspace/Models/SkillsSelector.svelte+62 −0 added@@ -0,0 +1,62 @@ +<script lang="ts"> + import Checkbox from '$lib/components/common/Checkbox.svelte'; + import Tooltip from '$lib/components/common/Tooltip.svelte'; + import { getContext, onMount } from 'svelte'; + + import { getSkillItems } from '$lib/apis/skills'; + + export let selectedSkillIds: string[] = []; + + let _skills: Record<string, any> = {}; + + const i18n = getContext('i18n'); + + onMount(async () => { + const res = await getSkillItems(localStorage.token).catch(() => null); + const skills = res?.items ?? []; + _skills = skills.reduce((acc: Record<string, any>, skill: any) => { + acc[skill.id] = { + ...skill, + selected: selectedSkillIds.includes(skill.id) + }; + + return acc; + }, {}); + }); +</script> + +<div> + <div class="flex w-full justify-between mb-1"> + <div class=" self-center text-xs font-medium text-gray-500">{$i18n.t('Skills')}</div> + </div> + + <div class="flex flex-col mb-1"> + {#if Object.keys(_skills).length > 0} + <div class=" flex items-center flex-wrap"> + {#each Object.keys(_skills) as skill, skillIdx} + <div class=" flex items-center gap-2 mr-3"> + <div class="self-center flex items-center"> + <Checkbox + state={_skills[skill].selected ? 'checked' : 'unchecked'} + on:change={(e) => { + _skills[skill].selected = e.detail === 'checked'; + selectedSkillIds = Object.keys(_skills).filter((s) => _skills[s].selected); + }} + /> + </div> + + <Tooltip content={_skills[skill]?.description ?? _skills[skill].id}> + <div class=" py-0.5 text-sm w-full capitalize font-medium"> + {_skills[skill].name} + </div> + </Tooltip> + </div> + {/each} + </div> + {/if} + </div> + + <div class=" text-xs dark:text-gray-700"> + {$i18n.t('To select skills here, add them to the "Skills" workspace first.')} + </div> +</div>
src/lib/components/workspace/Skills.svelte+17 −17 modified@@ -54,22 +54,7 @@ let viewOption = ''; let page = 1; - // Debounce only query changes - $: if (query !== undefined) { - loading = true; - clearTimeout(searchDebounceTimer); - searchDebounceTimer = setTimeout(() => { - page = 1; - getSkillItems(); - }, 300); - } - - // Immediate response to page/filter changes - $: if (page && viewOption !== undefined) { - getSkillItems(); - } - - const getSkillItems = async () => { + const loadSkillItems = async () => { if (!loaded) return; loading = true; @@ -95,6 +80,21 @@ } }; + // Debounce only query changes + $: if (query !== undefined) { + loading = true; + clearTimeout(searchDebounceTimer); + searchDebounceTimer = setTimeout(() => { + page = 1; + loadSkillItems(); + }, 300); + } + + // Immediate response to page/filter changes + $: if (page && viewOption !== undefined) { + loadSkillItems(); + } + const cloneHandler = async (skill) => { const _skill = await getSkillById(localStorage.token, skill.id).catch((error) => { toast.error(`${error}`); @@ -136,7 +136,7 @@ } page = 1; - getSkillItems(); + loadSkillItems(); await _skills.set(await getSkills(localStorage.token)); };
Vulnerability mechanics
AI mechanics synthesis has not run for this CVE yet.
References
4News mentions
0No linked articles in our index yet.