Skip to content

Commit 9bd8425

Browse files
authored
Merge pull request open-webui#23120 from open-webui/dev
0.8.12
2 parents 4d058a1 + 66c9bf5 commit 9bd8425

26 files changed

Lines changed: 461 additions & 234 deletions

File tree

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,22 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.8.12] - 2026-03-26
9+
10+
### Added
11+
12+
- 🌐 **Translation updates.** Translations for Simplified Chinese, Catalan, Portuguese (Brazil), Finnish, and Lithuanian were enhanced and expanded.
13+
14+
### Fixed
15+
16+
- 🔒 **Terminal server connection security.** Terminal server verification and policy saving now proxy through the backend, preventing API key exposure and CORS errors when connecting to in-cluster services. [Commit](https://github.com/open-webui/open-webui/commit/a6413257079a52fa4487eda36543f3955d0fbd53), [Commit](https://github.com/open-webui/open-webui/commit/4567cdc0d9cb7b42b6eba7b676c0ced3f4850d31)
17+
- 🛠️ **Terminal tools exception handling.** Exceptions in middleware.py due to invalid return values from get_terminal_tools() have been resolved. [Commit](https://github.com/open-webui/open-webui/commit/52a06bd48aff34fb2211aac2879f0cd028129267)
18+
- 📦 **Missing beautifulsoup4 dependency.** Users can now start Open WebUI using uvx without encountering the "bs4 module missing" error. [Commit](https://github.com/open-webui/open-webui/commit/1994d65306bbcc7406584e1bfef82f5d353fc91c)
19+
- 🔌 **API files list error.** The /api/v1/files/ endpoint no longer returns a 500 error, fixing a regression that prevented file listing via the API. [Commit](https://github.com/open-webui/open-webui/commit/11f52921dc21c2dc61c03f12bcdf6f19140a350c)
20+
- 📜 **License data loading.** License data now loads correctly, displaying the expected color and logo in the interface. [Commit](https://github.com/open-webui/open-webui/commit/16335f866ea4cedf00c4971963622fcc1fe02d82)
21+
- 👑 **Admin model visibility.** Administrators can now see models even when no access control is configured yet, allowing them to manage all available models. [Commit](https://github.com/open-webui/open-webui/commit/f3f8f9874f55282603c2650b91801640cb3f69cb)
22+
- 📊 **Tool call embed visibility.** Rich UI embeds from tool calls (like visualizations) are now rendered outside collapsed groups and remain visible without requiring manual expansion. [Commit](https://github.com/open-webui/open-webui/commit/4c872a8d128757d4a6f311fb86bc382af2ba5d0d), [Commit](https://github.com/open-webui/open-webui/commit/308fa924a5b2b7e08cd1e8f15b9c8c96e1de8f02)
23+
824
## [0.8.11] - 2026-03-25
925

1026
### Added

backend/open_webui/models/files.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ class FileModelResponse(BaseModel):
8787

8888
filename: str
8989
data: Optional[dict] = None
90-
meta: FileMeta
90+
meta: Optional[FileMeta] = None
9191

9292
created_at: int # timestamp in epoch
9393
updated_at: Optional[int] = None # timestamp in epoch, optional for legacy files
@@ -246,7 +246,7 @@ def get_file_list(
246246
total = query.count()
247247

248248
items = [
249-
FileModel.model_validate(file)
249+
FileModelResponse.model_validate(file, from_attributes=True)
250250
for file in query.order_by(File.updated_at.desc(), File.id.desc()).offset(skip).limit(limit).all()
251251
]
252252

backend/open_webui/retrieval/utils.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
from open_webui.models.chats import Chats
3131
from open_webui.models.notes import Notes
3232
from open_webui.models.access_grants import AccessGrants
33+
from open_webui.utils.access_control.files import has_access_to_file
3334

3435
from open_webui.retrieval.vector.main import GetResult
3536
from open_webui.utils.headers import include_user_info_headers
@@ -1042,7 +1043,11 @@ async def get_sources_from_items(
10421043
}
10431044
elif item.get('id'):
10441045
file_object = Files.get_file_by_id(item.get('id'))
1045-
if file_object:
1046+
if file_object and (
1047+
user.role == 'admin'
1048+
or file_object.user_id == user.id
1049+
or has_access_to_file(item.get('id'), 'read', user)
1050+
):
10461051
query_result = {
10471052
'documents': [[file_object.data.get('content', '')]],
10481053
'metadatas': [

backend/open_webui/routers/configs.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,92 @@ async def set_terminal_servers_config(
269269
}
270270

271271

272+
@router.post('/terminal_servers/verify')
273+
async def verify_terminal_server_connection(
274+
request: Request, form_data: TerminalServerConnection, user=Depends(get_admin_user)
275+
):
276+
"""
277+
Verify the connection to a terminal server by detecting its type.
278+
279+
Tries GET {url}/api/v1/policies (orchestrator) then GET {url}/api/config
280+
(plain terminal). Returns ``{status: true, type: "orchestrator"|"terminal"}``.
281+
"""
282+
base_url = (form_data.url or '').rstrip('/')
283+
if not base_url:
284+
raise HTTPException(status_code=400, detail='Terminal server URL is required')
285+
286+
headers = {}
287+
if form_data.auth_type == 'bearer' and form_data.key:
288+
headers['Authorization'] = f'Bearer {form_data.key}'
289+
290+
try:
291+
async with aiohttp.ClientSession(
292+
trust_env=True,
293+
timeout=aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT),
294+
) as session:
295+
# Orchestrators expose a policies API; plain terminals don't.
296+
try:
297+
async with session.get(f'{base_url}/api/v1/policies', headers=headers) as resp:
298+
if resp.ok:
299+
return {'status': True, 'type': 'orchestrator'}
300+
except Exception:
301+
pass
302+
303+
# Fall back to open-terminal config endpoint.
304+
try:
305+
async with session.get(f'{base_url}/api/config', headers=headers) as resp:
306+
if resp.ok:
307+
return {'status': True, 'type': 'terminal'}
308+
except Exception:
309+
pass
310+
311+
except Exception as e:
312+
log.debug(f'Failed to connect to the terminal server: {e}')
313+
314+
raise HTTPException(status_code=400, detail='Failed to connect to the terminal server')
315+
316+
317+
class TerminalServerPolicyForm(BaseModel):
318+
url: str
319+
key: Optional[str] = ''
320+
auth_type: Optional[str] = 'bearer'
321+
policy_id: str
322+
policy_data: dict
323+
324+
325+
@router.post('/terminal_servers/policy')
326+
async def put_terminal_server_policy(
327+
request: Request, form_data: TerminalServerPolicyForm, user=Depends(get_admin_user)
328+
):
329+
"""
330+
Proxy a policy PUT to an orchestrator terminal server.
331+
"""
332+
base_url = (form_data.url or '').rstrip('/')
333+
if not base_url:
334+
raise HTTPException(status_code=400, detail='Terminal server URL is required')
335+
336+
headers = {'Content-Type': 'application/json'}
337+
if form_data.auth_type == 'bearer' and form_data.key:
338+
headers['Authorization'] = f'Bearer {form_data.key}'
339+
340+
try:
341+
async with aiohttp.ClientSession(
342+
trust_env=True,
343+
timeout=aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT),
344+
) as session:
345+
policy_url = f'{base_url}/api/v1/policies/{form_data.policy_id}'
346+
async with session.put(policy_url, headers=headers, json=form_data.policy_data) as resp:
347+
if resp.ok:
348+
return await resp.json()
349+
detail = await resp.text()
350+
raise HTTPException(status_code=resp.status, detail=detail)
351+
except HTTPException:
352+
raise
353+
except Exception as e:
354+
log.debug(f'Failed to save policy to terminal server: {e}')
355+
raise HTTPException(status_code=400, detail='Failed to save policy to terminal server')
356+
357+
272358
@router.post('/tool_servers/verify')
273359
async def verify_tool_servers_config(request: Request, form_data: ToolServerConnection, user=Depends(get_admin_user)):
274360
"""

backend/open_webui/routers/utils.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@ async def format_code(form_data: CodeForm, user=Depends(get_admin_user)):
4242

4343
@router.post('/code/execute')
4444
async def execute_code(request: Request, form_data: CodeForm, user=Depends(get_verified_user)):
45+
if not request.app.state.config.ENABLE_CODE_EXECUTION:
46+
raise HTTPException(
47+
status_code=403,
48+
detail='Code execution is disabled',
49+
)
50+
4551
if request.app.state.config.CODE_EXECUTION_ENGINE == 'jupyter':
4652
output = await execute_code_jupyter(
4753
request.app.state.config.CODE_EXECUTION_JUPYTER_URL,

backend/open_webui/utils/auth.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,15 @@ def nt(b):
143143
pn, pt = nt(pb)
144144

145145
data = json.loads(aesgcm.decrypt(pn, pt, None).decode())
146-
if not data.get('exp') or data.get('exp') < datetime.now().date():
147-
return False
146+
147+
exp = data.get('exp')
148+
if exp:
149+
if isinstance(exp, str):
150+
from datetime import date
151+
152+
exp = date.fromisoformat(exp)
153+
if exp < datetime.now().date():
154+
return False
148155

149156
data_handler(data)
150157
return True

backend/open_webui/utils/middleware.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2607,12 +2607,17 @@ async def tool_function(**kwargs):
26072607
# so system terminals work even when no other tools are selected)
26082608
if terminal_id:
26092609
try:
2610-
terminal_tools, system_prompt = await get_terminal_tools(
2610+
terminal_result = await get_terminal_tools(
26112611
request,
26122612
terminal_id,
26132613
user,
26142614
extra_params,
26152615
)
2616+
if isinstance(terminal_result, tuple):
2617+
terminal_tools, system_prompt = terminal_result
2618+
else:
2619+
terminal_tools = terminal_result
2620+
system_prompt = None
26162621
if terminal_tools:
26172622
tools_dict = {**tools_dict, **terminal_tools}
26182623
if system_prompt:

backend/open_webui/utils/models.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,10 @@ def get_filtered_models(models, user, db=None):
452452
or model['id'] in accessible_model_ids
453453
):
454454
filtered_models.append(model)
455+
elif user.role == 'admin':
456+
# No DB entry means no access control configured yet;
457+
# only admins can see unconfigured models.
458+
filtered_models.append(model)
455459

456460
return filtered_models
457461
else:

backend/open_webui/utils/tools.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -956,7 +956,7 @@ async def get_terminal_tools(
956956
terminal_id: str,
957957
user: UserModel,
958958
extra_params: dict,
959-
) -> tuple[dict[str, dict], Optional[str]]:
959+
) -> dict[str, dict] | tuple[dict[str, dict], Optional[str]]:
960960
"""Resolve tools for a terminal server identified by terminal_id.
961961
962962
- Finds the connection in TERMINAL_SERVER_CONNECTIONS

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)