Skip to main content

PraisonAI Platform CVE-2026-47413

CRITICAL
Improper Privilege Management (CWE-269)
2026-06-01 https://github.com/MervinPraison/PraisonAI GHSA-8g2p-pqm3-fcfh
9.6
CVSS 3.1 · GitHub Advisory
Share

Severity by source

GitHub Advisory PRIMARY
9.6 CRITICAL
AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N

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:C/C:H/I:H/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Changed
Confidentiality
High
Integrity
High
Availability
None

Lifecycle Timeline

2
Source Code Evidence Fetched
Jun 01, 2026 - 14:53 vuln.today
Analysis Generated
Jun 01, 2026 - 14:53 vuln.today

DescriptionGitHub Advisory

Summary

Type: Privilege escalation / cross-tenant member injection. The POST /workspaces/{workspace_id}/members endpoint is gated only by require_workspace_member(workspace_id) (default min_role="member") and forwards the request body's user_id and role straight into MemberService.add(workspace_id, user_id, role), which has no caller-permission check. A user with the lowest workspace privilege can add any user (including a new attacker-controlled second account, or an existing account they want to grief) as owner of the workspace. File: src/praisonai-platform/praisonai_platform/api/routes/workspaces.py, lines 92-101; services/member_service.py, lines 26-38. Root cause: MemberService.add validates only that role is in VALID_ROLES = {"owner", "admin", "member"} - the value, not the caller's right to assign it. The route's Depends(require_workspace_member) resolves to the default min_role="member". So a member-level token plus one POST gives the attacker an alternate identity with owner role inside the same workspace, bypassing every owner-only operation that *would* otherwise gate them.

Affected Code

File 1: src/praisonai-platform/praisonai_platform/api/routes/workspaces.py, lines 92-101.

python
@router.post("/{workspace_id}/members", response_model=MemberResponse, status_code=status.HTTP_201_CREATED)
async def add_member(
    workspace_id: str,
    body: MemberAdd,
    user: AuthIdentity = Depends(require_workspace_member),
# <-- BUG: defaults to min_role="member"
    session: AsyncSession = Depends(get_db),
):
    member_svc = MemberService(session)
    member = await member_svc.add(workspace_id, body.user_id, body.role)
# <-- writes any (user, role)
    return MemberResponse.model_validate(member)

File 2: src/praisonai-platform/praisonai_platform/services/member_service.py, lines 26-38.

python
async def add(
    self,
    workspace_id: str,
    user_id: str,
    role: str = "member",
) -> Member:
    """Add a user to a workspace."""
    if role not in VALID_ROLES:
# only validates the value
        raise ValueError(f"Invalid role: {role}. Must be one of {VALID_ROLES}")
    member = Member(workspace_id=workspace_id, user_id=user_id, role=role)
    self._session.add(member)
# <-- BUG: no caller-permission check
    await self._session.flush()
    return member

Why it's wrong: workspace member management is the textbook capability that must be gated on owner role. The role hierarchy is implemented (MemberService.has_role, member_service.py:80-96), the dependency-tunable min_role parameter exists (require_workspace_member(min_role), deps.py:58), but the POST .../members route uses neither. The VALID_ROLES enum check is purely cosmetic - it accepts "owner" from any caller because the route never asked whether the caller has the right to assign that role.

Exploit Chain

  1. Attacker registers two accounts (or recruits a member account on the target workspace W). Account A is an existing member of W; Account B is a fresh signup the attacker controls (any account on the platform - auth/register is open by default). State: attacker holds tokens for both A and B.
  2. Attacker authenticates as Account A and POSTs Authorization: Bearer <A_jwt> to POST /workspaces/W/members with body {"user_id": "<B_user_id>", "role": "owner"}. State: control flow enters add_member.
  3. require_workspace_member(W, A) passes (A is a member). MemberService.add(W, B, "owner") writes a new row Member(workspace_id=W, user_id=B, role="owner"). State: Account B is now a workspace-W owner.
  4. Attacker switches to Account B and acts as workspace owner - change settings, add/remove members, delete the workspace, or pivot to the companion advisories' primitives. State: attacker holds owner of any workspace they had member access to, via a fresh attacker-controlled identity that the original workspace's audit logs cannot easily attribute to A.
  5. Final state: with one member-level token plus one POST, the attacker plants an owner-role identity on any workspace they can reach. The same primitive lets the attacker invite a competitor or external-vendor account into the workspace as owner, exfiltrating the workspace's content under that competitor's name.

