From 10848b98cb4938e998841075b1a041d6d1a8e063 Mon Sep 17 00:00:00 2001 From: Hugh Brown Date: Tue, 3 Mar 2026 13:31:49 -0700 Subject: [PATCH] security: scope agent board listing to organization Main agents (board_id=None) could list boards across all organizations. Now resolves the agent's organization via its gateway and filters boards by organization_id to prevent cross-tenant data leakage. Co-Authored-By: Claude Opus 4.6 --- backend/app/api/agent.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/backend/app/api/agent.py b/backend/app/api/agent.py index fbff168f..8d149795 100644 --- a/backend/app/api/agent.py +++ b/backend/app/api/agent.py @@ -23,6 +23,7 @@ from app.db.session import get_session from app.models.agents import Agent from app.models.board_webhook_payloads import BoardWebhookPayload from app.models.boards import Board +from app.models.gateways import Gateway from app.models.tags import Tag from app.models.task_dependencies import TaskDependency from app.models.tasks import Task @@ -368,6 +369,14 @@ async def list_boards( statement = select(Board) if agent_ctx.agent.board_id: statement = statement.where(col(Board.id) == agent_ctx.agent.board_id) + else: + # Main agents (board_id=None) must be scoped to their organization + # via their gateway to prevent cross-tenant board leakage. + gateway = await Gateway.objects.by_id(agent_ctx.agent.gateway_id).first(session) + if gateway is not None: + statement = statement.where( + col(Board.organization_id) == gateway.organization_id, + ) statement = statement.order_by(col(Board.created_at).desc()) return await paginate(session, statement)