From 09a82c271e42c74874d2593e488ffcc4070e417c Mon Sep 17 00:00:00 2001 From: Devin Foley Date: Wed, 3 Jun 2026 20:42:47 -0700 Subject: [PATCH] fix(authz): preserve board org-membership check in assertBoardOrAgent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Greptile flagged that swapping `assertBoardOrgAccess` → `assertBoardOrAgent` on `GET /api/plugins/tools` and `POST /api/plugins/tools/execute` silently dropped the org-membership check for board actors. `GET /api/plugins/tools` has no downstream company-scope guard, so memberless board users could enumerate every installed plugin tool. Tighten `assertBoardOrAgent` to delegate the board branch back to `assertBoardOrgAccess` (require company membership or instance-admin) while keeping the new agent-JWT path. Adds a regression test asserting a board user with no memberships gets 403 on tool listing. Co-Authored-By: Paperclip --- .../src/__tests__/plugin-routes-authz.test.ts | 22 +++++++++++++++++++ server/src/routes/authz.ts | 9 ++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/server/src/__tests__/plugin-routes-authz.test.ts b/server/src/__tests__/plugin-routes-authz.test.ts index f2f33ad6ec6..460d1e45293 100644 --- a/server/src/__tests__/plugin-routes-authz.test.ts +++ b/server/src/__tests__/plugin-routes-authz.test.ts @@ -915,6 +915,28 @@ describe.sequential("plugin tool and bridge authz", () => { // ─── Agent JWT tool execution (cherry-picked from #5549) ───────────────────── + it("rejects board users with no company memberships from listing plugin tools", async () => { + const listToolsForAgent = vi.fn(() => []); + const { app } = await createApp( + boardActor({ companyIds: [], isInstanceAdmin: false, source: "session" }), + {}, + { + toolDeps: { + toolDispatcher: { + listToolsForAgent, + getTool: vi.fn(), + executeTool: vi.fn(), + }, + }, + }, + ); + + const res = await request(app).get("/api/plugins/tools"); + + expect(res.status).toBe(403); + expect(listToolsForAgent).not.toHaveBeenCalled(); + }); + it("allows agent JWT to list available plugin tools", async () => { const listToolsForAgent = vi.fn(() => []); const { app } = await createApp(agentActor(), {}, { diff --git a/server/src/routes/authz.ts b/server/src/routes/authz.ts index e24567bc085..d51a09934d9 100644 --- a/server/src/routes/authz.ts +++ b/server/src/routes/authz.ts @@ -32,9 +32,14 @@ export function assertBoardOrgAccess(req: Request) { } export function assertBoardOrAgent(req: Request) { - if (req.actor.type !== "board" && req.actor.type !== "agent") { - throw forbidden("Board or agent access required"); + if (req.actor.type === "agent") { + return; } + if (req.actor.type === "board") { + assertBoardOrgAccess(req); + return; + } + throw forbidden("Board or agent access required"); } export function assertInstanceAdmin(req: Request) {