Security Impact

Severity: sec-critical. CVSS 9.1: network attack, low complexity, low privileges (member tier), no user interaction, scope changed (the new owner is a different security principal), high confidentiality and integrity, no availability claim. Attacker capability: with one workspace-member token plus one POST request, the attacker grants owner-tier access to any user_id on the platform. From there, full workspace control via the Account B token, plus indirect attribution: the original workspace's audit logs see "user A added user B as owner" but the audit trail cannot tell that B is attacker-controlled. Preconditions: praisonai-platform is deployed multi-tenant; the attacker has any membership token in the target workspace; the attacker can register or knows any other user_id on the platform. Differential: source-inspection-verified. The asymmetry between MemberService.has_role (clearly tiered) and add_member's default min_role="member" confirms the gap. With the suggested fix below, the gate refuses the member-tier token, the elevated POST returns 403, and the second-identity owner is never created.

Suggested Fix

diff
--- a/src/praisonai-platform/praisonai_platform/api/routes/workspaces.py
+++ b/src/praisonai-platform/praisonai_platform/api/routes/workspaces.py
@@ -90,11 +90,15 @@
+def _require_workspace_owner(workspace_id: str, user, session):
+    return require_workspace_member(workspace_id, user, session, min_role="owner")
+
 @router.post("/{workspace_id}/members", response_model=MemberResponse, status_code=status.HTTP_201_CREATED)
 async def add_member(
     workspace_id: str,
     body: MemberAdd,
-    user: AuthIdentity = Depends(require_workspace_member),
+    user: AuthIdentity = Depends(_require_workspace_owner),
     session: AsyncSession = Depends(get_db),
 ):
     member_svc = MemberService(session)
+    if body.role == "owner" and not await member_svc.has_role(workspace_id, user.id, "owner"):
+        raise HTTPException(status_code=403, detail="Only owners can add other owners")
     member = await member_svc.add(workspace_id, body.user_id, body.role)

The four other workspace mutation endpoints (update_workspace, delete_workspace, update_member_role, remove_member) exhibit the same default-min-role gap and are filed as their own advisories.

AnalysisAI

{workspace_id}/members. The endpoint enforces only the default member-tier gate and forwards the caller-supplied role directly to MemberService.add, which validates the role string but never checks the caller's permission to assign it. No public exploit identified at time of analysis, but a detailed exploit chain is documented in the vendor advisory.

Technical ContextAI

praisonai-platform is a Python/FastAPI multi-tenant workspace backend for the PraisonAI agent framework, using SQLAlchemy async sessions and a JWT-based AuthIdentity dependency model. The flaw is a CWE-269 Improper Privilege Management defect: the FastAPI dependency require_workspace_member supports a tunable min_role argument (deps.py:58) and the service layer already implements a tiered role check via MemberService.has_role (member_service.py:80-96), but the add_member route in workspaces.py:92-101 wires the dependency with its default min_role='member'. MemberService.add only validates that the requested role is in VALID_ROLES = {'owner','admin','member'} - a value-check, not an authorization check - so the request body's role field is written verbatim into the Member row.

RemediationAI

Vendor-released patch: upgrade praisonai-platform to 0.1.4 or later, which is the fixed version listed in GHSA-8g2p-pqm3-fcfh. The structural fix is to change the add_member dependency from Depends(require_workspace_member) to a min_role='owner' variant and to additionally guard owner-role assignment with an explicit MemberService.has_role check, as shown in the advisory's suggested diff. If immediate upgrade is not possible, compensating controls include placing an authenticated reverse proxy or WAF rule in front of the API that blocks or 403s POST /workspaces/*/members for non-owner principals (trade-off: requires the proxy to parse the JWT and consult workspace role state, otherwise it will either over-block or be bypassable), restricting workspace membership to fully trusted users until patched (trade-off: defeats the purpose of multi-tenant collaboration), or disabling open self-registration on auth/register so attackers cannot freely create the secondary identity used as the elevation target (trade-off: only narrows the attack to insiders, since an existing member account is still sufficient to grief or hand owner rights to any known user_id).

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-47413 vulnerability details – vuln.today

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