Share
## https://sploitus.com/exploit?id=22CFEBF4-738A-52AD-B1A9-E066D3D33C80
# CVE-2026-46558
Plane’s V2 asset subsystem trusted workspace slugs and asset UUIDs without enforcing the right membership checks, which let one authenticated user read, copy, delete, and overwrite assets in other workspaces.

## Intro

I found this issue while reviewing **Plane**, the open-source project management platform, with a very specific question in mind:

**Do the V2 asset endpoints actually enforce workspace boundaries, or do they trust attacker-supplied workspace slugs and asset IDs too far?**

In this case, the answer was no.

Plane’s V2 asset subsystem exposed two related authorization flaws that broke workspace isolation for any authenticated user:
- the workspace-level asset endpoint did not enforce target-workspace membership before asset operations
- the duplicate-assets flow authorized only the destination workspace and trusted the source asset UUID without checking source-workspace access

That made cross-workspace asset abuse possible.

In my validated PoC, one normal user in workspace Bravo was able to:
- download Alpha’s private uploaded asset
- duplicate that asset into Bravo’s own workspace
- delete Alpha’s original asset
- overwrite Alpha’s workspace logo with attacker-controlled content

That issue was later assigned **CVE-2026-46558**.

**Plane:** [Plane on GitHub](https://github.com/makeplane/plane)  
**CVE:** CVE-2026-46558

This affected **Plane**, which on its official site is presented as being used by **50,000+ teams across the globe**. Plane also highlights strong open-source adoption, including **46,000+ GitHub stars** and **1,000,000+ Docker pulls**, and showcases organizations such as **Tencent**, **Accenture**, **Microsoft**, and **Amazon**.



---

## Attack Chain

`authenticated attacker in workspace B → workspace-level V2 asset route trusts target workspace slug and asset UUID without proper membership checks → presigned read / patch / delete against workspace A assets + duplicate-assets source lookup trusts uploaded source UUID → cross-workspace disclosure, copying, deletion, and branding overwrite`

---

## What Plane Does

**Plane** is an open-source project management platform used to manage:
- tasks
- issues
- sprints
- docs
- triage
- workspace-level branding and assets

That means its asset subsystem sits on a real trust boundary.

The important question here was not whether Plane supports uploads.

The real question was:

**Does Plane enforce workspace isolation when one authenticated user references assets owned by another workspace?**

In this case, it did not.

---

## Why This Bug Was Worth Looking At

A lot of multi-tenant application reviews focus first on obvious admin endpoints or direct settings updates.

That misses a very common and very real bug class:

**secondary object access through shared file or asset subsystems**

Asset systems are easy to get wrong because they often combine:
- user-controlled identifiers
- storage-layer indirection
- metadata-driven object linking
- presigned URL generation
- multiple entity types behind a shared route

That is exactly the kind of place where tenant boundaries silently weaken.

This issue was not about storage corruption.
It was not about S3 itself.
It was not about upload MIME handling.

It was an **authorization boundary failure**:
- attacker-controlled identifiers crossed the boundary
- the server resolved cross-workspace objects
- authorization was incomplete or missing
- privileged asset actions still succeeded

That is enough to create a real vulnerability.

---

## The Boundary I Focused On

I did not approach Plane by blindly fuzzing random endpoints or guessing at UUIDs with no model.

The stronger approach was to identify the most promising isolation boundary first.

For Plane, that was the **V2 asset subsystem**.

Why?

Because a shared asset system becomes dangerous when:
- multiple workspaces exist
- uploaded objects are referenced by UUID
- workspace slugs are attacker-controlled route inputs
- the application later turns successful lookups into presigned download or mutation paths

That was the right boundary to inspect.

And it was exactly where the bug lived.

---

## Root Cause

This was really two related authorization failures in the same subsystem.

### Root cause 1: workspace asset routes missing membership enforcement

The workspace-level asset routes were exposed through:

- `apps/api/plane/app/urls/asset.py:50-56`

The vulnerable handlers were in:

- `apps/api/plane/app/views/asset/v2.py:314`
- `apps/api/plane/app/views/asset/v2.py:379`
- `apps/api/plane/app/views/asset/v2.py:400`
- `apps/api/plane/app/views/asset/v2.py:409`

The problem was simple.

`WorkspaceFileAssetEndpoint` accepted a workspace slug and asset UUID, then directly resolved objects like:

```python
workspace = Workspace.objects.get(slug=slug)
```

and:

```python
asset = FileAsset.objects.get(id=asset_id, workspace__slug=slug)
```

without first enforcing that the caller was actually an authorized member of that target workspace.

That meant the endpoint could still:
- create assets
- finalize assets
- delete assets
- return presigned download URLs

for another workspace’s objects.

### Root cause 2: duplicate-assets trusted the source asset UUID

The duplicate-assets route was mapped through:

- `apps/api/plane/app/urls/asset.py:100-101`

The vulnerable logic was in:

- `apps/api/plane/app/views/asset/v2.py:736-780`

The destination workspace had an authorization decorator.
But the source asset lookup did not.

The source object was loaded with:

```python
original_asset = FileAsset.objects.filter(id=asset_id, is_uploaded=True).first()
```

That meant the caller only needed:
- valid access to the destination workspace
- a source asset UUID that was uploaded

There was no check that the caller belonged to the source workspace that actually owned that asset.

That is the whole second bug.

---

## Why This Is a Security Issue, Not Just Bad Access Logic

The important distinction is cross-workspace impact.

A lot of authorization bugs get minimized as:
> “it still requires login”

That misses the point.

The real question is not:

> “Is the caller authenticated?”

The real question is:

> “Is the caller authorized for the specific workspace and specific asset being acted on?”

In Plane, that answer was no.

That turns what might look like ordinary object handling into a real multi-tenant security issue.

There is a clear difference between:
- authenticated access inside your own workspace
- and authenticated access that crosses another tenant’s boundary

This issue was firmly the second case.

---

## PoC

I validated the issue locally against **Plane Community Edition 1.2.3** using two ordinary users in two unrelated workspaces:

- Alpha in workspace `alpha-20260323072017`
- Bravo in workspace `bravo-20260323072017`

I used Alpha to create a legitimate private uploaded asset in a project issue.

The validated private asset ID in my run was:

```text
6ed6ed62-d1b2-4399-8220-336c01b7d72c
```

### Case 1: unauthorized read from another workspace

As Bravo, I requested:

```http
GET /api/assets/v2/workspaces/alpha-20260323072017/6ed6ed62-d1b2-4399-8220-336c01b7d72c/
```

Plane returned:

```http
HTTP/1.1 302 Found
```

with a presigned download URL for Alpha’s asset.

The downloaded file hash matched Alpha’s original private asset exactly:

```text
original:          0d4d070f550ba59ba6b30bee62343cf68ea221af7034f131f72f6409cf5a598e
unauthorized read: 0d4d070f550ba59ba6b30bee62343cf68ea221af7034f131f72f6409cf5a598e
```

That proved the read path crossed workspace boundaries successfully.

---

### Case 2: cross-workspace duplication through source UUID trust

As Bravo, I then requested:

```http
POST /api/assets/v2/workspaces/bravo-20260323072017/duplicate-assets/6ed6ed62-d1b2-4399-8220-336c01b7d72c/
```

Plane returned:

```http
HTTP/1.1 200 OK
```

and created a duplicated attacker-side asset:

```text
72d51497-ccc1-4546-ba14-28fae5d37dbb
```

The duplicated file’s SHA-256 matched Alpha’s original asset exactly:

```text
0d4d070f550ba59ba6b30bee62343cf68ea221af7034f131f72f6409cf5a598e
```

That proved the source asset UUID alone was enough to copy cross-workspace content into an attacker-controlled workspace.

---

### Case 3: unauthorized delete of victim asset

As Bravo, I then sent:

```http
DELETE /api/assets/v2/workspaces/alpha-20260323072017/6ed6ed62-d1b2-4399-8220-336c01b7d72c/
```

Plane returned:

```http
HTTP/1.1 204 No Content
```

When Alpha later fetched that asset, the server returned:

```http
HTTP/1.1 404 Not Found
```

That proved cross-workspace integrity impact, not just disclosure.

---

### Case 4: unauthorized workspace-logo overwrite

As Bravo, I created a `WORKSPACE_LOGO` asset against Alpha’s workspace through the vulnerable workspace-level asset route, uploaded attacker-controlled content, and finalized it.

After that, Alpha’s workspace metadata pointed to attacker-controlled logo asset:

```text
c1032f06-3cf5-4f7e-b139-e6976d8c567d
```

The downloaded final logo hash matched the attacker payload exactly:

```text
expected: b9d1ef1de88d61bf55dd18055839bacacb832a752e17a7312547f641113d0e7b
observed: b9d1ef1de88d61bf55dd18055839bacacb832a752e17a7312547f641113d0e7b
```

That proved a visible cross-workspace overwrite path, not just a hidden backend access issue.

---

## Why the Full Chain Matters

Any one of the above results would already have been enough to justify a real bug report.

But validating the full chain mattered for two reasons.

### First

It showed the issue was not limited to read-only exposure.

The same weak boundary enabled:
- disclosure
- copying
- deletion
- overwrite

That makes the impact much stronger than a narrow “can fetch one file” IDOR.

### Second

It showed the two code paths were related but independently important.

One flaw exposed workspace-level asset operations directly.
The second flaw turned uploaded asset UUIDs into a reusable exfiltration primitive through duplication.

That made the overall security story much harder to dismiss.

---

## Scope Validation

The most visible overwrite impact I validated was:

- `WORKSPACE_LOGO`

That was deliberate because it is easy to verify and demonstrates obvious cross-tenant integrity failure.

But the endpoint was not limited to workspace logos.

The vulnerable workspace-level asset flow also accepted multiple entity contexts, including:
- project covers
- user images
- issue content
- page content
- comment content

That mattered because it showed the bug was **structural**, not tied to a single branding field.

I validated the workspace-logo path directly.
The broader code path strongly suggested additional asset-backed contexts were exposed to the same authorization mistake.

---

## Severity and Classification

This issue was reasonably classified as **High**.

The advisory classification was:

- **CWE-862**: Missing Authorization
- **CWE-639**: Authorization Bypass Through User-Controlled Key
- **CVSS:** 
```text
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:L
```

That classification makes sense.

The claim is not that an unauthenticated attacker can compromise Plane from nothing.
The claim is that any normal authenticated user can cross tenant boundaries in the V2 asset subsystem and perform high-impact asset operations against other workspaces.

That is a real and defensible multi-tenant authorization vulnerability.

---

## Why This Was Still Worth Reporting

Some people underrate authenticated cross-tenant bugs because they hear:

> “the attacker already needed an account”

That is not a serious defense.

In multi-workspace software, normal authenticated users are supposed to be **contained** inside their own authorization scope.

If a low-privileged user in workspace Bravo can read, copy, delete, or overwrite objects in workspace Alpha, then workspace isolation is broken.

That is exactly the security property the application is supposed to protect.

Especially in a project-management platform that stores internal work content and branding assets, that is a meaningful issue with real confidentiality and integrity impact.

---

## Fix Analysis

The issue was fixed in **Plane v1.3.1**.

The release notes for `v1.3.1` described the fix clearly:

- add `@allow_permission` to all `WorkspaceFileAssetEndpoint` methods
- scope `DuplicateAssetEndpoint` source asset lookup to workspaces where the caller is an active member

That is the correct remediation direction because it addresses both failed security properties:

1. **workspace-level asset actions now require real membership enforcement**
2. **source assets in duplication flow are no longer trusted by UUID alone**

This is exactly what this bug needed.

A good fix here is not about hiding UUIDs better.
It is not about changing presigned URL generation.

It is about restoring the correct rule:

**workspace slug plus asset UUID must never be enough without authorization scoped to the current user**

That is the part the patch restored.

---

## Disclosure

This issue was reported privately through GitHub Security Advisories.

The report included:
- root cause analysis for both code paths
- a local end-to-end PoC
- raw HTTP evidence
- hash-based proof for unauthorized download, duplication, and overwrite
- remediation guidance

The issue was later published as:

- **GHSA-qw87-v5w3-6vxx**
- **CVE-2026-46558**

The advisory was published on **May 15, 2026**.
The fix shipped in **Plane v1.3.1**.

---

## What This Bug Actually Teaches

The key lesson here is simple:

> shared asset subsystems are authorization boundaries, not just storage helpers

A lot of developers think in terms of:
- upload succeeds
- object exists
- UUID resolves
- presigned URL works

Those things are implementation details.

The real security question is:

**who is allowed to resolve, mutate, copy, or relink that asset across tenant boundaries?**

In Plane, that boundary was not enforced consistently.

That is the real takeaway.

This bug also reinforces something important about reviewing multi-tenant applications:

- shared object layers deserve direct security review
- attacker-controlled identifiers are enough when authorization is incomplete
- a single subsystem can expose both confidentiality and integrity failures at once

---

## Key Points

- asset endpoints are real multi-tenant security boundaries
- authenticated access is not the same thing as authorized cross-workspace access
- workspace slugs and asset UUIDs should never be sufficient on their own
- presigned download generation becomes dangerous when upstream authorization is weak
- validating both read and write consequences makes an authorization report much stronger
- structural authorization bugs in shared asset systems often affect more than one entity type

---

## Final Words

This vulnerability was not about exotic storage behavior.

It was about asking the right trust-boundary question.

In Plane, one authenticated user could supply another workspace’s slug and asset UUIDs, and the V2 asset subsystem trusted those identifiers farther than it should have.

That is why this became **CVE-2026-46558**.

Fixed in **Plane v1.3.1**.