Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
🐍
🐍 PyPI
Not in CISA KEV
HIGH severity

praisonai-platform: Missing authorization on member removal enables full workspace takeover by any user regardless of roleGHSA-w388-2392-px73

HIGH

GHSA-w388-2392-px73 is a high-severity (CVSS 8.1) Improper Privilege Management vulnerability in praisonai-platform. A fix is available for praisonai-platform — see the affected versions and patch details below.

Also known asCVE-2026-47409PYSEC-2026-2938
Published
Updated
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed
Exploitation data as of Oct 12, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

Exploitation Status

Proof-of-concept exploit code exists

  • CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.

Exploitation and automatability from CISA’s SSVC triage for GHSA-w388-2392-px73.

EPSS Exploitation Probability

via FIRST.org ↗
0.5%probability of exploitation in next 30 days
Lower Risk0.00%
Lower risk than most CVEs43th percentile — riskier than 43% of all scored CVEsHighest risk
0.00%0.34%0.68%1.03%0.3%0.5%0.5%0.5%Aug 26Oct 26Oct 26

Probability of exploitation in the next 30 days, from FIRST.org EPSS.

How urgent is this, really

GHSA-w388-2392-px73 by exploitation likelihood (EPSS) against impact (CVSS). Outside the shaded patch-first corner.

Where this sits among everything scored

Of 385,987 CVEs with a current EPSS score, this one falls in the < 10% band (highlighted). Counts from FIRST.org, log-scaled.

Real-World Exposure

1 pkg affected
🐍praisonai-platform

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects PyPI packages — download data is not available via public APIs for these ecosystems.

Description

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

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

Affected Packages

1 total 1 fixed
EcosystemPackageVulnerable rangeFix
🐍PyPIpraisonai-platformall versions0.1.4pip install --upgrade 'praisonai-platform==0.1.4'

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for praisonai-platform, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.

  2. Fix

    Update praisonai-platform to 0.1.4 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-w388-2392-px73 is resolved across your whole dependency graph.

  3. Workarounds

    Close the privilege gap rather than the entry point: audit which accounts, roles and service identities can reach the affected operation, drop the component to the least privilege it actually needs, and review file and directory permissions created by earlier installs — a default left in place is what makes this reachable.

Frequently Asked Questions

## 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, use
O3 Security · Impact-Aware SCA

Is GHSA-w388-2392-px73 in your dependencies?

Find it across PyPI, including transitive dependencies.

praisonai-platform: Missing authorization on member…