GHSA-rcmc-q9rj-4wmq
MEDIUMGHSA-rcmc-q9rj-4wmq is a medium-severity (CVSS 6.5) Improper Privilege Management vulnerability in praisonai-platform. O3 Security confirms whether GHSA-rcmc-q9rj-4wmq is actually reachable in your code before you act, and blocks exploitation at runtime until you patch.
praisonai-platform: Any workspace member can rewrite workspace name, description, and settings via PATCH /workspaces/{id}
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-rcmc-q9rj-4wmq.
EPSS Exploitation Probability
EPSS (Exploit Prediction Scoring System) is a daily probability model maintained by FIRST.org. It estimates the likelihood a CVE will be exploited in production environments within the next 30 days, derived from real-world threat intelligence signals.
How urgent is this, really
GHSA-rcmc-q9rj-4wmq plotted by exploitation likelihood (EPSS) against impact (CVSS). The shaded corner — EPSS 50%+ and CVSS 7.0+ — is where this CVE doesn't sit, though severity or exploitability alone can still warrant action.
Where this sits among everything scored
Of 366,298 CVEs with a current EPSS score, this one falls in the < 10% band (highlighted). Real counts from FIRST.org, not a sample — log-scaled since the landscape is heavily right-skewed.
Real-World Exposure
praisonai-platformReal-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 workspace metadata + settings tampering. The PATCH /workspaces/{workspace_id} endpoint is gated only by require_workspace_member(workspace_id) (default min_role="member"). Any member can rewrite the workspace's name, description, and the settings JSON blob. The settings field is a free-form JSON object — depending on which downstream code reads it, this becomes a configuration-injection primitive for any setting the platform exposes there.
File: src/praisonai-platform/praisonai_platform/api/routes/workspaces.py, lines 63-74; services/workspace_service.py's update() method.
Root cause: Depends(require_workspace_member) resolves to default min_role="member". WorkspaceService.update(workspace_id, name, description, settings) writes the new fields to the workspace row without any caller-permission check. The role hierarchy (MemberService.has_role) is never consulted.
Affected Code
File: src/praisonai-platform/praisonai_platform/api/routes/workspaces.py, lines 63-74.
@router.patch("/{workspace_id}", response_model=WorkspaceResponse)
async def update_workspace(
workspace_id: str,
body: WorkspaceUpdate,
user: AuthIdentity = Depends(require_workspace_member), # <-- BUG: defaults to min_role="member"
session: AsyncSession = Depends(get_db),
):
ws_svc = WorkspaceService(session)
ws = await ws_svc.update(workspace_id, body.name, body.description, body.settings) # <-- writes any value
if ws is None:
raise HTTPException(status_code=404, detail="Workspace not found")
return WorkspaceResponse.model_validate(ws)
Why it's wrong: workspace name and settings are owner-tier fields. Renaming the workspace to a profanity is a low-impact griefing vector; rewriting the JSON settings blob is potentially a much higher-impact configuration injection (depending on what fields downstream code reads from settings, the attacker may flip feature flags, redirect webhook URLs, change LLM provider keys for shared configs, disable audit logging, etc.). The require_workspace_member(min_role) parameter is implemented and unused. This endpoint should require owner.
Exploit Chain
- Attacker is a member of workspace
Wwith role "member". State: attacker holds JWT. - Attacker sends
PATCH /workspaces/WwithAuthorization: Bearer <attacker_jwt>and body{"name": "Compromised", "description": "Owned by attacker", "settings": {"allow_public_invite": true, "ai_provider_url": "https://attacker.example/v1"}}. State: control flow entersupdate_workspace. require_workspace_member(W, attacker)passes.WorkspaceService.update(W, ...)writes the three fields. State: workspaceWnow has attacker-chosen name, description, and settings.- The settings JSON is read by any downstream code that consults workspace settings (LLM proxying, invite flows, webhook routing). If the deployment uses settings-keyed configuration overrides, those overrides now point at attacker-controlled endpoints.
- Final state: with one member-level token plus one PATCH, the attacker rewrites the workspace's metadata and settings, with effects ranging from cosmetic (rename) to substantive (settings-keyed config injection).
Security Impact
Severity: sec-moderate. CVSS 6.5: network attack, low complexity, low privileges, no user interaction, scope unchanged, no confidentiality directly (though settings rewrites may enable indirect data exfiltration via attacker-pointed integration URLs), high integrity, no availability claim.
Attacker capability: rewrite any workspace's name, description, and settings JSON. The actual blast radius depends on what fields the deployment reads from settings — but that field is documented as a free-form JSON blob, so any future configuration the platform adds there becomes attacker-tunable.
Preconditions: praisonai-platform is deployed multi-tenant; attacker has any membership token in the target workspace.
Differential: source-inspection-verified. With the suggested fix below, member-tier tokens fail the gate and the metadata rewrite is rejected with 403.
Suggested Fix
--- a/src/praisonai-platform/praisonai_platform/api/routes/workspaces.py
+++ b/src/praisonai-platform/praisonai_platform/api/routes/workspaces.py
@@ -63,11 +63,11 @@
@router.patch("/{workspace_id}", response_model=WorkspaceResponse)
async def update_workspace(
workspace_id: str,
body: WorkspaceUpdate,
- user: AuthIdentity = Depends(require_workspace_member),
+ user: AuthIdentity = Depends(_require_workspace_owner), # see member-update-role advisory for helper
session: AsyncSession = Depends(get_db),
):
ws_svc = WorkspaceService(session)
ws = await ws_svc.update(workspace_id, body.name, body.description, body.settings)
if ws is None:
raise HTTPException(status_code=404, detail="Workspace not found")
return WorkspaceResponse.model_validate(ws)
Defence-in-depth: validate the keys allowed in body.settings against an allowlist so the field cannot become an arbitrary config-injection primitive even for owners. The four companion workspace-mutation endpoints (add_member, update_member_role, remove_member, delete_workspace) exhibit the same default-min-role gap and are filed as their own advisories.
Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 🐍PyPI | praisonai-platform | all versions | 0.1.4 |
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for praisonai-platform. O3's reachability analysis confirms whether the vulnerable code path is actually invoked in your application, so you act on real exposure instead of every transitive match.
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-rcmc-q9rj-4wmq is resolved across your whole dependency graph.
Workarounds
If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.
How O3 protects you
O3 pinpoints whether GHSA-rcmc-q9rj-4wmq is reachable in your code and exactly where to fix it, then blocks exploitation in production at runtime until the patched version is deployed.
Tailored to GHSA-rcmc-q9rj-4wmq. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.
Frequently Asked Questions
Is GHSA-rcmc-q9rj-4wmq in your dependencies?
O3 detects GHSA-rcmc-q9rj-4wmq across PyPI dependencies and uses function-level reachability to confirm whether the vulnerable code path is actually reachable — not just present. No false positives.