Repository navigation
[FEATURE] add exec tool as more secure alternative to bash tool #2206
Description
[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:
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:
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 anthropics/claude-code#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:
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.
Activity
github-actions commentedon Aug 23, 2025
This issue might be a duplicate of existing issues. Please check:
rm -rfand proposes a command block list approachFeel free to ignore if none of these address your specific case.
thdxr commentedon Aug 23, 2025
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
aspiers commentedon Aug 23, 2025
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
realpathto 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, evenrm $HOME/foowould be allowed becauserealpathwon'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
bashtool, as that would cripple functionality. And it's definitely great to apply best efforts to improving security of thebashtool. 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 anexectool would allow.rekram1-node commentedon Aug 29, 2025
@aspiers just btw:
would block:
npm run test <(rm -rf $HOME/*)
aspiers commentedon Aug 29, 2025
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).anuramat commentedon Sep 14, 2025
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
aspiers commentedon Sep 14, 2025
No, please let's not go down this road.
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
exectool I propose here, and the treesitter approach which opencode already has.anuramat commentedon Sep 23, 2025
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?
I am mostly talking about mitigating poor instruction following, not prompt injection;
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 doesn't really need to be high, only the precision, as this would be an additional check
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
aspiers commentedon Sep 23, 2025
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
bashtool calls forexectool calls when there's a genuine need forbash; instead, it's to avoidbashwhen it's completely overkill andexecis 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 invokingexecto runnpmdirectly will dramatically shrink the attack surface (and avoid consuming unnecessary system resources as a minor but nice side effect).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
exectool would be one simple contribution in that direction.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.
Even if research indicates that all of this is feasible, there are still several concerns:
I'm not sure what's the difference between accuracy and precision here.
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.
Agreed.
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.
Nindaleth commentedon Nov 25, 2025
@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?rekram1-node commentedon Nov 25, 2025
@Nindaleth I believe everything BUT the
bash -cstuff is handled but I will fix thatmlanza commentedon Dec 5, 2025
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.
aspiers commentedon Dec 10, 2025
@rekram1-node Will it catch commands like this?
This is an example of what I meant by my earlier comment:
rekram1-node commentedon Dec 10, 2025
it depends if you allow "$" or not
aspiers commentedon Dec 10, 2025
It doesn't; you could just type
evalinstead. I was just demonstrating one way how an obviouseval-based attack could be obfuscated, and I'm sure there are countless other ways too.rekram1-node commentedon Dec 10, 2025
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
rekram1-node commentedon Dec 10, 2025
Some people do mounting of volumes w/ docker, or use cloud sandboxes. I think there is more to come in the sandbox realm soon
aspiers commentedon Dec 11, 2025
Sure.
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 statuswhich doesn't even require wrapping in a bash shell. In those cases sandboxing is like using a sledgehammer to crack a walnut, and anexectool would be a far simpler solution which follows the standard best practice security of not exposing an unnecessarily large attack surface.True, that's not quite the point though. I wouldn't put
nodeor similar commands on the automatic allowlist for mybashorexectool, 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.
lostmsu commentedon Feb 13, 2026
A sidenote: on Windows I don't want to use bash at all. I want to use pwsh. Then the agent could decide which shell to use and call it via exec explicitly.
smola commentedon Feb 14, 2026
If you are looking at securing the agent (e.g. in YOLO mode), you'd want a sandbox, not an
exectool. See #2242That being said, having implementing sandboxing in my fork, I still see the value of having an
exectool.bashtool will source~/.bashrcet al. In a sandbox with read-only filesystem, this leads to quite a few popular developer tools generating noise in the output (e.g.pyenv rehash) because they require some write operation during initialization. Anexecis much simpler, and would not lead to that, unless it's used itself to callbash.aspiers commentedon Feb 14, 2026
On a related note, I have recently discovered https://github.com/Dicklesworthstone/destructive_command_guard which in several ways is a far superior approach to what any of the agent harnesses currently offer in terms of protecting against dangerous command invocations (and also part of a marvellous suite of related tools). The only potential downside is that it defaults to allowing commands it recognizes, although this can be changed.
That said, I still strongly believe in the need for an
exectool in specific cases where a shell isn't required.smola commentedon Feb 15, 2026
@aspiers That sort of guardrails is a fool's errand. They are bound to miss all kinds of edge cases. For example,
findhas at least 4 flags that allow you to execute arbitrary commands (exec, execdir, ok, okdir). That project, as far as I can tell, does not detect all of them.Any pattern-matching solution is bound to fail in one of the following ways (or both):
aspiers commentedon Feb 16, 2026
@smola I largely agree, which is not surprising because most of your points have already been stated either above or in other issues some of which are linked from above. For example see "Beyond an exec tool: the need for a full sandboxing solution" in the original issue, or #9516, or this statement above:
That said, I disagree with the "fool's errand" categorisation:
This is not really any different from a million other discussions on the tradeoffs between security and convenience. There's no such thing as perfect security, and that's OK. Each approach is another tool in the box which gives users more options to find the right balance for them. In the case of this specific issue, the idea of an
exectool is simply to be able to allow specific commands which are known to be definitely safe (and this would of course exclude commands like find/xargs/timeout which can fork other commands) without requiring the huge extra attack surface of a shell process wrapped around them.github-actions commentedon Apr 18, 2026
To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.