From 2e34a779809bf0f243dabd52674f927b4d6815fb Mon Sep 17 00:00:00 2001 From: Chad Date: Fri, 10 Apr 2026 01:09:27 -0500 Subject: [PATCH] fix: allow agent JWTs to access plugin tool endpoints Agents running with JWT authentication are blocked from discovering and executing plugin-contributed tools because both GET /plugins/tools and POST /plugins/tools/execute require board-level auth (assertBoard). This means agents cannot call plugin tools like escalate_to_human without workarounds. Add assertBoardOrAgent() to authz.ts and use it on the two plugin tool endpoints. Agents are still scoped to their own company via the existing assertCompanyAccess check in the execute endpoint. Co-Authored-By: Claude Opus 4.6 (1M context) --- server/src/routes/authz.ts | 6 ++++++ server/src/routes/plugins.ts | 8 +++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/server/src/routes/authz.ts b/server/src/routes/authz.ts index ec6c49621dc..e24567bc085 100644 --- a/server/src/routes/authz.ts +++ b/server/src/routes/authz.ts @@ -31,6 +31,12 @@ export function assertBoardOrgAccess(req: Request) { throw forbidden("Company membership or instance admin access required"); } +export function assertBoardOrAgent(req: Request) { + if (req.actor.type !== "board" && req.actor.type !== "agent") { + throw forbidden("Board or agent access required"); + } +} + export function assertInstanceAdmin(req: Request) { assertBoard(req); if (req.actor.source === "local_implicit" || req.actor.isInstanceAdmin) { diff --git a/server/src/routes/plugins.ts b/server/src/routes/plugins.ts index c3f6265f0af..1c01bbba890 100644 --- a/server/src/routes/plugins.ts +++ b/server/src/routes/plugins.ts @@ -11,8 +11,9 @@ * - Retrieving UI slot contributions for frontend rendering * - Discovering and executing plugin-contributed agent tools * - * All routes require board-level authentication, and sensitive instance-wide + * Most routes require board-level authentication, and sensitive instance-wide * mutations such as install/upgrade require instance-admin privileges. + * Plugin tool discovery and execution routes allow both board and agent access. * * @module server/routes/plugins * @see doc/plugins/PLUGIN_SPEC.md for the full plugin specification @@ -60,6 +61,7 @@ import { JsonRpcCallError, PLUGIN_RPC_ERROR_CODES } from "@paperclipai/plugin-sd import { assertAuthenticated, assertBoard, + assertBoardOrAgent, assertBoardOrgAccess, assertCompanyAccess, assertInstanceAdmin, @@ -878,7 +880,7 @@ export function pluginRoutes( * Errors: 501 if tool dispatcher is not configured */ router.get("/plugins/tools", async (req, res) => { - assertBoardOrgAccess(req); + assertBoardOrAgent(req); if (!toolDeps) { res.status(501).json({ error: "Plugin tool dispatch is not enabled" }); @@ -912,7 +914,7 @@ export function pluginRoutes( * - 502 if the plugin worker is unavailable or the RPC call fails */ router.post("/plugins/tools/execute", async (req, res) => { - assertBoardOrgAccess(req); + assertBoardOrAgent(req); if (!toolDeps) { res.status(501).json({ error: "Plugin tool dispatch is not enabled" });