Skip to content

[BUG] Plugin hooks (tool.execute.before) don't intercept subagent tool calls - security policy bypass #5894

Closed

Description

@samuelho-dev

Description

Plugin hooks using tool.execute.before successfully block tool calls from primary agents but do not intercept tool calls from subagents spawned via the task tool. This allows security policies implemented via plugins to be completely bypassed.
Security Impact
This is a critical security issue for projects using plugins to enforce:

  • Code quality guardrails (e.g., enforcing GritQL for all code modifications)
  • Access control policies (e.g., blocking certain tools or commands)
  • Compliance requirements (e.g., audit logging of all tool executions)
    Attack vector: Any agent can bypass plugin restrictions by delegating work to a subagent using the task tool.
    Reproduction Steps

1. Create a plugin that blocks grep and glob tools

.opencode/plugin/test-guardrails.ts:
import type { Plugin } from "@opencode-ai/plugin"
export const TestGuardrails: Plugin = async ({ project }) => {
console.log("[TestGuardrails] Plugin loaded for project:", project.name)

return {
"tool.execute.before": async (input, output) => {
const toolName = (input?.tool ?? '').toLowerCase().trim()

  console.log("[TestGuardrails] Tool called:", toolName)
  
  // Block grep and glob
  if (toolName === 'grep' || toolName === 'glob') {
    throw new Error(`Tool '${toolName}' is BLOCKED by test guardrails`)
  }

export default TestGuardrails

2. Test with primary agent (WORKS ✅)

In OpenCode TUI, try to use grep directly

Search for files containing "Plugin"
Result: Tool is blocked with error message Tool 'grep' is BLOCKED by test guardrails

3. Test with subagent (FAILS ❌)

In OpenCode TUI, use task tool to invoke subagent

@general find all files containing the word "Plugin"
Result: Subagent successfully uses grep/glob tools without triggering the plugin hook
Expected Behavior

  • Plugin hooks should fire for all tool executions regardless of which agent makes the call
  • Subagents spawned via task tool should inherit plugin hooks from the project
  • Console should log: [TestGuardrails] Tool called: grep when subagent uses grep
    Actual Behavior
  • Plugin hooks only fire for primary agent tool calls
  • Subagents bypass plugin hooks entirely
  • No console logs appear when subagent uses blocked tools
  • Security policies can be completely circumvented
    Environment
  • OpenCode Version: 1.0.182 (latest as of 2024-12-21)
  • OS: macOS (also affects Linux)
  • Installation Method: npm/homebrew
  • Plugin Location: .opencode/plugin/ (project-level)

Workaround

According to OpenCode documentation (https://opencode.ai/docs/agents#tools), tool restrictions can be configured per-agent in opencode.json

Limitations of workaround:

  • Must manually configure every built-in subagent
  • Does not work for custom subagents created by users
  • Duplicates policy logic (once in plugin, once in agent config)
  • Not scalable for complex security policies
    Proposed Solution
    Option 1: Plugin Hook Propagation (Preferred)
  • Ensure tool.execute.before hooks fire for subagent tool calls
  • Plugins loaded at project level should apply to all agents in that project
  • Maintain consistent security boundary across agent hierarchy
    Option 2: Documentation Update
  • If subagent tool isolation is intentional, clearly document this limitation
  • Provide guidance on securing subagents via agent-level config
  • Add warning about security implications of using task tool with plugins
    Related Documentation
  • Plugins Documentation (https://opencode.ai/docs/plugins)
  • Agents Documentation (https://opencode.ai/docs/agents)
  • Tools Documentation (https://opencode.ai/docs/tools)
    Additional Context
    This issue was discovered while implementing a strict GritQL-only code modification policy. The goal was to enforce that all code search/modification must go through GritQL (structural code search) rather than raw grep/sed/edit operations for consistency and safety.
    The plugin works perfectly for primary agents but becomes ineffective when agents delegate to subagents, making the entire security layer bypassable.

OpenCode version

1.0.182

Steps to reproduce

1: Create a test plugin that blocks grep/glob tools

Create .opencode/plugin/test-block.ts in your project:
import type { Plugin } from "@opencode-ai/plugin"

export const TestBlock: Plugin = async () => {
  return {
    "tool.execute.before": async (input) => {
      const tool = input?.tool?.toLowerCase()
      if (tool === 'grep' || tool === 'glob') {
        throw new Error(`BLOCKED: ${tool} tool is not allowed`)
      }
    }
  }
}

export default TestBlock

2: Verify plugin blocks primary agent (expected to work)

  1. Start OpenCode in your project: opencode
  2. In the TUI, ask: "Search for all TypeScript files"
  3. Expected result: Error message appears: BLOCKED: glob tool is not allowed
  4. Observation: ✅ Plugin successfully blocks the primary agent

3: Test if plugin blocks subagent

  1. Still in OpenCode TUI, ask: "@general find all files containing the word Plugin"
  2. Expected result: Should show same error: BLOCKED: grep tool is not allowed
  3. Actual result: ❌ Subagent successfully searches and returns results
  4. Observation: Plugin hook did NOT fire - security policy was bypassed

Screenshot and/or share link

No response

Operating System

No response

Terminal

Ghostty

Activity

  1. github-actions commented on Dec 21, 2025

    @github-actions
    Contributor

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

    These issues all relate to security policies and permission systems being bypassed through agent delegation and task tool usage. Feel free to ignore if your specific case requires a different approach.

  2. rekram1-node commented on Dec 22, 2025

    @rekram1-node
    Contributor

    @samuelho-dev I dont think this is an issue. I think what's happening is the subagent is using the bash tool to call the tools, I just did your test cases and cannot replicate.

    If I navigate to the subagent session I see:

    Image

    Which makes sense.

  3. ArmirKS commented on Feb 13, 2026

    @ArmirKS

    hey, i looked into this and i think @rekram1-node is actually right about the subagent part.

    When the task tool spawns a subagent it runs its own session through the same prompt loop and plugins are loaded per-Instance so the hooks DO fire for the subagent's tools.
    What probably happened is the general agent used bash to run grep/glob as shell commands, so tool.execute.before only saw tool: "bash" not tool: "grep".

    While investigating i found a different place where hooks genuinely dont fire.

    batch.ts calls tool.execute() directly without going through Plugin.trigger().

    so any tools called via the batch tool completely bypass plugin hooks. you can verify this with a test:

    // plugin that captures hook calls to a file
    // then run BatchTool.execute with a "read" sub-tool
    // the capture file never gets created — hooks never fired
    

    also the tool.execute.before/after calls are duplicated across 3 separate places in prompt.ts (theres even a TODO comment saying "centralize invoke tool logic"). None of them pass agent info to the hook, so plugins cant even tell which agent triggered the call.

    Im already working on a fix for the batch blind spot + centralizing the hooks.

  4. github-actions commented on Apr 15, 2026

    @github-actions
    Contributor

    To stay organized issues are automatically closed after 90 days of no activity. If the issue is still relevant please open a new one.

  5. added a commit that references this issue on Jul 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions