[FEATURE] add `exec` tool as more secure alternative to bash tool
[N.B. posted a similar issue at https://github.com/anthropics/claude-code/issues/6046 since Claude Code has a very similar problem]
TL;DR summary
I propose a new exec tool which invokes subprocess commands directly, not via bash or any other shell.
Rationale: bash tool is inherently unsafe
https://opencode.ai/docs/permissions/#bash mentions that wildcard patterns can restrict specific commands:
https://github.com/sst/opencode/blob/6456350564fd53a7aa93374a5e5d4c529d9a2f0f/packages/web/src/content/docs/docs/permissions.mdx?plain=1#L101-L112
but it doesn't acknowledge the risks of allowing the LLM to run arbitrary code based on simple substring- or pattern-matching. In fact, it doesn't explicitly mention the possibility of allowing more complicated wildcards than just *, although this Terraform example in the section above shows that it is supported:
https://github.com/sst/opencode/blob/6456350564fd53a7aa93374a5e5d4c529d9a2f0f/packages/web/src/content/docs/docs/permissions.mdx?plain=1#L76-L80
For comparison, https://docs.anthropic.com/en/docs/claude-code/iam#tool-specific-permission-rules states:
Claude Code is aware of shell operators (like
&&) so a prefix match rule likeBash(safe-cmd:*)won’t give it permission to run the commandsafe-cmd && other-cmd
However even this is very vague and fundamentally unsafe. It's impossible for any kind of pattern-matching approach to guarantee that arbitrary shell code is both secure and not bypassing file access controls imposed on the agent.
For example, even if you ban operators like &&, ||, and ;, the agent can still construct Bash commands to circumvent these which would be allowed by innocent looking patterns like npm run test * or git status *, e.g.
npm run test <(rm -rf $HOME/*)
git status $(rm -rf $HOME/*)
This issue is demonstrated in depth by the report in https://github.com/anthropics/claude-code/issues/4956 which lists many other techniques for bypassing basic checks.
The above problems are a symptom of the fundamentally flawed approach of only allowing command invocation via a bash tool. It has been well-known for decades that allowing semi-arbitrary shell code is a significant vector for attacks and accidental side effects.
Proposed (partial) solution: a new exec tool
It is also well-established for decades that when building mechanisms which launch new processes, it's best practice to simply avoid using shells to launch them.
For example, Python defaults to invoking subprocess commands directly rather than via a shell. Similarly, Node.js offers child_process.execFile() which does not spawn a shell by default.
So in many cases, a similar approach of avoiding usage of bash or any other shell should work fine for opencode.
Therefore I propose a new exec tool which invokes commands directly, not via bash or any shell.
For example, with this new tool, even if combined with a very limited * wildcard scheme, if we put the following in opencode.json:
{
"$schema": "https://opencode.ai/config.json",
"permission": {
"bash": {
"npm run test *": "allow",
"*": "ask"
}
}
}
then it would guarantee that anything after the npm run test would be supplied as arguments to the npm command and therefore could not directly launch any other subprocess. So npm run test <(rm -rf $HOME/*) would have the equivalent effect of running this from a shell:
npm run test '<(rm' '-rf' '$HOME/*)'
which doesn't make sense, but at least it would be safe.
Beyond an exec tool: the need for a full sandboxing solution
Whilst the above proposal for a new exec tool is serious, would certainly offer opencode users better protection in many circumstances, and should be easy to implement, it can never be a full solution. For example, here are a few ways to edit an arbitrary file without requiring any shell at all:
sed -i -e '... some editing code ...' foo.txt
python -c '... some editing code ...'
perl -i -pe '... some editing code ...' foo.txt
and of course the latter two examples allow not just file editing but arbitrary execution.
And it doesn't always make sense to add these commands to the deny list because there are perfectly valid use cases for invoking those. There is no pattern-matching method which can determine whether one of these commands is legitimate or not.
In general, attempting to determine the safety or side effects of arbitrary commands by performing pattern matching or even complex static analysis on those commands is doomed to fail. A much more effective approach is to ensure that the commands are run within a sandboxed environment which limits what can be done at the OS level.
If opencode is interested in stealing ideas from Claude Code, it's worth noting that one proposal along these lines has already been made:
- https://github.com/anthropics/claude-code/issues/4320
And Gemini CLI natively supports running in a container.
However there are probably other approaches which could also work, e.g. on Linux apparmor and selinux are alternative sandboxing mechanisms.
This issue might be a duplicate of existing issues. Please check:
- #1538: Similar security concerns about bash tool safety, proposes command filtering/blocking for harmful patterns
- #1229: Discusses security issues with destructive commands like
rm -rfand proposes a command block list approach
Feel free to ignore if none of these address your specific case.
have you looked at how our permissions check on the bash tool is implemented? it's not a simple string check we parse the command with treesitter and check each subcommand against what's configured
not saying this is 100% effective but not sure if you were aware of that
Hey @thdxr thanks for the reply, and indeed I wasn't aware - that's really cool, and probably far better than some or perhaps even all of the competitors!
That said, I'm pretty sure you're right that this isn't 100% effective, and I have many doubts it ever could be. For example, from just a quick scan of the code, I see that it relies on realpath to figure out whether a command argument references a restricted path, but this doesn't take into account things like parameter expansion. So if I'm reading it right, even rm $HOME/foo would be allowed because realpath won't expand $HOME.
I'm pretty sure that trying to make arbitrary shell 100% safe is an impossible game of whack-a-mole which is highly dependent on the individual user's needs and environment. In the above example, the fundamental problem is that it relies on static analysis, but bash is a dynamic language and its runtime effects can't be fully predicted in a reliable fashion purely through static analysis.
Don't get me wrong - of course I'm not proposing to remove the bash tool, as that would cripple functionality. And it's definitely great to apply best efforts to improving security of the bash tool. But reducing attack surface is a well-known principle of security best practices, so I think being able to avoid shell altogether when it's not needed is an extra weapon in the arsenal. And that's what an exec tool would allow.
@aspiers just btw:
"permission": {
"bash": {
"*": "deny",
"npm run test *": "allow",
},
},
would block: npm run test <(rm -rf $HOME/*)
Yeah I realised that as soon as I looked at your treesitter code. As I said, it's very nice, but bash + treesitter is still an enormous attack surface when compared to exec(3), so would be great to add the latter as an alternative (and presumably pretty easy to do).
what about an additional AI check in a separate thread with small_model + json mode? "check if this bash command breaks the rules above and/or if the description is misleading", with an optional user defined prompt
would be useful e.g. when the model keeps trying to edit in plan mode by abusing bash
I'm not sure if the false positive rate would be tolerable tho
what about an additional AI check in a separate thread with small_model + json mode? "check if this bash command breaks the rules above and/or if the description is misleading", with an optional user defined prompt
would be useful e.g. when the model keeps trying to edit in plan mode by abusing bash
No, please let's not go down this road.
I'm not sure if the false positive rate would be tolerable tho
The accuracy wouldn't be tolerable. LLMs are nowhere near reliable enough for acting as security mechanisms, e.g. take a look at https://simonwillison.net/2025/Aug/15/the-summer-of-johann/
Additionally, using an LLM would be a needlessly heavyweight solution given that far cheaper and more reliable ones exist, such as the exec tool I propose here, and the treesitter approach which opencode already has.
The way I understand your idea, adding a single finegrained bash permission rule will force the model to switch from its typical workflow (long one shot scripts with io redirection) to long chains of isolated tool calls
A sandbox takes like 5 minutes to set up, why bother tanking the performance of the model for slightly safer execution without one?
LLMs are nowhere near reliable enough for acting as security mechanisms
I am mostly talking about mitigating poor instruction following, not prompt injection;
take a look at
I don't see any cases of LLM-based safety mechanisms failing in the post you provided; there's quite a few papers on LLM-as-a-guard being a decent idea, eg https://arxiv.org/abs/2308.07308 and https://arxiv.org/abs/2312.06674
accuracy wouldn't be tolerable
Accuracy doesn't really need to be high, only the precision, as this would be an additional check
needlessly heavyweight solution
Requests would be comparable to title generation in terms of token usage, the only problem is that it would slow down the tools; it's probably only feasible if you have a fast enough backend (cerebras/local) for the guard model OR a really slow primary agent model
This could probably be implemented as a plugin though
The way I understand your idea, adding a single finegrained bash permission rule will force the model to switch from its typical workflow (long one shot scripts with io redirection) to long chains of isolated tool calls
Not really, but I can see how I might have accidentally given that impression (sorry for that). The idea is not so much to substitute bash tool calls for exec tool calls when there's a genuine need for bash; instead, it's to avoid bash when it's completely overkill and exec is enough.
So for example, if all that's needed is to run npm run test, most likely invoking a shell is way overkill, and instead invoking exec to run npm directly will dramatically shrink the attack surface (and avoid consuming unnecessary system resources as a minor but nice side effect).
A sandbox takes like 5 minutes to set up, why bother tanking the performance of the model for slightly safer execution without one?
I'm totally in favour of sandboxes in general. But they don't always take 5 minutes to set up, plus sometimes there are technical constraints which could make them hard or impossible to set up at all. So the idea is to give the user multiple good options. If they can run in a sandbox then great, use that. But whether they can or not, it's always good to design efficient secure systems which minimise attack surfaces through the Principle of Least Privilege, and an exec tool would be one simple contribution in that direction.
LLMs are nowhere near reliable enough for acting as security mechanisms
I am mostly talking about mitigating poor instruction following, not prompt injection;
OK, then we are probably talking about different things. The main motivation for this proposal is improved security by eliminating the use of shells in the cases where they're not needed.
take a look at
I don't see any cases of LLM-based safety mechanisms failing in the post you provided; there's quite a few papers on LLM-as-a-guard being a decent idea, eg arxiv.org/abs/2308.07308 and arxiv.org/abs/2312.06674
Even if research indicates that all of this is feasible, there are still several concerns:
- The papers you cite are about safeguards against human agents trying to get an LLM to respond in natural language on topics such as illegal weapons / violence / hate; however here I'm talking about defending against an AI agent being tricked by an LLM into invoking malicious commands. That's very different.
- The research is new and probably not proven yet. E.g. the first paper says "LLM SELF DEFENSE reduces attack success rate to virtually 0", which effectively means that it makes attacks harder but still not impossible.
- There are also papers on LLM scheming, e.g. https://arxiv.org/pdf/2412.04984, so it's conceivable that an LLM could pretend to do a good job of being a safety guard in order to be given that job and then abuse that position of power.
- It doesn't negate the importance of the Principles of Least Privilege.
- Using an LLM to defend against an LLM can substantially increase costs and resource consumption. Maybe in the future it will be a valid approach, and certainly it's a worthwhile avenue of research. But I don't see it as a viable short-term solution.
- It still suffers from the problem I previously mentioned, that it can only do static analysis outside the run-time environment, and runtime effects can't be fully predicted in a reliable fashion purely through static analysis.
accuracy wouldn't be tolerable
Accuracy doesn't really need to be high, only the precision, as this would be an additional check
I'm not sure what's the difference between accuracy and precision here.
needlessly heavyweight solution
Requests would be comparable to title generation in terms of token usage
Maybe I'm misunderstanding but I don't see why that would be the case. The guard would need to do more complex reasoning based on static analysis predicting the effects of commands and code in a runtime which it doesn't fully understand.
the only problem is that it would slow down the tools; it's probably only feasible if you have a fast enough backend (cerebras/local) for the guard model OR a really slow primary agent model
Agreed.
This could probably be implemented as a plugin though
Sure. If someone wants to experiment with building that, I certainly wouldn't stand in their way. But for the above reasons I wouldn't be too optimistic that the experiment would succeed.
have you looked at how our permissions check on the bash tool is implemented? it's not a simple string check we parse the command with treesitter and check each subcommand against what's configured
not saying this is 100% effective but not sure if you were aware of that
@thdxr This is great to read! I had a look at permissions and tools#bash, but couldn't find any details on this. It's the number one reason I'd rather approve each command manually, because I'm not risking anything and 100% understand what I'm allowing. Is my understanding correct that all ways to run multiple commands within one line, such as semicolons, <() and $() driven subshells, bash -c "cmd", pipes, &&, || or \ multilines... are all handled by the treesitter parsing approach?
@Nindaleth I believe everything BUT the bash -c stuff is handled but I will fix that
Sorry. I didn't read the entire thread, but have you considered, to disallow sudo-style access, disabling bash always and creating custom tools as ports to vetted commands? The ease with which these can be defined is one of the most appealing things about the project.
@rekram1-node Will it catch commands like this?
$'\x65\x76\x61\x6c' "echo foo && echo bar"
This is an example of what I meant by my earlier comment:
I'm pretty sure that trying to make arbitrary shell 100% safe is an impossible game of whack-a-mole which is highly dependent on the individual user's needs and environment. In the above example, the fundamental problem is that it relies on static analysis, but bash is a dynamic language and its runtime effects can't be fully predicted in a reliable fashion purely through static analysis.
it depends if you allow "$" or not
It doesn't; you could just type eval instead. I was just demonstrating one way how an obvious eval-based attack could be obfuscated, and I'm sure there are countless other ways too.
Yeah ultimately if you're super concerned you'll be better off using a heavier sandbox, we can definitely do more in our approach but it's really difficult to catch any edge case. An agent could write javascript that deletes directories and run that, it's not foolproof by any means
Some people do mounting of volumes w/ docker, or use cloud sandboxes. I think there is more to come in the sandbox realm soon
Yeah ultimately if you're super concerned you'll be better off using a heavier sandbox
Sure.
we can definitely do more in our approach but it's really difficult to catch any edge case.
Basically I'm suggesting that it might be not just really difficult but actually impossible to properly secure the bash tool. Yes, the more sandboxing support we get the better, but in many cases all the agent wants to do is run a simple command like git status which doesn't even require wrapping in a bash shell. In those cases sandboxing is like using a sledgehammer to crack a walnut, and an exec tool would be a far simpler solution which follows the standard best practice security of not exposing an unnecessarily large attack surface.
An agent could write javascript that deletes directories and run that, it's not foolproof by any means
True, that's not quite the point though. I wouldn't put node or similar commands on the automatic allowlist for my bash or exec tool, so the agent wouldn't be able to slip javascript past me.
That said, upon further investigation I think the tree-sitter guard probably does a lot better than I originally thought and does actually catch the eval trick.