Skip to content

Any signed-in user can act as any Bot: canRunAgent is defined, tested, and never called #35

Description

@beardthelion

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions