makeplane / makeplane/plane

[bug]: Workspace slug starting with `admin*` silently 403s the REST API even when user is workspace Admin

Open
#9,041 2 comments 0 reactions 0 assignees View on GitHub

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:

  1. Be rejected at create time with a clear error (consistent with
    the admin slug rejection), OR
  2. 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
  1. Create a workspace via the Plane UI with the slug
    administrative (UI display name can be anything).
  2. Confirm the creating user appears as Admin in
    Workspace Settings → Members.
  3. Generate a global API token (Workspace Settings → API Tokens
    → Add Token) for that user. Confirm GET /api/v1/users/me/
    returns 200 with the expected user identity.
  4. Call:
    curl -H "X-API-Key: $TOKEN" \
         https://<your-plane>/api/v1/workspaces/administrative/projects/
    
  5. Observe 403 Forbidden with
    {"detail":"You do not have permission to perform this action."}.
  6. Repeat against another workspace the same user owns whose slug
    does NOT start with admin — observe 200 OK.
  7. Rename the workspace's slug to one that does not start with
    admin (we used sphere-admin); observe the API immediately
    begins returning 200 OK with 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-web task:
    api + frontend + space + proxy + caddy; plane-worker task 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:

  1. Per-workspace token scoping — false. Plane CE issues tokens
    globally; one token cleanly authenticates against 5 of 6
    workspaces owned by the same user.
  2. Token snapshots workspace memberships at creation time
    false. Rotating the global token (revoke + create new from a
    different workspace) did not change the 403.
  3. The bare admin slug 403s purely because of routing
    collision
    — partially true: the routing/permission collision
    is real, but it affects ALL admin* slugs, not just admin.
    The slug-creation validator only rejects exact admin, leaving
    the door open to misleading 403s on administrative,
    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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.