Skip to main content

PraisonAI Platform CVE-2026-47409

HIGH
Improper Privilege Management (CWE-269)
2026-05-29 https://github.com/MervinPraison/PraisonAI GHSA-w388-2392-px73
8.1
CVSS 3.1 · GitHub Advisory
Share

Severity by source

GitHub Advisory PRIMARY
8.1 HIGH
AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:H

Primary rating from GitHub Advisory · only source for this CVE.

CVSS VectorGitHub Advisory

CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:H
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Unchanged
Confidentiality
None
Integrity
High
Availability
High

Lifecycle Timeline

2
Source Code Evidence Fetched
May 29, 2026 - 23:24 vuln.today
Analysis Generated
May 29, 2026 - 23:24 vuln.today

DescriptionGitHub Advisory

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.

python
@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.

python
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 True

Why 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

  1. Attacker is a member of workspace W with role "member". State: attacker holds JWT.
  2. Attacker enumerates the workspace owner's user_id via GET /workspaces/W/members (list_members has the same default-member gate, separate finding). Owner UUID O_id is now known. State: attacker holds O_id.
  3. Attacker sends DELETE /workspaces/W/members/O_id with Authorization: Bearer <attacker_jwt>. State: control flow enters remove_member.
  4. 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.
  5. Owner attempts GET /workspaces/W/... and require_workspace_member(W, O_id) returns 403. State: legitimate owner is now locked out of their own workspace.
  6. Combined with the update_member_role companion advisory, the attacker first promotes themselves to owner, then removes the legitimate owner, then has uncontested control. Combined with delete_workspace, the attacker wipes the workspace after kicking the owner.
  7. 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

diff
--- 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

{workspace_id}/members/{user_id}, which defaults to min_role="member"` instead of requiring owner/admin privileges, and there is no public exploit identified at time of analysis.

Technical ContextAI

praisonai-platform is a Python/FastAPI multi-tenant orchestration backend for the MervinPraison/PraisonAI project, distributed via pip as praisonai-platform. The vulnerable route is defined in api/routes/workspaces.py and delegates to MemberService.remove in services/member_service.py; authorization is implemented through a tunable require_workspace_member dependency whose min_role parameter is left at the default member value for this destructive endpoint. The root cause maps to CWE-269 (Improper Privilege Management): although a role hierarchy helper (MemberService.has_role) exists, it is never invoked on this path, so neither caller-role nor target-role checks run, and no last-owner guard prevents deletion of the only remaining owner.

RemediationAI

Vendor-released patch: upgrade praisonai-platform to 0.1.4 or later, per GHSA-w388-2392-px73 (https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-w388-2392-px73). Operators who cannot upgrade immediately should apply the vendor's suggested fix by swapping the require_workspace_member dependency on the member-removal route for an owner/admin-level dependency, adding a target-role check that forbids lower-privileged callers from removing owners, and adding a last-owner guard that returns HTTP 409 when only one owner remains; the same default-min-role gap affects the four companion workspace-mutation endpoints noted in the advisory, so review and gate those simultaneously. As a temporary compensating control, restrict access to DELETE /workspaces/{workspace_id}/members/{user_id} at the ingress/reverse-proxy layer to trusted administrative IPs or disable the route entirely - the trade-off is that legitimate member-management workflows break until the patch is applied. Audit Member rows and recent DELETE activity for any unexpected owner removals before resuming normal operation.

More in Python

View all
CVE-2025-24016 CRITICAL POC
9.9 Feb 10

Wazuh SIEM platform versions 4.4.0 through 4.9.0 contain an unsafe deserialization vulnerability in the DistributedAPI t

CVE-2025-27520 CRITICAL POC
9.8 Apr 04

BentoML version 1.4.2 and earlier contains an unauthenticated remote code execution vulnerability through insecure deser

CVE-2025-2945 CRITICAL POC
9.9 Apr 03

pgAdmin 4 contains critical remote code execution vulnerabilities in the Query Tool download and Cloud Deployment endpoi

CVE-2013-5093 MEDIUM POC
6.8 Sep 27

The renderLocalView function in render/views.py in graphite-web in Graphite 0.9.5 through 0.9.10 uses the pickle Python

CVE-2025-32375 CRITICAL POC
9.8 Apr 09

BentoML is a Python library for building online serving systems optimized for AI apps and model inference. Rated critica

CVE-2014-0224 HIGH POC
7.4 Jun 05

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

CVE-2024-21644 HIGH POC
7.5 Jan 08

pyLoad download manager version prior to 0.5.0b3.dev77 exposes the Flask SECRET_KEY through an unauthenticated endpoint.

CVE-2017-9462 HIGH POC
8.8 Jun 06

In Mercurial before 4.1.3, "hg serve --stdio" allows remote authenticated users to launch the Python debugger, and conse

CVE-2026-39987 CRITICAL POC
9.3 Apr 08

Unauthenticated remote code execution in Marimo ≤0.20.4 allows attackers to execute arbitrary system commands via the `/

CVE-2024-21645 MEDIUM POC
5.3 Jan 08

pyLoad is the free and open-source Download Manager written in pure Python. Rated medium severity (CVSS 5.3), this vulne

CVE-2026-33017 CRITICAL POC
9.3 Mar 17

Langflow (a visual LLM pipeline builder) contains a critical unauthenticated code execution vulnerability (CVE-2026-3301

CVE-2026-55255 HIGH POC
8.4 Jun 19

Cross-user flow execution in Langflow (< 1.9.1) lets any authenticated API-key holder run another user's flow by passing

Share

CVE-2026-47409 vulnerability details – vuln.today

This site uses cookies essential for authentication and security. No tracking or analytics cookies are used. Privacy Policy