Skip to content

feat(tools): shell-first exploration with a bash-backed Bash tool - #59

Merged
code-crusher merged 1 commit into
mainfrom
feat/shell-first-bash-tool
Oct 3, 2026
Merged

code-crusher merged 1 commit into
mainfrom
feat/shell-first-bash-tool

Conversation

@code-crusher

Copy link
Copy Markdown
Member

Summary

  • Stop offering list_files / search_files; the model searches and lists with rg, find, ls, git via the shell tool (like Claude Code). The system prompt teaches the patterns.
  • Read-only commands (rg, grep, find without -exec/-delete, ls, cat, git status/diff/log/..., pipes/&& of these, no redirects or command substitution) skip approval and run in parallel. Anything unrecognised still prompts (src/tools/readOnlyCommand.ts).
  • Rename execute_command to Bash. Old name still works for resumed sessions and hook matchers.
  • Run commands in bash instead of $SHELL (zsh/sh fallback on POSIX; Git Bash on Windows, cmd.exe only as a last resort). The prompt states the shell.

Notes

  • Old list_files/search_files executors are left in place for resumed sessions; they can be deleted in a follow-up.
  • Not yet run against a live model; bench/ would be the place to compare.

Test plan

  • tsc --noEmit
  • node --import tsx --test test/*.test.ts (85 pass)
  • Manual: search/list in the TUI without approval prompts, a write command still prompts
  • Manual: Windows with and without Git for Windows

🤖 Generated with Claude Code

Drop list_files/search_files in favour of rg/find/ls via the shell tool,
auto-approve and parallelise read-only commands, rename execute_command to
Bash (legacy name still accepted), and run commands in bash (Git Bash on
Windows) instead of $SHELL.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@matterai-app

matterai-app Bot commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

Summary By MatterAI MatterAI logo

🔄 What Changed

Replaced dedicated search/list tools with a unified shell-first approach via a bash-backed Bash tool, introducing read-only command classification, cross-platform shell selection (Git Bash/POSIX/cmd.exe), and parallel execution optimizations.

🔍 Impact of the Change

Streamlines agent tool execution by eliminating redundant specialized file search tools, enabling robust shell command parsing, safe auto-approval of read-only exploration commands (rg, grep, find, ls), and improved cross-platform CLI compatibility.

📁 Total Files Changed

Click to Expand
File ChangeLog
Test Config package.json Added test:readonly npm script for read-only command validation tests.
Core Agent src/core/agent.ts Integrated isReadOnlyCommand checks to enable parallel read-only tool execution and safe command auto-approval.
Core Hooks src/core/hooks.ts Updated hook matcher regex to support legacy execute_command alongside Bash.
System Prompts src/prompts/system.ts Updated system prompts to guide the LLM on shell-first exploration and bash hygiene.
Command Executor src/tools/executors/executeCommand.ts Configured windowsVerbatimArguments for cmd.exe execution stability.
Tool Registry src/tools/index.ts Registered Bash executor and updated approval kinds and descriptions.
Tool Schema src/tools/schemas/bash.ts Renamed and updated the Bash tool schema definition.
Tool Schemas src/tools/schemas/index.ts Removed dedicated search/list tool schemas in favor of shell-based discovery.
Read-Only Detector src/tools/readOnlyCommand.ts Added conservative shell command parser to detect read-only observing operations securely.
Shell Utility src/utils/shell.ts Added cross-platform shell resolution prioritizing Git Bash, bash, zsh, and cmd.exe.
Test Suite test/read-only-command.test.ts Added comprehensive unit tests covering safe read-only commands and unsafe write command rejection.

🧪 Test Added/Recommended

Added

  • test/read-only-command.test.ts: Validates safe read-only commands (rg, grep, find, ls, git status) and blocks unsafe execution/write patterns.

🔒 Security Vulnerabilities

  • 🛡️ Command Injection Risk Mitigated: Implemented robust quote-aware command segmentation and unsafe flag checks (UNSAFE_FLAGS) in isReadOnlyCommand to prevent execution of injected subshells, redirections, or destructive shell operations.

@code-crusher
code-crusher merged commit 5098ad5 into main Oct 3, 2026
1 check was pending
@code-crusher
code-crusher deleted the feat/shell-first-bash-tool branch October 3, 2026 11:42

@matterai-app matterai-app Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧪 PR Review is completed: Well-built shell-first redesign with a thoughtful conservative classifier, but the approval gate has two bypasses (fd --exec=… value-attached flags, uniq INPUT OUTPUT) and the new no-prompt path allows reading any file on the system, not just the workspace. Reviewed src/prompts/system.ts, src/utils/shell.ts, src/tools/schemas/index.ts, src/tools/schemas/bash.ts, src/tools/index.ts, src/tools/executors/executeCommand.ts, src/core/hooks.ts, test/read-only-command.test.ts, package.json: no issues found.

Skipped files
  • CHANGELOG.md: Skipped file pattern
  • README.md: Skipped file pattern
  • docs/HOOKS.md: Skipped file pattern
⬇️ Low Priority Suggestions (2)
src/tools/readOnlyCommand.ts (2 suggestions)

Location: src/tools/readOnlyCommand.ts (Lines 23-27)

🔴 Security

Issue: The fd unsafe-flag regex is anchored with $, but fd (clap-based) accepts =-attached and short-attached value syntax: fd --exec=rm . and fd --exec-batch=rm . do not match ^(--exec|--exec-batch)$ and are classified read-only — arbitrary command execution with no approval prompt and in the parallel batch. The same $-anchor gap exists for sort -oout.txt, tree -ofile, and git diff -Oorderfile (getopt/clap attached short values).

Fix: Drop the end anchor for value-taking flags so any attached form matches (no other flag of these tools shares these prefixes, so there are no false positives).

Impact: Closes an arbitrary-command-execution bypass of the approval gate.

-  	fd: /^(-x|-X|--exec|--exec-batch)$/,
-  	rg: /^(--pre|--pre-glob|--hostname-bin)(=|$)/,
-  	sort: /^(-o|--output)(=|$)/,
-  	tree: /^-o$/,
-  	git: /^(--output|--ext-diff|--textconv|-O|--open-files-in-pager)(=|$)/,
+  	fd: /^(-x|-X|--exec|--exec-batch)/,
+  	rg: /^(--pre|--pre-glob|--hostname-bin)/,
+  	sort: /^(-o|--output)/,
+  	tree: /^-o/,
+  	git: /^(--output|--ext-diff|--textconv|-O|--open-files-in-pager)/,

Location: src/tools/readOnlyCommand.ts (Lines 11-11)

🟠 Security

Issue: uniq is listed as read-only, but coreutils uniq [OPTION]... [INPUT [OUTPUT]] writes to the OUTPUT positional argument: uniq a b overwrites b with no redirection and no unsafe flag, so it passes the classifier and silently overwrites an arbitrary file without an approval prompt (e.g. uniq x ~/.bashrc).

Fix: Per this file's own policy ("a false positive would run something unreviewed"), remove uniq from the allow-list; piped ... | uniq | sort usage just falls back to prompting.

Impact: Eliminates an unreviewed file-overwrite primitive.

-  	"pwd", "echo", "sort", "uniq", "cut", "tr", "nl", "du", "df", "which", "basename", "dirname", "realpath",
+  	"pwd", "echo", "sort", "cut", "tr", "nl", "du", "df", "which", "basename", "dirname", "realpath",

Comment thread src/core/agent.ts
Comment on lines +1522 to +1523
const readOnly = !isDangerous && isReadOnlyCommand(String(args.command ?? ""))
needsApproval = isDangerous || !(readOnly || this.sessionApproveCommands || this.options.autoApproveSafeCommands)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Security / NEEDS DISCUSSION

Issue: The old logic prompted for every command in default mode; the new readOnly short-circuit means cat, head, git show, etc. never prompt — but isReadOnlyCommand does not scope paths to the workspace. cat ~/.ssh/id_rsa, cat ~/.aws/credentials, or rg secrets /etc run silently, pulling sensitive files outside the workspace into model context, from where they can be exfiltrated via web_fetch (a realistic prompt-injection chain, since tool results/file contents are untrusted input). The system prompt only says "prefer commands scoped to the workspace" — it is not enforced.

Fix (discussion): Consider requiring approval when a classified-read-only command references paths outside the workspace (home-dir shorthand, absolute paths outside cwd), or limiting the no-prompt fast path to workspace-relative invocations. This is a policy decision worth settling before merge rather than a one-line patch.

Impact: Prevents silent reads of credentials/system files outside the project.

Suggested change
const readOnly = !isDangerous && isReadOnlyCommand(String(args.command ?? ""))
needsApproval = isDangerous || !(readOnly || this.sessionApproveCommands || this.options.autoApproveSafeCommands)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant