Skip to content

External plugin tools bypass configured permissions #50652

Open

Description

@PanAchy

Description

Tools registered by external plugins execute without applying their configured permission action. The same bypass occurs when a plugin tool is called through Code Mode.

A tool with options.permission: "demo_create_work_item" executes immediately even when that action is configured to ask. It should prompt before calling the executor.

The plugin tool declares:

options: { namespace: "demo", permission: "demo_create_work_item" }

The project config contains the matching action and resource:

{
  "permissions": [
    { "action": "demo_create_work_item", "resource": "*", "effect": "ask" }
  ]
}

Plugins

None

OpenCode version

V2, reproduced at 956de96d8b8dbb6431cc555bded5a2a84c1f6134 and confirmed unchanged on current v2

Steps to reproduce

  1. Register an external plugin tool with options.permission set to demo_create_work_item.
  2. Configure { "action": "demo_create_work_item", "resource": "*", "effect": "ask" } in opencode.jsonc.
  3. Invoke the tool directly or through Code Mode.
  4. Observe that it executes without a permission prompt.

Screenshot and/or share link

Operating System

Arch Linux (Omarchy)

Terminal

Ghostty

Activity

  1. github-actions commented on Sep 22, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

  2. soar commented on Oct 11, 2026

    @soar

    This is still an issue and needs to be fixed, verified on v2.0.25

  3. rekram1-node commented on Oct 23, 2026

    @rekram1-node
    Contributor

    On V2 this is currently expected behavior, though I get why it's surprising.

    The reason we don't auto prompt: for an arbitrary plugin tool, opencode has no idea which inputs matter for a permission decision. Built-in tools know this. shell asks about the command, edit asks about the file path, and "always allow" saves that pattern. For a plugin tool we could prompt on every call, but then "always allow" gets messy. Which parameters are you saving approval for? All of them? Just the tool name? There isn't a good generic answer, so right now the tool itself decides what to ask about.

    So for now, call ctx.permission.assert inside your tool's execute. It's available from v2.0.29 and goes through the same rules, saved approvals and prompt as built-in tools, for direct calls and Code Mode:

    execute: async (input, context) => {
      await ctx.permission.assert({
        sessionID: context.sessionID,
        agent: context.agent,
        source: { type: "tool", messageID: context.messageID, id: context.id },
        action: "demo_create_work_item",
        resources: ["*"],
      })
      // ...
    }

    Use resources (and save) to say what's being approved, for example a project or environment name, so "always allow" saves something meaningful. Docs are in the plugin guide under Permissions.

    We'll probably change this so plugin tools get sensible permission checks by default, so I'm leaving this open for now.

  4. opencodejr commented on Oct 28, 2026

    @opencodejr

    Thanks for the detailed report! What you're asking for is that a plugin tool's declared options.permission be checked automatically when the tool runs, so an ask rule prompts before the tool's execute is called, both for direct calls and through Code Mode.

    As mentioned above, this is how v2 currently works by design. options.permission only decides whether a tool is hidden when its action is denied outright. When the tool runs, the tool itself has to request permission. Until that changes, calling ctx.permission.assert at the start of your tool's execute (v2.0.29+) gives you the normal rules, saved approvals and the approval prompt, for direct calls and Code Mode:

    await ctx.permission.assert({
      sessionID: context.sessionID,
      agent: context.agent,
      source: { type: "tool", messageID: context.messageID, id: context.id },
      action: "demo_create_work_item",
      resources: ["*"],
    })

    We're keeping this issue open as the request for plugin tools to get permission checks by default, and maintainers will consider it.

    Related:

  5. Project579 commented on Nov 7, 2026

    @Project579

    On "sensible permission checks by default": what if a plugin tool that never calls ctx.permission.assert gets a tool-wide check against its options.permission key, but without "always allow"? ask would prompt once per call, deny would block, and nothing would persist, so there's no question of which parameters to save.

    Tools that do their own assert would declare that, e.g. options.experimental.permission: { self: true }, and skip the host check, so there's no double prompt.

    I already have this working on a branch, if the direction sounds right.

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

Metadata

Metadata

Assignees

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