[BUG] Custom agent 'deny' permissions in opencode.json are ignored when invoked via SDK #3977

Closed
opened 2026-02-16 17:42:09 -05:00 by yindo · 7 comments
Owner

Originally created by @ramarivera on GitHub (Dec 29, 2025).

Originally assigned to: @rekram1-node on GitHub.

Description

Custom agents defined in opencode.json with explicit deny permissions have those permissions completely ignored when the agent is invoked via the OpenCode SDK. The agent can freely use denied tools.

This is related to but distinct from:

  • #3575 (agent prompt overrides work, but permissions are ignored)
  • #5894 (plugin hooks don't intercept subagent calls)
  • #5965 (feature request for SDK-level permission overrides)

The difference here is that I'm not trying to override built-in agents or use plugins - I have a custom agent with explicit denies that are simply not enforced.

Security Impact

This is a security issue for pipelines using OpenCode SDK to run constrained agents. Denied tools can be used freely, bypassing intended restrictions.

Environment

  • OpenCode Version: 1.0.218
  • OS: macOS (darwin)
  • Installation: npm

Reproduction Steps

1. Define a custom agent with denied tools in opencode.json:

{
  "agents": {
    "edit": {
      "model": "anthropic/claude-sonnet-4-20250514",
      "permission": {
        "read": { "*": "allow" },
        "write": { "*": "deny" },
        "edit": { "*.mdx": "allow", "*": "deny" },
        "glob": { "*": "allow" },
        "grep": { "*": "allow" },
        "bash": { "*": "deny" },
        "pty_spawn": "deny",
        "pty_write": "deny",
        "pty_read": "deny",
        "pty_list": "deny",
        "pty_kill": "deny"
      }
    }
  }
}

2. Create the agent prompt in .opencode/agent/edit.md:

You are a documentation editor focused on MDX files.
You should ONLY use read, edit, glob, and grep tools.
DO NOT use bash, PTY, or write tools.

3. Invoke the agent via SDK:

import { Session, Permissions } from "@opencode-ai/sdk"

const session = await Session.start({
  path: projectPath,
  agent: "edit"
})

const response = await session.prompt({
  thread: "main",
  text: "List all open beads issues"
})

4. Observe the agent using denied tools:

✓ pty_spawn: pty_spawn({"command":"bd","args":["list","--status=open"...) (112ms)
✓ pty_write: pty_write({"id":"pty_b4e0438a","data":"bd list --status=i...) (77ms)
✓ pty_read: pty_read({"id":"pty_b4e0438a","limit":50}) (6ms)

Expected Behavior

The agent should be blocked from using pty_spawn, pty_write, pty_read (and other denied tools). The SDK should enforce permissions defined in opencode.json.

Actual Behavior

The agent freely uses all denied tools. Permission denies have no effect when the agent is invoked via SDK.

Additional Context

The agent prompt correctly includes the custom .opencode/agent/edit.md content, so the agent definition is being loaded. Only the permissions are ignored.

This was discovered in a documentation generation pipeline where the edit agent was supposed to be constrained to read/edit/glob/grep only, but was found using PTY tools to run shell commands.


Note: Similar issues were searched before filing. This report was created with AI assistance.

Originally created by @ramarivera on GitHub (Dec 29, 2025). Originally assigned to: @rekram1-node on GitHub. ## Description Custom agents defined in `opencode.json` with explicit `deny` permissions have those permissions **completely ignored** when the agent is invoked via the OpenCode SDK. The agent can freely use denied tools. This is related to but distinct from: - #3575 (agent prompt overrides work, but permissions are ignored) - #5894 (plugin hooks don't intercept subagent calls) - #5965 (feature request for SDK-level permission overrides) The difference here is that I'm **not** trying to override built-in agents or use plugins - I have a **custom agent** with explicit denies that are simply not enforced. ## Security Impact This is a security issue for pipelines using OpenCode SDK to run constrained agents. Denied tools can be used freely, bypassing intended restrictions. ## Environment - **OpenCode Version:** 1.0.218 - **OS:** macOS (darwin) - **Installation:** npm ## Reproduction Steps ### 1. Define a custom agent with denied tools in `opencode.json`: ```json { "agents": { "edit": { "model": "anthropic/claude-sonnet-4-20250514", "permission": { "read": { "*": "allow" }, "write": { "*": "deny" }, "edit": { "*.mdx": "allow", "*": "deny" }, "glob": { "*": "allow" }, "grep": { "*": "allow" }, "bash": { "*": "deny" }, "pty_spawn": "deny", "pty_write": "deny", "pty_read": "deny", "pty_list": "deny", "pty_kill": "deny" } } } } ``` ### 2. Create the agent prompt in `.opencode/agent/edit.md`: ```markdown You are a documentation editor focused on MDX files. You should ONLY use read, edit, glob, and grep tools. DO NOT use bash, PTY, or write tools. ``` ### 3. Invoke the agent via SDK: ```typescript import { Session, Permissions } from "@opencode-ai/sdk" const session = await Session.start({ path: projectPath, agent: "edit" }) const response = await session.prompt({ thread: "main", text: "List all open beads issues" }) ``` ### 4. Observe the agent using denied tools: ``` ✓ pty_spawn: pty_spawn({"command":"bd","args":["list","--status=open"...) (112ms) ✓ pty_write: pty_write({"id":"pty_b4e0438a","data":"bd list --status=i...) (77ms) ✓ pty_read: pty_read({"id":"pty_b4e0438a","limit":50}) (6ms) ``` ## Expected Behavior The agent should be **blocked** from using `pty_spawn`, `pty_write`, `pty_read` (and other denied tools). The SDK should enforce permissions defined in `opencode.json`. ## Actual Behavior The agent freely uses all denied tools. Permission denies have no effect when the agent is invoked via SDK. ## Additional Context The agent prompt correctly includes the custom `.opencode/agent/edit.md` content, so the agent definition **is** being loaded. Only the permissions are ignored. This was discovered in a documentation generation pipeline where the edit agent was supposed to be constrained to read/edit/glob/grep only, but was found using PTY tools to run shell commands. --- *Note: Similar issues were searched before filing. This report was created with AI assistance.*
yindo closed this issue 2026-02-16 17:42:09 -05:00
Author
Owner

@github-actions[bot] commented on GitHub (Dec 29, 2025):

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

  • #3575: BUG: Overriding built-in agents, doesn't allow overriding permissions (similar issue with agent permission overrides being ignored)
  • #5894: [BUG] Plugin hooks (tool.execute.before) don't intercept subagent tool calls - security policy bypass (related security issue with permissions/tools being bypassed)
  • #5965: [FEATURE]: SDK-level permission overrides for tools (related feature request for programmatic permission control via SDK)

Feel free to ignore if none of these address your specific case.

@github-actions[bot] commented on GitHub (Dec 29, 2025): This issue might be a duplicate of existing issues. Please check: - #3575: BUG: Overriding built-in agents, doesn't allow overriding permissions (similar issue with agent permission overrides being ignored) - #5894: [BUG] Plugin hooks (tool.execute.before) don't intercept subagent tool calls - security policy bypass (related security issue with permissions/tools being bypassed) - #5965: [FEATURE]: SDK-level permission overrides for tools (related feature request for programmatic permission control via SDK) Feel free to ignore if none of these address your specific case.
Author
Owner

@rekram1-node commented on GitHub (Dec 29, 2025):

Where is the permission defined and where are you running the sdk?

@rekram1-node commented on GitHub (Dec 29, 2025): Where is the permission defined and where are you running the sdk?
Author
Owner

@rekram1-node commented on GitHub (Dec 29, 2025):

fairly certain it's because you aren't passing a directory?

      query: {
        directory: string,
      },

The server is "global" so if u wanna make it load the config for a specific project/dir, youll need to send the directory in requests

@rekram1-node commented on GitHub (Dec 29, 2025): fairly certain it's because you aren't passing a directory? ``` query: { directory: string, }, ``` The server is "global" so if u wanna make it load the config for a specific project/dir, youll need to send the directory in requests
Author
Owner

@ramarivera commented on GitHub (Dec 29, 2025):

oh I see, so is it the same to send the directory via client creation than it is to do it via query?

createOpencodeClient

?

@ramarivera commented on GitHub (Dec 29, 2025): oh I see, so is it the same to send the directory via client creation than it is to do it via query? ``` createOpencodeClient ``` ?
Author
Owner

@ramarivera commented on GitHub (Dec 29, 2025):

And does sending directory affect which paths the agent can traverse, or just which opencode.json to load? 🤔

@ramarivera commented on GitHub (Dec 29, 2025): And does sending directory affect which paths the agent can traverse, or just which opencode.json to load? 🤔
Author
Owner

@rekram1-node commented on GitHub (Dec 30, 2025):

Yeah you can set it at the interaction level like .prompt, or whatever, or you can set it for all requests for the client:

createOpencodeClient(
{
directory: "... the dir ...."
}
)

@rekram1-node commented on GitHub (Dec 30, 2025): Yeah you can set it at the interaction level like .prompt, or whatever, or you can set it for all requests for the client: createOpencodeClient( { directory: "... the dir ...." } )
Author
Owner

@ramarivera commented on GitHub (Dec 30, 2025):

Closing with working as expected it seems

@ramarivera commented on GitHub (Dec 30, 2025): Closing with working as expected it seems
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: anomalyco/opencode#3977