Skip to content

Commit e04e7b6

Browse files
fix(bash): decide auto-approval from the parsed command, not its prefix
Reapplies the change reverted in charmbracelet#3565 (revert of charmbracelet#3426), unchanged. The original PR was merged before @meowgorithm had finished checking it against @taciturnaxolotl's sysadmin mode, so it was reverted to take main back to a known state. This restores the branch as a live PR so that review can finish before it lands a second time. The tree for the four touched files is byte-identical to the post-charmbracelet#3426 state; no behavior was changed while it was out. Co-authored-by: joestump-agent <joestump-agent@users.noreply.github.com> Assisted-by: Claude Opus 5
1 parent 240c487 commit e04e7b6

4 files changed

Lines changed: 736 additions & 114 deletions

File tree

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,13 @@ permissions. Use this with care.
509509
permissions allow view ls grep edit mcp_context7_get-library-doc
510510
```
511511

512+
The `bash` tool additionally runs a small set of read-only commands (`ls`,
513+
`pwd`, `git status`, `git log`, …) without prompting. Crush parses the command
514+
to decide, so it applies only when the whole command is provably inert: a
515+
redirection, a variable assignment, a command substitution, a pipeline, or an
516+
argument that mutates state (`git branch -D`, `git remote set-url`) all fall
517+
back to the normal permission prompt.
518+
512519
### Disabling Built-In Tools
513520

514521
You can also deny tools, hiding then from the agent entirely:

internal/agent/tools/bash.go

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -206,25 +206,13 @@ func NewBashTool(permissions permission.Service, workingDir string, attribution
206206
// Determine working directory
207207
execWorkingDir := cmp.Or(params.WorkingDir, workingDir)
208208

209-
isSafeReadOnly := false
210-
cmdLower := strings.ToLower(params.Command)
211-
212-
if !containsCommandChaining(params.Command) {
213-
for _, safe := range safeCommands {
214-
if strings.HasPrefix(cmdLower, safe) {
215-
if len(cmdLower) == len(safe) || cmdLower[len(safe)] == ' ' || cmdLower[len(safe)] == '-' {
216-
isSafeReadOnly = true
217-
break
218-
}
219-
}
220-
}
221-
}
209+
safeReadOnly := isSafeReadOnly(params.Command)
222210

223211
sessionID := GetSessionFromContext(ctx)
224212
if sessionID == "" {
225213
return fantasy.ToolResponse{}, fmt.Errorf("session ID is required for executing shell command")
226214
}
227-
if !isSafeReadOnly {
215+
if !safeReadOnly {
228216
p, err := permissions.Request(
229217
ctx,
230218
permission.CreatePermissionRequest{

0 commit comments

Comments
 (0)