opencode icon indicating copy to clipboard operation
opencode copied to clipboard

[BUG] Bash permission patterns with wildcards don't match commands with flags (e.g., "kill *" doesn't match "kill -9 PID")

Open ramarivera opened this issue 2 months ago • 12 comments

Description

Bash permission patterns using wildcards appear to not match commands that include flags or arguments in certain positions.

Configuration:

"permission": {
  "bash": {
    "kill *": "ask",
    "pkill *": "ask"
  }
}

Expected behavior: Running kill -9 44165 should prompt for permission ("ask")

Actual behavior: The command executes immediately without prompting, even though the pattern "kill *" should match kill -9 44165

Possible cause (unconfirmed): After investigating the codebase, we noticed that the bash tool in packages/opencode/src/tool/bash.ts uses a whitelist of treesitter node types when extracting command tokens for permission matching:

if (
  child.type !== "command_name" &&
  child.type !== "word" &&
  child.type !== "string" &&
  child.type !== "raw_string" &&
  child.type !== "concatenation"
) {
  continue;
}

It's possible that flags like -9 are parsed as argument nodes by treesitter, which would cause them to be skipped during extraction. This could result in the command string being truncated to just "kill", which wouldn't match the pattern "kill *" (due to the literal space). The system might then fall back to the default "*": "allow" rule.

Note: This is speculative based on code review - we haven't confirmed this is the definitive root cause.

OpenCode version

1.0.224

Steps to reproduce

  1. Add this to opencode.json:
"permission": {
  "bash": {
    "kill *": "ask",
    "pkill *": "ask"
  }
}
  1. Start OpenCode and ask the agent to kill a process by PID
  2. Observe that kill -9 <PID> executes without prompting for permission

Screenshot and/or share link

No response

Operating System

macOS

Terminal

Ghostty


🤖 This content was generated with AI assistance using Claude Opus 4.5 (Thinking).

ramarivera avatar Jan 02 '26 18:01 ramarivera

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

  • #5330: More explicit bash command permissions - discusses wildcard permission patterns matching commands with additional arguments/flags

github-actions[bot] avatar Jan 02 '26 18:01 github-actions[bot]

I've also experienced this. for me... wildcard or not, this issue persists.

  "permission": {
    "bash": {
      "git ls-files": "allow",
      "git ls-files*": "allow",
      "git ls-files *": "allow",
    }
  },

From the export


User

run git ls-files


Assistant (Build · gpt-5-mini (Github Copilot) · 13.1s)

I'll list the repository's tracked files by running git ls-files. Running the command now.

Tool: bash

