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:
That made cross-workspace asset abuse possible.
In my validated PoC, one normal user in workspace Bravo was able to:
That issue was later assigned CVE-2026-46558.
Plane: Plane on GitHub
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.
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
Plane is an open-source project management platform used to manage:
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.
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:
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:
That is enough to create a real vulnerability.
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:
That was the right boundary to inspect.
And it was exactly where the bug lived.
This was really two related authorization failures in the same subsystem.
The workspace-level asset routes were exposed through:
apps/api/plane/app/urls/asset.py:50-56The vulnerable handlers were in:
apps/api/plane/app/views/asset/v2.py:314apps/api/plane/app/views/asset/v2.py:379apps/api/plane/app/views/asset/v2.py:400apps/api/plane/app/views/asset/v2.py:409The problem was simple.
WorkspaceFileAssetEndpoint accepted a workspace slug and asset UUID, then directly resolved objects like:
and:
without first enforcing that the caller was actually an authorized member of that target workspace.
That meant the endpoint could still:
for another workspace’s objects.
The duplicate-assets route was mapped through:
apps/api/plane/app/urls/asset.py:100-101The vulnerable logic was in:
apps/api/plane/app/views/asset/v2.py:736-780The destination workspace had an authorization decorator. But the source asset lookup did not.
The source object was loaded with:
That meant the caller only needed:
There was no check that the caller belonged to the source workspace that actually owned that asset.
That is the whole second bug.
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:
This issue was firmly the second case.
I validated the issue locally against Plane Community Edition 1.2.3 using two ordinary users in two unrelated workspaces:
alpha-20260323072017bravo-20260323072017I used Alpha to create a legitimate private uploaded asset in a project issue.
The validated private asset ID in my run was:
As Bravo, I requested:
Plane returned:
with a presigned download URL for Alpha’s asset.
The downloaded file hash matched Alpha’s original private asset exactly:
That proved the read path crossed workspace boundaries successfully.
As Bravo, I then requested:
Plane returned:
and created a duplicated attacker-side asset:
The duplicated file’s SHA-256 matched Alpha’s original asset exactly:
That proved the source asset UUID alone was enough to copy cross-workspace content into an attacker-controlled workspace.
As Bravo, I then sent:
Plane returned:
When Alpha later fetched that asset, the server returned:
That proved cross-workspace integrity impact, not just disclosure.
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:
The downloaded final logo hash matched the attacker payload exactly:
That proved a visible cross-workspace overwrite path, not just a hidden backend access issue.
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.
It showed the issue was not limited to read-only exposure.
The same weak boundary enabled:
That makes the impact much stronger than a narrow “can fetch one file” IDOR.
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.
The most visible overwrite impact I validated was:
WORKSPACE_LOGOThat 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:
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.
This issue was reasonably classified as High.
The advisory classification was:
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.
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.
The issue was fixed in Plane v1.3.1.
The release notes for v1.3.1 described the fix clearly:
@allow_permission to all WorkspaceFileAssetEndpoint methodsDuplicateAssetEndpoint source asset lookup to workspaces where the caller is an active memberThat is the correct remediation direction because it addresses both failed security properties:
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.
This issue was reported privately through GitHub Security Advisories.
The report included:
The issue was later published as:
The advisory was published on May 15, 2026. The fix shipped in Plane v1.3.1.
The key lesson here is simple:
shared asset subsystems are authorization boundaries, not just storage helpers
A lot of developers think in terms of:
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:
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.