claude-code icon indicating copy to clipboard operation
claude-code copied to clipboard

[FEATURE] add Exec tool as more secure alternative to Bash tool

Open aspiers opened this issue 4 months ago • 3 comments

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://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 like Bash(safe-cmd:*) won’t give it permission to run the command safe-cmd && other-cmd

However 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 Claude's fundamentally flawed approach which only allows command invocation via the 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.

So in many cases, a similar approach of avoiding usage of bash or any other shell should work fine for Claude Code.

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 Claude Code's existing very limited :* pattern matching scheme, if we put the following in settings.json:

{
  "permissions": {
    "allow": [
      "Exec(npm run test:*)"
    ]
  }
}

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 Claude Code 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.

One proposal along these lines has already been made:

  • https://github.com/anthropics/claude-code/issues/4320

However there are probably other approaches which could also work, e.g. on Linux apparmor and selinux are alternative sandboxing mechanisms.

aspiers avatar Aug 18 '25 16:08 aspiers

Hey, this is a fantastic and well-researched write-up. I'm just another user, but I've been digging into the security model myself and wanted to share some thoughts based on the official documentation.

You've hit on a really important point about the Bash tool's security model. You're absolutely right that simple prefix matching can be bypassed with techniques like command substitution ($()) and process substitution (<()), which the current documentation doesn't seem to explicitly address.

Your proposal for a dedicated Exec tool is an excellent idea and aligns perfectly with security best practices. It's probably the "right" long-term fix for the core issue.

In the meantime, I found a couple of existing features in the docs that might serve as powerful workarounds to achieve a similar level of security.

1. Workaround using PreToolUse Hooks

This seems to be the most direct way to implement the custom validation you're talking about. The Hooks Guide and Hooks Reference show that you can register a PreToolUse hook that fires before any Bash command is executed.

The hook receives a JSON object on stdin with the tool_name and tool_input (which contains the command string). You could write a script (in Python, JS, or even a safer shell like oil) that performs much more sophisticated validation than the built-in pattern matching.

For example, your script could:

  • Parse the command string to ensure it contains no shell metacharacters.
  • Check if the command is on a strict allowlist.
  • Deny commands that contain paths outside the project directory.

If your script detects a dangerous command, it can simply exit 2. According to the docs, this will block the tool from running and feed your stderr message back to Claude as feedback.

It's not as clean as a built-in Exec tool, but it gives you total control over what gets executed. Here's the example from the docs for a file protection hook, which could be adapted for command validation:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/your/custom_bash_validator.py"
          }
        ]
      }
    ]
  }
}

2. Sandboxing with Devcontainers

On your wider point about sandboxing, the official docs point to using Development Containers as a way to achieve this. The reference implementation includes a firewall that restricts network access and isolates the agent's environment from your host machine.

This directly addresses your concern about tools like python or sed being used to break out of the intended file permissions. By running claude inside a properly configured devcontainer, you can enforce OS-level restrictions on its actions.

Of course, as the docs warn, it's not a silver bullet, but it's a significant layer of defense that seems to align with what you're suggesting.

Anyway, hope this is helpful from one user to another! The hooks seem like the most powerful tool we have right now to lock this down.

Cheers

coygeek avatar Aug 18 '25 18:08 coygeek

This issue has been inactive for 30 days. If the issue is still occurring, please comment to let us know. Otherwise, this issue will be automatically closed in 30 days for housekeeping purposes.

github-actions[bot] avatar Dec 05 '25 10:12 github-actions[bot]

Regarding your first point, IMHO any type of validation based on static analysis is doomed to fail. Even when using tree-sitter (as opencode does), I'm fairly sure it's impossible to catch all possible cases, e.g.

$'\x65\x76\x61\x6c' "echo foo && echo bar"

(also posted to https://github.com/sst/opencode/issues/2206#issuecomment-3639257888)

Regarding sandboxing with containers, yes I'm well aware of that route. There are often ways to break out of a container, so something like SELinux or AppArmor is going to be safer, but at that level the question becomes whether one is defending against an errant but benign AI agent, or an actively malicious attacker.

aspiers avatar Dec 10 '25 22:12 aspiers