praisonai-platform: list_issue_activity returns activity log for any issue regardless of workspace ownership
Date: 2026-05
Affected: pip/praisonai-platform
Attack vector: auth-bypass
Category: vulnerability-disclosure
Severity: Medium
CVE: CVE-2026-47408
CWE: CWE-639
CVSS: 6.5 (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N)

## Summary **Type:** Insecure Direct Object Reference. The `GET /workspaces/{workspace_id}/issues/{issue_id}/activity` endpoint is gated by `require_workspace_member(workspace_id)` and dispatches to `ActivityService.list_for_issue(issue_id)`, which executes `SELECT * FROM activity WHERE issue_id = :issue_id` with no workspace constraint. A user who is a member of any workspace can read the full activity log of any issue across the entire multi-tenant deployment. **File:** `src/praisonai-platform/praisonai_platform/api/routes/activity.py`, lines 32-43; `services/activity_service.py`'s `list_for_issue` method. **Root cause:** the route extracts `workspace_id` from the URL path, uses it solely for the membership gate, then passes the URL-supplied `issue_id` directly to `ActivityService.list_for_issue(issue_id)` without verifying which workspace the issue belongs to. The companion `list_workspace_activity` endpoint at line 19-29 is implemented correctly (it passes `workspace_id` to `svc.list_for_workspace(workspace_id)`) — the asymmetry is the smoking gun. ## Affected Code **File:** `src/praisonai-platform/praisonai_platform/api/routes/activity.py`, lines 19-43. ```python @router.get("/activity", response_model=List[ActivityLogResponse]) async def list_workspace_activity( workspace_id: str, limit: int = Query(50, ge=1, le=200), offset: int = Query(0, ge=0), user: AuthIdentity = Depends(require_workspace_member), session: AsyncSession = Depends(get_db), ):


Mitigations:

Architectural layers (MAESTRO):