**Input:**
```json
{
  "command": "git ls-files",
  "description": "List tracked files in the git repository",
  "timeout": 120000
}

Error:

Error: The user rejected permission to use this specific tool call.
Image Image

I can revert back to v1.0.223 until this is fixed, and simply include "git ls-files": "allow",, which works as expected.

I have a feeling this much more granular syntax is going to be challenging, and perhaps increase custom permissions exponentially, if this continues as such. For example, i'm happy to allow opencode to call any git read-only operations it needs, but want it to always ask before doing any writing ops. Any chance adoption of a standard like regex is on the roadmap?

scottrbaxter avatar Jan 05 '26 00:01 scottrbaxter

@scottrbaxter can you show your config?

opencode debug config

rekram1-node avatar Jan 05 '26 00:01 rekram1-node

@scottrbaxter can you show your config?

opencode debug config

I shared the relevant part of my config above... is there something else specific to this issue that you need? i'd rather not just dump my entire config into a public forum.

  "permission": {
    "bash": {
      "git ls-files": "allow",
      "git ls-files*": "allow",
      "git ls-files *": "allow",
    }
  },

scottrbaxter avatar Jan 05 '26 02:01 scottrbaxter

Well ideally all your agent configs, you can like take out irrelevant stuff but ideally all the agent configs and the global permissions, if u do a debug config I can see the fully resolved permissions which helps debug.

rekram1-node avatar Jan 05 '26 02:01 rekram1-node

@rekram1-node no agent configs and i can still reproduce this issue. also, think i narrowed it down to the appended global wildcard.

  "permission": {
    "bash": {
      "git ls-files": "allow",
      "git ls-files*": "allow",
      "git ls-files *": "allow",
      "*": "ask" // last rule
    }
  },

simply comment out "*": "ask" in permission.bash and the command is allowed.

here's a config (only provider is removed) which is able to reproduce this issue:

{
  "$schema": "https://opencode.ai/config.json",
  "theme": "palenight",
  "keybinds": {
    "leader": "ctrl+x",
    "app_exit": "ctrl+q,q",
    "editor_open": "<leader>e",
    "theme_list": "<leader>t",
    "sidebar_toggle": "<leader>b",
    "scrollbar_toggle": "none",
    "username_toggle": "none",
    "status_view": "<leader>s",
    "session_export": "<leader>x",
    "session_new": "<leader>n",
    "session_list": "<leader>l",
    "session_timeline": "<leader>g",
    "session_fork": "none",
    "session_rename": "none",
    "session_share": "none",
    "session_unshare": "none",
    "session_interrupt": "escape",
    "session_compact": "<leader>c",
    "messages_page_up": "pageup",
    "messages_page_down": "pagedown",
    "messages_half_page_up": "ctrl+alt+u",
    "messages_half_page_down": "ctrl+alt+d",
    "messages_first": "ctrl+g,home",
    "messages_last": "ctrl+alt+g,end",
    "messages_next": "none",
    "messages_previous": "none",
    "messages_last_user": "none",
    "messages_copy": "<leader>y",
    "messages_undo": "<leader>u",
    "messages_redo": "<leader>r",
    "messages_toggle_conceal": "<leader>h",
    "tool_details": "none",
    "model_list": "<leader>m",
    "model_cycle_recent": "f2",
    "model_cycle_recent_reverse": "shift+f2",
    "model_cycle_favorite": "none",
    "model_cycle_favorite_reverse": "none",
    "command_list": "ctrl+p",
    "agent_list": "<leader>a",
    "agent_cycle": "tab",
    "agent_cycle_reverse": "shift+tab",
    "variant_cycle": "ctrl+t",
    "input_clear": "ctrl+c",
    "input_paste": "ctrl+v",
    "input_submit": "return",
    "input_newline": "shift+return,ctrl+return,alt+return,ctrl+j",
    "input_move_left": "left,ctrl+b",
    "input_move_right": "right,ctrl+f",
    "input_move_up": "up",
    "input_move_down": "down",
    "input_select_left": "shift+left",
    "input_select_right": "shift+right",
    "input_select_up": "shift+up",
    "input_select_down": "shift+down",
    "input_line_home": "ctrl+a",
    "input_line_end": "ctrl+e",
    "input_select_line_home": "ctrl+shift+a",
    "input_select_line_end": "ctrl+shift+e",
    "input_visual_line_home": "alt+a",
    "input_visual_line_end": "alt+e",
    "input_select_visual_line_home": "alt+shift+a",
    "input_select_visual_line_end": "alt+shift+e",
    "input_buffer_home": "home",
    "input_buffer_end": "end",
    "input_select_buffer_home": "shift+home",
    "input_select_buffer_end": "shift+end",
    "input_delete_line": "ctrl+shift+d",
    "input_delete_to_line_end": "ctrl+k",
    "input_delete_to_line_start": "ctrl+u",
    "input_backspace": "backspace,shift+backspace",
    "input_delete": "ctrl+d,delete,shift+delete",
    "input_undo": "ctrl+-,super+z",
    "input_redo": "ctrl+.,super+shift+z",
    "input_word_forward": "alt+f,alt+right,ctrl+right",
    "input_word_backward": "alt+b,alt+left,ctrl+left",
    "input_select_word_forward": "alt+shift+f,alt+shift+right",
    "input_select_word_backward": "alt+shift+b,alt+shift+left",
    "input_delete_word_forward": "alt+d,alt+delete,ctrl+delete",
    "input_delete_word_backward": "ctrl+w,ctrl+backspace,alt+backspace",
    "history_previous": "shift+up",
    "history_next": "shift+down",
    "session_child_cycle": "<leader>right",
    "session_child_cycle_reverse": "<leader>left",
    "session_parent": "<leader>up",
    "terminal_suspend": "ctrl+z",
    "terminal_title_toggle": "none",
    "tips_toggle": "<leader>h"
  },
  "share": "disabled",
  "autoupdate": false,
  "disabled_providers": [
    "opencode",
    "ollama-cloud",
    "github-copilot"
  ],
  "permission": {
    "bash": {
      "git ls-files": "allow",
      "git ls-files*": "allow",
      "git ls-files *": "allow",
      "*": "ask"
    }
  },
  "agent": {},
  "mode": {},
  "plugin": [],
  "command": {},
  "username": "scott"
}

scottrbaxter avatar Jan 05 '26 05:01 scottrbaxter

@scottrbaxter try this instead:

"permission": {
    "bash": {
      "*": "ask",  // last rule is now first rule
      "git ls-files": "allow",
      "git ls-files*": "allow",
      "git ls-files *": "allow",
    }
  },

rekram1-node avatar Jan 05 '26 06:01 rekram1-node

@rekram1-node here are my findings with the wildcard set first (top of the list).

Allowed without prompt, and does not permit args without being asked first.

    "bash": {
      "*": "ask"
      "git ls-files": "allow",
    }

Allowed without prompt (this seems to match the command itself and include any args passed).

    "bash": {
      "*": "ask"
      "git ls-files*": "allow",
    }

What if i want to pass an arg to a command that begins with the same character(s) as other commands (e.g. w)? with this logic, i have to allow all executables that begin with w... or i'm stuck writing new rules foreach and every arg. not ideal.


Asks to allow.

    "bash": {
      "*": "ask"
      "git ls-files *": "allow",
    }

I believe this is the expected response, though does make this new change a lot more cumbersome and less intuitive to the user. I'll now have to reconsider a ton of commands in this list, just so that i can pass args (e.g. allow read only ops, ask for write ops) to them as intended. This list will easily become 10x longer.

Still seems like a great use case for regex patterns. Any chance that's being considered?

scottrbaxter avatar Jan 05 '26 21:01 scottrbaxter

I also think this is very unintuitive (and dangerous). I can only auto-allow flags in commands if there's no space before the wildcard. But this is risky because multiple commands may share that prefix.

If my config has "ls*": "allow". It will allow running lstmeval (for example). I did test this.

But if I use "ls *": "allow" (with space). It now asks for permissions when passing any flags to ls like ls -la.

So I can either allow all the commands starting with ls, even if the command is lsyouarehacked; or manually approve all commands.

polyrand avatar Jan 09 '26 18:01 polyrand

I found a way to get the expected behaviour, which is allowing both the bare command and the command with *. For example:

"ls": "allow",
"ls *": "allow",
"grep": "allow",
"grep *": "allow",

polyrand avatar Jan 09 '26 22:01 polyrand

This change is a pretty big rate limiter for moving from 1.0.223 to 1.1+. I could refactor all of these in my config, but i wonder if this really is going to be a permanent change for the seeable future? There are clearly going to be limitations and lots of added entries with trial and error to correctly pattern match, e.g. if a user wants just a few args for a command allowed to run vs asking or denying. also the wildcard at the top seems counter-intuitive... though maybe that's on me comparing, this logic to systems like iptables chains.

Is there any reconsideration on how this feature/change is being implemented? i could hold off on 1.0.233 while it's being reconsidered/refactored, if so.. but would like to better understand what the target state will be.

scottrbaxter avatar Jan 12 '26 16:01 scottrbaxter

ill discuss further with dax

rekram1-node avatar Jan 12 '26 17:01 rekram1-node

Image

Here is a basic SAFE test case, I think this works as expected rn?

My permissions:

 "permission": {
    "bash": {
      "ls *": "ask",
    },
  },

Notice that I got prompted for ls AND ls -al

rekram1-node avatar Jan 13 '26 06:01 rekram1-node

Image

Exact test case here too

rekram1-node avatar Jan 13 '26 06:01 rekram1-node

Here is a basic SAFE test case, I think this works as expected rn?

The problem is with "allow". Not "ask".

polyrand avatar Jan 13 '26 07:01 polyrand

Image

still works

Going to mark this as completed now believe my wildcarding fix fixed this

rekram1-node avatar Jan 13 '26 16:01 rekram1-node

Going to mark this as completed now believe my wildcarding fix fixed this

Awesome, thanks! I checked, and now the behaviour seems correct in v1.1.18

polyrand avatar Jan 13 '26 16:01 polyrand

Initial tests confirmed working as expected, thank you @rekram1-node!

scottrbaxter avatar Jan 13 '26 22:01 scottrbaxter