[bug]: Workspace slug starting with `admin*` silently 403s the REST API even when user is workspace Admin
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 59.6k
- Forks
- 5.8k
- Avg merge
- 1d 22h
- Merged PRs (30d)
- 49
Description
Current behavior
When a workspace is created with any slug starting with admin
(e.g. admin, administrative, admin-team, adminx), the
self-hosted Plane CE REST API silently returns 403 Forbidden
with {"detail":"You do not have permission to perform this action."}
on workspace-scoped endpoints — even when the requesting user
holds the workspace Admin role and the same global API token
authenticates successfully against every other workspace owned
by the same user.
Concretely, given a workspace whose slug is administrative and a
user tyler@sphere.ai who appears in
Workspace Settings → Members as Admin (joined date set,
authentication = Email):
$ curl -H "X-API-Key: $TOKEN" \
https://pm.example.com/api/v1/workspaces/administrative/projects/
{"detail":"You do not have permission to perform this action."}
HTTP 403
$ curl -H "X-API-Key: $TOKEN" \
https://pm.example.com/api/v1/workspaces/research/projects/
HTTP 200 # token + user are valid; same membership shape
$ curl -H "X-API-Key: $TOKEN" \
https://pm.example.com/api/v1/workspaces/this-slug-does-not-exist/projects/
{"detail":"You do not have permission to perform this action."}
HTTP 403 # identical response shape — non-existent and admin* slugs collapse
The admin slug itself is rejected at workspace-creation time as
reserved (presumably because of a known collision with the god-mode
admin URL routes), but longer admin*-prefixed slugs slip through
the slug validator while still tripping the same routing/permission
collision at request time.
Expected behavior
A workspace whose slug starts with admin (other than the bare
reserved admin) should either:
- Be rejected at create time with a clear error (consistent with
theadminslug rejection), OR - Function normally for API access by workspace members, the same
as any other slug.
The current behavior — accepted at create time, then silently
403'd at every API request, with the same error shape returned
for non-existent workspaces — is hard to debug for downstream
operators.
Steps to reproduce
- Create a workspace via the Plane UI with the slug
administrative(UI display name can be anything). - Confirm the creating user appears as
Adminin
Workspace Settings → Members. - Generate a global API token (Workspace Settings → API Tokens
→ Add Token) for that user. ConfirmGET /api/v1/users/me/
returns 200 with the expected user identity. - Call:
curl -H "X-API-Key: $TOKEN" \ https://<your-plane>/api/v1/workspaces/administrative/projects/ - Observe
403 Forbiddenwith
{"detail":"You do not have permission to perform this action."}. - Repeat against another workspace the same user owns whose slug
does NOT start withadmin— observe200 OK. - Rename the workspace's slug to one that does not start with
admin(we usedsphere-admin); observe the API immediately
begins returning200 OKwith no other change.
Workaround
Rename the workspace slug to one that does not start with admin.
The UI display name is unaffected, so cosmetically the workspace
can still appear as "admin" in the sidebar. Sphere standardized on
sphere-admin for this reason.
Environment
- Plane CE:
1.3.0(/api/instances/→current_version) - Self-hosted on AWS ECS Fargate (5-container
plane-webtask:
api + frontend + space + proxy + caddy;plane-workertask with
celery worker + beat + RabbitMQ; Aurora Postgres + ElastiCache
Redis backing). - Single global API token issued via Workspace Settings → API
Tokens. Plane CE does not appear to expose per-workspace token
issuance UI (relevant because it ruled out one of our hypotheses
below).
Hypotheses we falsified during diagnosis
Sharing in case it saves the next operator the same walk:
- Per-workspace token scoping — false. Plane CE issues tokens
globally; one token cleanly authenticates against 5 of 6
workspaces owned by the same user. - Token snapshots workspace memberships at creation time —
false. Rotating the global token (revoke + create new from a
different workspace) did not change the 403. - The bare
adminslug 403s purely because of routing
collision — partially true: the routing/permission collision
is real, but it affects ALLadmin*slugs, not justadmin.
The slug-creation validator only rejects exactadmin, leaving
the door open to misleading 403s onadministrative,
admin-team, etc.
The DRF response body for the failing case is
{"detail":"You do not have permission to perform this action."},
which is identical to the response for non-existent workspaces and
for unauthenticated requests — making it ambiguous whether the
issue is auth, membership, slug, or routing.
Possible relation
Issue #8822 ("Work item advanced-search endpoint resolves as
identifier route and search param mismatch") looks like the same
class of routing-conflict bug at a different layer (endpoint-level
URL pattern overlap rather than workspace-slug-level). Both
collapse into the same misleading "You do not have permission"
error.
Suggested fix shape
Either (a) extend the slug validator to reject any slug starting
with admin, OR (b) ensure the workspace API URL pattern matcher
is tighter so admin*-slugged workspaces route to the
workspace-API handler rather than to whatever admin-internal
handler currently shadows them. Option (a) is the cheap fix;
option (b) is the principled fix and would presumably also help
issues like #8822.
Has the issue been searched
- I have searched the existing issues. The closest existing
issue is #8822 (different endpoint, plausibly same root cause).
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reproducing the 403 with an admin* workspace slug, then locate the workspace slug validator and workspace-scoped REST API URL patterns or DRF permission handler. Compare the behavior with a non-admin slug and decide whether validation or routing is the appropriate boundary; done means the affected slug either receives normal member access or is rejected clearly at creation, with regression coverage.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- django, python
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100