From 85828817778eb6eb9abd2198b5a446f186a940c8 Mon Sep 17 00:00:00 2001 From: Devin Foley Date: Wed, 3 Jun 2026 18:33:26 -0700 Subject: [PATCH] test: cherry-pick agent JWT plugin-tool regression tests from #5549 Adds focused regression tests for the agent JWT plugin tool access fix in this PR: - agent JWT can list available plugin tools (GET /api/plugins/tools) - agent JWT can execute a tool within its company scope - agent JWT cannot call execute with runContext.companyId outside its authenticated company - agent JWT cannot call execute with a runContext.agentId that does not belong to runContext.companyId Cherry-picked from #5549 and adapted to this PR's symbol names (assertBoardOrAgent) and three-row validateToolRunContextScope queue. Co-Authored-By: Paperclip --- .../src/__tests__/plugin-routes-authz.test.ts | 118 ++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/server/src/__tests__/plugin-routes-authz.test.ts b/server/src/__tests__/plugin-routes-authz.test.ts index 90371991576..f2f33ad6ec6 100644 --- a/server/src/__tests__/plugin-routes-authz.test.ts +++ b/server/src/__tests__/plugin-routes-authz.test.ts @@ -912,4 +912,122 @@ describe.sequential("plugin tool and bridge authz", () => { expect(res.body).toEqual({ runId: "run-1", jobId: "job-1" }); expect(scheduler.triggerJob).toHaveBeenCalledWith("job-1", "manual"); }); + + // ─── Agent JWT tool execution (cherry-picked from #5549) ───────────────────── + + it("allows agent JWT to list available plugin tools", async () => { + const listToolsForAgent = vi.fn(() => []); + const { app } = await createApp(agentActor(), {}, { + toolDeps: { + toolDispatcher: { + listToolsForAgent, + getTool: vi.fn(), + executeTool: vi.fn(), + }, + }, + }); + + const res = await request(app).get("/api/plugins/tools"); + + expect(res.status).toBe(200); + expect(listToolsForAgent).toHaveBeenCalled(); + }); + + it("allows agent JWT to execute a tool within its company scope", async () => { + const executeTool = vi.fn().mockResolvedValue({ content: "ok" }); + const { app } = await createApp( + agentActor(), + {}, + { + db: createSelectQueueDb([ + [{ companyId: companyA }], + [{ companyId: companyA, agentId: agentA }], + [{ companyId: companyA }], + ]), + toolDeps: { + toolDispatcher: { + listToolsForAgent: vi.fn(), + getTool: vi.fn(() => ({ name: "paperclip.example:search", pluginDbId: pluginId })), + executeTool, + }, + }, + }, + ); + + const res = await request(app) + .post("/api/plugins/tools/execute") + .send({ + tool: "paperclip.example:search", + parameters: { q: "test" }, + runContext: { agentId: agentA, runId: runA, companyId: companyA, projectId: projectA }, + }); + + expect(res.status).toBe(200); + expect(executeTool).toHaveBeenCalledWith( + "paperclip.example:search", + { q: "test" }, + { agentId: agentA, runId: runA, companyId: companyA, projectId: projectA }, + ); + }); + + it("rejects agent JWT when runContext.companyId is outside the agent's company scope", async () => { + const executeTool = vi.fn(); + const { app } = await createApp( + agentActor(), + {}, + { + db: createSelectQueueDb([]), + toolDeps: { + toolDispatcher: { + listToolsForAgent: vi.fn(), + getTool: vi.fn(() => ({ name: "paperclip.example:search", pluginDbId: pluginId })), + executeTool, + }, + }, + }, + ); + + const res = await request(app) + .post("/api/plugins/tools/execute") + .send({ + tool: "paperclip.example:search", + parameters: {}, + runContext: { agentId: agentA, runId: runA, companyId: companyB, projectId: projectA }, + }); + + expect(res.status).toBe(403); + expect(executeTool).not.toHaveBeenCalled(); + }); + + it("rejects agent JWT when runContext.agentId does not belong to runContext.companyId", async () => { + const otherAgent = "77777777-7777-4777-8777-777777777777"; + const executeTool = vi.fn(); + const { app } = await createApp( + agentActor(), + {}, + { + db: createSelectQueueDb([ + [{ companyId: companyB }], + ]), + toolDeps: { + toolDispatcher: { + listToolsForAgent: vi.fn(), + getTool: vi.fn(() => ({ name: "paperclip.example:search", pluginDbId: pluginId })), + executeTool, + }, + }, + }, + ); + + const res = await request(app) + .post("/api/plugins/tools/execute") + .send({ + tool: "paperclip.example:search", + parameters: {}, + runContext: { agentId: otherAgent, runId: runA, companyId: companyA, projectId: projectA }, + }); + + expect(res.status).toBe(403); + expect(executeTool).not.toHaveBeenCalled(); + }); });