Repository navigation
Description
Summary
Two privileges assume that "admin of any workspace" is a trustworthy, global role. But any signed-in user can create a workspace and become its admin (accounts/services.py create_workspace). So both privileges are open to every user.
Found during the system audit in #76; not changed there because both need a product decision.
1. Workspace admin becomes a global Open WebUI admin
chat/config.py identity() sends X-Studio-Role: admin for superusers and for anyone who is admin of at least one workspace.
- An Open WebUI admin can see every user's chats, models and settings, and can run Python through Functions/Tools.
- Any user can therefore: create a workspace → become its admin → become an Open WebUI admin → read other users' chats and run code on the server.
- This is documented in
docs/chat.md(role table) and covered bytest_workspace_admin_is_chat_admin. - Studio's Knowledge/Tools admin embeds rely on Open WebUI admin rights.
- System audit: security, data-integrity and correctness fixes #76 already keeps Studio's own secrets out of the embedded Open WebUI process. Even so, Open WebUI runs as the same OS user in embedded/single-docker mode.
2. "Workspaces where I'm admin" connection visibility reaches every workspace's admins
The ADMINS visibility level is labelled "Workspaces where I'm admin". In practice it shares the connection, and its API key, with the admins of every workspace:
model_registry/services.pyvisible_connections_forandvisible_connection_ids_for;chat/access_sync.py, which grants every workspace's admin group.
So any user who creates a workspace can see and use other tenants' admin-shared connections, spending the owner's key in chat, the chat API passthrough and connection pings. This is encoded in test_admins_visibility_admins_of_any_ws_see_it, so it may be intended, but the label promises something narrower.
Related: models from connections shared into a workspace appear in the experiment picker, but create_audit_run rejects them ("Model is unavailable in this project"). The intended sharing rule decides which side should change.
Options to brainstorm
- Open WebUI role:
- map only Studio superusers to Open WebUI
admin; - keep workspace admins as Open WebUI
userand give the admin embeds another path (scoped Open WebUI groups or permissions, or proxy the needed admin APIs through Studio).
- map only Studio superusers to Open WebUI
- ADMINS visibility:
- limit it to workspaces where the connection's creator (or an admin of the owning workspace) is also admin;
- or rename the level to say what it does.
- Self-service workspaces:
- optionally restrict workspace creation (or the admin role it grants) on multi-tenant deployments;
- the registration toggle already blocks new sign-ups (UI, API and WorkOS since System audit: security, data-integrity and correctness fixes #76), but not workspace creation by existing users.
- Shared models at launch: once sharing is settled, either allow shared-connection models in
create_audit_runor hide them from the picker.
Affected
chat/config.py,chat/access_sync.py,docs/chat.mdmodel_registry/services.py,audits/services.pyaccounts/services.py
Activity
avalyset commentedon Nov 4, 2026
Some input on P2 and I3/I4 (deferred from #76), read against main at 243747b.
Open WebUI role. Open WebUI's permissions docs say to "treat the Tools Access permission as root-equivalent access", because Tools and Functions run Python on the host. Scoped groups or permissions can therefore carry the Knowledge embed, but not the Tools embed: any route that lets a workspace admin manage Tools also gives them code execution. Tools would have to stay with superusers, whichever option is chosen for the rest.
For Knowledge, part of this already exists.
chat/access_sync.py:145creates a "— Admins" group per workspace, and Open WebUI group permissions are additive on top of the global defaults, so Knowledge Access could be granted to those groups with the defaults left off. The sync currently grants only read access to models (_connection_grants,access_sync.py:102–125), so the embed would also need knowledge-base grants to the workspace groups.The role header is applied at sign-in (
docs/chat.md, role mapping; Open WebUI writes the trusted role to the user row in its sign-in handler). Accounts that were already promoted keepadminin Open WebUI until their next sign-in, and any Tools or Functions they created remain. Changing the mapping alone does not cover them.ADMINS visibility.
ModelConnection.created_byexists (model_registry/models.py:46), so "workspaces where the creator is admin" is one extra condition atmodel_registry/services.py:302and:341, with the matching grant ataccess_sync.py:112–114.created_byisSET_NULL, so under that condition a deleted creator would leave the connection owner-only.shared_with(models.py:41) already covers explicit shares to specific workspaces. If the level is renamed instead, the label appears in two places:models.py:23andconnection_share_labelatservices.py:373.avalyset commentedon Nov 6, 2026
The extensions from #83 (v0.5.46, ba55a23) use the same rule this issue describes, so a user who creates their own workspace gets admin level there too:
user_level()takes the highest role in any workspace (infra/extensions.py:188-189), andcreate_workspace()makes the creator admin (accounts/services.py:177-200).With a manifest configured, that user sees admin-only menu entries (
infra/context_processors.py:200), can open/x/<slug>/(infra/extension_views.py:27), and gets200from/ext/authz?role=admin(infra/extension_views.py:57) withX-Studio-Role: admin(chat/config.py:203,:221). Self-registration is on by default (accounts/services.py:482), so this needs no invitation.Two possible directions:
admin/superuserlevels fromis_staff/is_superuserinstead of workspace membership.Project.created_by,accounts/models.py:60) inuser_level(). The creator can still add a second account as admin (accounts/views.py:299,add_member), so on its own this closes the one-account path but not the two-account one.Whatever is settled here for "admin of any workspace" may also cover the extension ladder, which is a separate copy of the rule.
Reproduction:
infra/tests/test_extensions_self_admin.py, 3 of 3 fail on ba55a23With
infra/tests/test_extensions.py:3 failed, 12 passed. The check before the workspace is created passes; every check after it fails with: