Type: Insecure Direct Object Reference. Five label endpoints — PATCH /workspaces/{workspace_id}/labels/{label_id}, DELETE .../labels/{label_id}, POST .../issues/{issue_id}/labels/{label_id}, DELETE .../issues/{issue_id}/labels/{label_id}, GET .../issues/{issue_id}/labels — gate access on require_workspace_member(workspace_id) only and pass URL-supplied label_id and issue_id straight through to LabelService without verifying either belongs to the workspace.
File: src/praisonai-platform/praisonai_platform/services/label_service.py, lines 35-100; route handlers at src/praisonai-platform/praisonai_platform/api/routes/labels.py, lines 42-106.
Root cause: identical pattern to the agent / issue / project / comment IDORs in this codebase: the route's workspace_id is used as a membership predicate but never threaded through to the service layer. LabelService.get(label_id) runs session.get(IssueLabel, label_id) with no workspace filter; update/delete inherit the gap; add_to_issue(issue_id, label_id) and remove_from_issue(issue_id, label_id) write/delete association rows without verifying either ID belongs to the membership-checked workspace; list_for_issue(issue_id) reads them.
File 1: src/praisonai-platform/praisonai_platform/services/label_service.py, lines 35-100.
class LabelService:
...
async def get(self, label_id: str) -> Optional[IssueLabel]:
return await self._session.get(IssueLabel, label_id) # <-- BUG: no workspace_id predicate
async def update(
self,
label_id: str,
...
) -> Optional[IssueLabel]:
label = await self.get(label_id) # <-- inherits the gap
...
async def delete(self, label_id: str) -> bool:
label = await self.get(label_id) # <-- inherits the gap
...
async def add_to_issue(self, issue_id: str, label_id:...
0.1.00.1.10.1.20.1.30.1.4Exploitability
AV:NAC:LPR:LUI:NScope
S:UImpact
C:LI:HA:LCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:H/A:L