Skip to content

Uninstalling a skill leaves its grants, so the next skill written under that name inherits every Bot it was on #557

Description

@zopeVaibhav

A skill's grants outlive the skill. uninstallSkill deletes the row in skills and nothing else (server/src/plugins/store.ts:2423), and a grant is a row in plugin_grants whose ref is plain text with no foreign key (server/src/db/schema/plugins.ts:281). Every grant that put the skill on a Bot stays behind after the skill is gone, and it stays harmless only until somebody reuses the name.

How the name comes back

Slugs are shared across the deployment, and a free one is anybody's. skillRefusal treats a slug with no row as a new skill and lets any signed-in person write it (server/src/plugins/routes.ts:131). listForAgent then resolves a Bot's skill grants by slug alone, without asking whose skill answers to that slug now (server/src/plugins/store.ts:2585), so the Bot is offered whatever the new author wrote.

That reaches the model in two places. The / menu in a channel is built from GET /api/plugins/for/:agentId (app/src/lib/plugins/skill-commands.ts:9), and on send the chosen chip's instructions are put in front of the run (app/src/components/channels/channel-chat.tsx:851). When tool selection runs for that Bot, the skill's title and summary also go into the selection prompt (server/src/plugins/selection.ts:98).

What it gets around

Asked for directly, the grant route refuses this outcome: a person may put only their own skill, and only on a Bot they own, never on a Bot the deployment shares (server/src/plugins/routes.ts:780). The schema says a person's skill is "theirs alone" (server/src/db/schema/plugins.ts:197). The tenant package already guards against this on its own path, granting a package skill only when the package actually wrote it (server/src/tenant-package.ts:914). Uninstalling from the Skills page is the path without that guard.

Reproduction

Driven through createPluginRoutes as non-administrators against a migrated test database, on main at a96d88c:

  1. Alice writes standup, grants it to her own Bot, and uninstalls it. All three answer 200.
  2. Bob writes a skill named standup with his own instructions. 200.
  3. Bob asks to grant standup to Alice's Bot. 403, as it should be.
  4. GET /for/<alice's bot> lists standup with Bob's instructions.

The deployment-wide version is worse. An administrator puts a deployment skill on a shared Bot and uninstalls it. Alice writes a skill under the same name, and her direct grant to the shared Bot is refused with 403. The shared Bot then lists Alice's instructions for everybody who uses it. Afterwards all three original grants are still in plugin_grants.

What a fix probably has to do

Remove a skill's grants in the same transaction that removes the skill, and delete the rows that earlier uninstalls already left, because deployments that have uninstalled a granted skill are exposed today. A grant naming no skill is never listed or offered, so deleting it changes nothing a person can see. Only skill grants should go: mcp and bot grants share the table.

Filtering at read time instead would also hide skills an administrator deliberately put on a Bot the skill's author does not own, which the grant route allows (server/src/plugins/routes.ts:765).

Severity

Medium. It needs a skill to have been uninstalled while still granted, and the new author has to choose that slug, which is easy for a deployment skill because everybody can see those names. Past that point, any signed-in person can put instructions of their choosing on a Bot other people use, around the ownership rule, and the trail shows no grant because none was made. It does not widen what a Bot can do: a skill can only ask for tools the Bot already holds, and every call is still decided and audited.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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