CVE-2026-47413: praisonai-platform: member can escalate to workspace owner
GHSA-8g2p-pqm3-fcfh CRITICALA critical authorization flaw in praisonai-platform allows any workspace member to escalate their account—or a fresh attacker-controlled identity—to workspace owner with a single POST request, bypassing every owner-gated operation in the platform. The vulnerability carries a CVSS score of 9.1 with no user interaction required and low exploitation complexity, meaning any member-level credential is sufficient to fully compromise a workspace. With 65 other CVEs recorded in the same package, this pattern of missing authorization checks appears systemic rather than isolated, amplifying risk across all multi-tenant PraisonAI deployments. Teams running praisonai-platform should upgrade immediately to version 0.1.4, audit all workspace owner memberships for unexpected accounts, and review API logs for POST /workspaces/*/members calls originating from non-owner tokens.
What is the risk?
CRITICAL. CVSS 9.1 (AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N). Network-exploitable with low privileges and zero user interaction required. Any authenticated workspace member can achieve full workspace ownership via a single unauthenticated privilege escalation call. The scope change (S:C) reflects that the escalated owner identity is a distinct security principal from the attacker's original member account, enabling lateral movement and attribution evasion. The presence of 65 prior CVEs in the same package strongly suggests systemic authorization gaps across the broader codebase rather than an isolated oversight. Not yet in CISA KEV, but trivial exploitation complexity means weaponization by low-sophistication actors is highly probable in the near term.
Attack Kill Chain
What systems are affected?
| Package | Ecosystem | Vulnerable Range | Patched |
|---|---|---|---|
| praisonai-platform | pip | < 0.1.4 | 0.1.4 |
Do you use praisonai-platform? You're affected.
Severity & Risk
Attack Surface
What should I do?
5 steps-
PATCH IMMEDIATELY
Upgrade praisonai-platform to version 0.1.4, which gates POST /workspaces/{id}/members on owner-level authentication via require_workspace_member(min_role='owner').
-
AUDIT
Query your database for workspace members with 'owner' role — cross-reference against known legitimate owners and revoke any unexpected entries added since deployment.
-
DETECT
Implement logging and alerting for all POST /workspaces/*/members API calls; alert on any request where the authenticated caller is not already an owner of that workspace.
-
ROTATE CREDENTIALS
Any workspace where unauthorized owner accounts may have been injected should be treated as fully compromised — rotate all API keys, tokens, and credentials stored in workspace configuration.
-
REVIEW RELATED ENDPOINTS
The advisory notes that update_workspace, delete_workspace, update_member_role, and remove_member share the same default min_role gap; verify all are remediated in 0.1.4 before considering the attack surface closed.
Classification
Compliance Impact
This CVE is relevant to:
Frequently Asked Questions
What is CVE-2026-47413?
A critical authorization flaw in praisonai-platform allows any workspace member to escalate their account—or a fresh attacker-controlled identity—to workspace owner with a single POST request, bypassing every owner-gated operation in the platform. The vulnerability carries a CVSS score of 9.1 with no user interaction required and low exploitation complexity, meaning any member-level credential is sufficient to fully compromise a workspace. With 65 other CVEs recorded in the same package, this pattern of missing authorization checks appears systemic rather than isolated, amplifying risk across all multi-tenant PraisonAI deployments. Teams running praisonai-platform should upgrade immediately to version 0.1.4, audit all workspace owner memberships for unexpected accounts, and review API logs for POST /workspaces/*/members calls originating from non-owner tokens.
Is CVE-2026-47413 actively exploited?
No confirmed active exploitation of CVE-2026-47413 has been reported, but organizations should still patch proactively.
How to fix CVE-2026-47413?
1. PATCH IMMEDIATELY: Upgrade praisonai-platform to version 0.1.4, which gates POST /workspaces/{id}/members on owner-level authentication via require_workspace_member(min_role='owner'). 2. AUDIT: Query your database for workspace members with 'owner' role — cross-reference against known legitimate owners and revoke any unexpected entries added since deployment. 3. DETECT: Implement logging and alerting for all POST /workspaces/*/members API calls; alert on any request where the authenticated caller is not already an owner of that workspace. 4. ROTATE CREDENTIALS: Any workspace where unauthorized owner accounts may have been injected should be treated as fully compromised — rotate all API keys, tokens, and credentials stored in workspace configuration. 5. REVIEW RELATED ENDPOINTS: The advisory notes that update_workspace, delete_workspace, update_member_role, and remove_member share the same default min_role gap; verify all are remediated in 0.1.4 before considering the attack surface closed.
What systems are affected by CVE-2026-47413?
This vulnerability affects the following AI/ML architecture patterns: AI agent frameworks, Multi-tenant AI platforms, Agent configuration management.
What is the CVSS score for CVE-2026-47413?
CVE-2026-47413 has a CVSS v3.1 base score of 9.6 (CRITICAL).
AI Security Impact
Affected AI Architectures
MITRE ATLAS Techniques
AML.T0012 Valid Accounts AML.T0021 Establish Accounts AML.T0049 Exploit Public-Facing Application AML.T0081 Modify AI Agent Configuration AML.T0085 Data from AI Services Compliance Controls Affected
Technical Details
Original 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.
Exploitation Scenario
An attacker working as a legitimate contractor or supply-chain vendor obtains member-level access to a target organization's PraisonAI workspace containing production AI agent configurations, API keys, and proprietary tool integrations. Using their member JWT token, they register a second account under a neutral email address and send POST /workspaces/{target_id}/members with body {"user_id": "<new_account_id>", "role": "owner"}. The platform accepts the request and writes the owner row without any authorization check. The attacker authenticates as the new owner account, exports agent configurations and stored credentials, modifies agent system prompts to include silent data exfiltration instructions, and removes their original member account to reduce the visible audit trail. The original workspace owners observe 'contractor-account added external-account as owner' in logs — an entry that may appear routine if the contractor engagement was recent — giving the attacker a substantial dwell-time advantage.
Weaknesses (CWE)
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N References
Timeline
Related Vulnerabilities
CVE-2026-47392 9.9 praisonaiagents: RCE via Python sandbox bypass
Same package: praisonai GHSA-vc46-vw85-3wvm 9.8 PraisonAI: RCE via malicious workflow YAML execution
Same package: praisonai CVE-2026-39890 9.8 PraisonAI: YAML deserialization enables unauthenticated RCE
Same package: praisonai GHSA-9qhq-v63v-fv3j 9.8 PraisonAI: RCE via MCP command injection
Same package: praisonai CVE-2026-47410 9.8 praisonai-platform: hardcoded JWT → full account takeover
Same package: praisonai