Severity by source
AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:H
Requires low privilege (member) but no user interaction; attack changes membership integrity and disrupts availability; no confidentiality impact.
Primary rating from Vendor (https://github.com/MervinPraison/PraisonAI).
CVSS VectorVendor: https://github.com/MervinPraison/PraisonAI
Lifecycle Timeline
3DescriptionCVE.org
Summary
Type: Authorization bypass enabling owner lockout. The DELETE /workspaces/{workspace_id}/members/{user_id} endpoint is gated only by require_workspace_member(workspace_id) (default min_role="member"). Any member can remove any other member, including the workspace owner, using a single DELETE. There is no caller-role check, no target-role check, no "cannot remove last owner" guard. File: src/praisonai-platform/praisonai_platform/api/routes/workspaces.py, lines 130-140; services/member_service.py, lines 71-78. Root cause: MemberService.remove(workspace_id, user_id) performs the deletion without any caller-permission check or owner-protection logic. The route accepts the URL-supplied user_id and dispatches it straight through. The role hierarchy (MemberService.has_role) is implemented but never invoked here. A member-tier attacker can issue DELETE .../members/<owner_user_id> and immediately lock the legitimate owner out of the workspace.
Affected Code
File 1: src/praisonai-platform/praisonai_platform/api/routes/workspaces.py, lines 130-140.
@router.delete("/{workspace_id}/members/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
async def remove_member(
workspace_id: str,
user_id: str,
user: AuthIdentity = Depends(require_workspace_member),
# <-- BUG: defaults to min_role="member"
session: AsyncSession = Depends(get_db),
):
member_svc = MemberService(session)
removed = await member_svc.remove(workspace_id, user_id)
# <-- removes any member, including owner
if not removed:
raise HTTPException(status_code=404, detail="Member not found")File 2: src/praisonai-platform/praisonai_platform/services/member_service.py, lines 71-78.
async def remove(self, workspace_id: str, user_id: str) -> bool:
"""Remove a member from a workspace."""
member = await self.get(workspace_id, user_id)
if member is None:
return False
await self._session.delete(member)
# <-- BUG: no caller-role check, no last-owner protection
await self._session.flush()
return TrueWhy it's wrong: member-removal is the textbook capability that must be gated on owner role. Removing the workspace owner is a permanent denial-of-service against the legitimate owner unless another owner exists. There must be (a) a caller min-role gate of "owner" or "admin", (b) a check that prevents removing a member whose role is higher than the caller's, and (c) a check that the workspace is left with at least one owner. None of these exist.
Exploit Chain
- Attacker is a member of workspace
Wwith role "member". State: attacker holds JWT. - Attacker enumerates the workspace owner's
user_idviaGET /workspaces/W/members(list_members has the same default-member gate, separate finding). Owner UUIDO_idis now known. State: attacker holdsO_id. - Attacker sends
DELETE /workspaces/W/members/O_idwithAuthorization: Bearer <attacker_jwt>. State: control flow entersremove_member. require_workspace_member(W, attacker)passes (attacker is a member).MemberService.remove(W, O_id)deletes the owner's member row. State:Member(workspace_id=W, user_id=O_id, role="owner")is gone.- Owner attempts
GET /workspaces/W/...andrequire_workspace_member(W, O_id)returns 403. State: legitimate owner is now locked out of their own workspace. - Combined with the
update_member_rolecompanion advisory, the attacker first promotes themselves to owner, then removes the legitimate owner, then has uncontested control. Combined withdelete_workspace, the attacker wipes the workspace after kicking the owner. - Final state: with one member-level token, the attacker locks the legitimate owner out of their own workspace permanently. The owner has no recourse other than database-level admin intervention.
Security Impact
Severity: sec-high. CVSS 8.1: network attack, low complexity, low privileges, no user interaction, scope unchanged, no confidentiality, high integrity (membership table corrupted), high availability (legitimate owner cannot access their own workspace). Attacker capability: with one workspace-member token plus one DELETE request, the attacker permanently locks any other member (including the workspace owner) out of the workspace. Preconditions: praisonai-platform is deployed multi-tenant; attacker has any membership token; owner's user_id is reachable via the (unauthenticated-for-member) list_members endpoint. Differential: source-inspection-verified. The asymmetry between require_workspace_member's tunable min_role parameter and this endpoint's use of the default value confirms the gap. With the suggested fix below, member-tier tokens fail the gate, and removing the workspace's last owner triggers the additional guard.
Suggested Fix
--- a/src/praisonai-platform/praisonai_platform/api/routes/workspaces.py
+++ b/src/praisonai-platform/praisonai_platform/api/routes/workspaces.py
@@ -130,11 +130,21 @@
@router.delete("/{workspace_id}/members/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
async def remove_member(
workspace_id: str,
user_id: str,
- user: AuthIdentity = Depends(require_workspace_member),
+ user: AuthIdentity = Depends(_require_workspace_owner),
session: AsyncSession = Depends(get_db),
):
member_svc = MemberService(session)
+ target = await member_svc.get(workspace_id, user_id)
+ if target is not None and target.role == "owner":
+
# Refuse to remove the last owner.
+ owners = [m for m in await member_svc.list_members(workspace_id) if m.role == "owner"]
+ if len(owners) <= 1:
+ raise HTTPException(status_code=409, detail="Cannot remove the last workspace owner")
removed = await member_svc.remove(workspace_id, user_id)
if not removed:
raise HTTPException(status_code=404, detail="Member not found")The four companion workspace-mutation endpoints exhibit the same default-min-role gap and are filed as their own advisories.
AnalysisAI
Authorization bypass in praisonai-platform's workspace member removal endpoint allows any workspace member to delete any other member, including the owner. An attacker with a low-privilege member token can permanently lock the legitimate owner out of their workspace, leading to a complete denial of service and, when combined with other flawed endpoints, full workspace takeover. Patched in version 0.1.4.
Technical ContextAI
The vulnerability exists in the Python FastAPI-based PraisonAI Platform due to missing role checks in the DELETE /workspaces/{workspace_id}/members/{user_id} route. The dependency require_workspace_member only validates that the caller is a member of the workspace (defaulting to min_role='member'), but the service layer MemberService.remove() performs no caller-role verification and lacks protection against removing the last owner. This is a classic improper privilege management issue (CWE-269), where the authorization guard is misapplied and the operation delegates trust entirely to the URL-supplied user ID. The affected component is the praisonai_platform Python package (CPE: pkg:pip/praisonai-platform).
RemediationAI
Upgrade praisonai-platform to version 0.1.4 or later, which introduces the necessary owner-role gate and last-owner protection via commit 24385d6 (https://github.com/MervinPraison/PraisonAI/commit/24385d64876577620f749957bd4814f162f4ca47). The fix also includes a PLATFORM_HOST environment variable and other security enhancements. Until an upgrade can be applied, consider restricting access to the member removal endpoint by applying a reverse proxy rule that blocks DELETE requests to /workspaces/*/members/* from non-administrative users, though this may interfere with legitimate owner operations.
Wazuh SIEM platform versions 4.4.0 through 4.9.0 contain an unsafe deserialization vulnerability in the DistributedAPI t
BentoML version 1.4.2 and earlier contains an unauthenticated remote code execution vulnerability through insecure deser
pgAdmin 4 contains critical remote code execution vulnerabilities in the Query Tool download and Cloud Deployment endpoi
The renderLocalView function in render/views.py in graphite-web in Graphite 0.9.5 through 0.9.10 uses the pickle Python
BentoML is a Python library for building online serving systems optimized for AI apps and model inference. Rated critica
OpenSSL before 0.9.8za, 1.0.0 before 1.0.0m, and 1.0.1 before 1.0.1h does not properly restrict processing of ChangeCiph
pyLoad download manager version prior to 0.5.0b3.dev77 exposes the Flask SECRET_KEY through an unauthenticated endpoint.
Langflow (a visual LLM pipeline builder) contains a critical unauthenticated code execution vulnerability (CVE-2026-3301
In Mercurial before 4.1.3, "hg serve --stdio" allows remote authenticated users to launch the Python debugger, and conse
Unauthenticated remote code execution affects Kestra OSS (the open-source event-driven orchestration platform) prior to
Unauthenticated remote code execution in Marimo ≤0.20.4 allows attackers to execute arbitrary system commands via the `/
pyLoad is the free and open-source Download Manager written in pure Python. Rated medium severity (CVSS 5.3), this vulne
Same weakness CWE-269 – Improper Privilege Management
View allSame technique Privilege Escalation
View allShare
External POC / Exploit Code
Leaving vuln.today
EUVD-2026-46323
GHSA-w388-2392-px73