server/src/agents/profile-policy.ts defines canAccessAgent (public, or owner, or administrator) and exports canRunAgent as its alias. Neither is called by any production path.
A repo-wide grep over every .ts and .tsx outside node_modules at main 06a1a84 returns the two definitions and 14 call sites, all of them in server/tests/agent-profile-policy.test.ts. Only canManageAgent is wired, at profile-store.ts:191 and routes.ts:348. The test asserts canRunAgent(otherUser, privateAgent) === false, so the rule is written down, green, and unenforced.
The roster and the CopilotKit runtime are fine: they get the rule indirectly from accessFilter in the store's read path. The surfaces that take a Bot id straight from the request are the ones that never asked:
| Surface |
Where the Bot id comes from |
Gate today |
/api/computers/:botId/* |
path |
requireUser |
POST /api/plugins/call |
body agentId |
requireUser |
GET /api/plugins/for/:agentId |
path |
requireUser |
GET /api/components/for-agent/:agentId |
path |
requireUser |
POST /api/components/:name/decision |
body agentId |
requireUser |
POST /api/components/:name/call |
body agentId |
requireUser |
/api/computers/:botId/stream (websocket) |
path |
session only, actor dropped |
So an account holding the base user role can reset another person's private coworker, drive its browser, read its workspace, watch its screen, list what it has been granted, and call its MCP tools and data functions on the deployment's stored credential. store.decide in plugins/store.ts:691 keys on (kind, ref, agentId) alone, so actorId reaches it only for the audit row and the policy context.
Verified by driving the real Hono routes with a role-user actor: a request naming a Bot the actor does not own reaches the gateway, and the plugin call reaches callTool, with no check in between.
Two details worth having. Enablement is careful about this on the same surfaces, enablementRefusal in plugins/routes.ts:255 stops a non-admin granting MCP or putting a skill on a Bot they do not own, so granting is checked while acting is not. And open PR #31, which moves tool execution server-side, threads actorId through for audit only, so it carries the gap over rather than closing it.
A PR follows. It asks the store's existing get(actor, id), which already applies the same policy, once per surface rather than per route.
server/src/agents/profile-policy.tsdefinescanAccessAgent(public, or owner, or administrator) and exportscanRunAgentas its alias. Neither is called by any production path.A repo-wide grep over every
.tsand.tsxoutsidenode_modulesatmain06a1a84 returns the two definitions and 14 call sites, all of them inserver/tests/agent-profile-policy.test.ts. OnlycanManageAgentis wired, atprofile-store.ts:191androutes.ts:348. The test assertscanRunAgent(otherUser, privateAgent) === false, so the rule is written down, green, and unenforced.The roster and the CopilotKit runtime are fine: they get the rule indirectly from
accessFilterin the store's read path. The surfaces that take a Bot id straight from the request are the ones that never asked:/api/computers/:botId/*requireUserPOST /api/plugins/callagentIdrequireUserGET /api/plugins/for/:agentIdrequireUserGET /api/components/for-agent/:agentIdrequireUserPOST /api/components/:name/decisionagentIdrequireUserPOST /api/components/:name/callagentIdrequireUser/api/computers/:botId/stream(websocket)So an account holding the base
userrole can reset another person's private coworker, drive its browser, read its workspace, watch its screen, list what it has been granted, and call its MCP tools and data functions on the deployment's stored credential.store.decideinplugins/store.ts:691keys on(kind, ref, agentId)alone, soactorIdreaches it only for the audit row and the policy context.Verified by driving the real Hono routes with a role-
useractor: a request naming a Bot the actor does not own reaches the gateway, and the plugin call reachescallTool, with no check in between.Two details worth having. Enablement is careful about this on the same surfaces,
enablementRefusalinplugins/routes.ts:255stops a non-admin granting MCP or putting a skill on a Bot they do not own, so granting is checked while acting is not. And open PR #31, which moves tool execution server-side, threadsactorIdthrough for audit only, so it carries the gap over rather than closing it.A PR follows. It asks the store's existing
get(actor, id), which already applies the same policy, once per surface rather than per route.