git: 4d0808efd654 - main - misc/crush: Add option to support for disabling dangerous command blocking

From: Hiroki Tagato <tagattie_at_FreeBSD.org>
Date: Mon, 29 Jun 2026 09:02:52 UTC
The branch main has been updated by tagattie:

URL: https://cgit.FreeBSD.org/ports/commit/?id=4d0808efd65497c4f3579a676279c3affba80dac

commit 4d0808efd65497c4f3579a676279c3affba80dac
Author:     Victor Igumnov <victoriggy@gmail.com>
AuthorDate: 2026-06-29 08:57:58 +0000
Commit:     Hiroki Tagato <tagattie@FreeBSD.org>
CommitDate: 2026-06-29 09:02:27 +0000

    misc/crush: Add option to support for disabling dangerous command blocking
    
    By default, crush blocks the execution of potentially dangerous
    commands (like known hazardous system commands or package
    installations).
    
    This change adds an option to support for disabling the dangerous
    command blocking, which would be convenient if one operates in a
    safe environment or needs to run those commands.
    
    PR:             296282
    Reported by:    Victor Igumnov <victoriggy@gmail.com>
    Pull Request:   https://github.com/freebsd/freebsd-ports/pull/547
---
 misc/crush/Makefile                                |  12 ++-
 .../files/extra-patch-disable-command-blocking     | 120 +++++++++++++++++++++
 misc/crush/pkg-message                             |  17 +++
 3 files changed, 146 insertions(+), 3 deletions(-)

diff --git a/misc/crush/Makefile b/misc/crush/Makefile
index 97f3a3dc7a91..09deedbcb2cf 100644
--- a/misc/crush/Makefile
+++ b/misc/crush/Makefile
@@ -1,6 +1,7 @@
 PORTNAME=	crush
 DISTVERSIONPREFIX=	v
 DISTVERSION=	0.80.0
+PORTREVISION=	1
 CATEGORIES=	misc devel
 
 MAINTAINER=	tagattie@FreeBSD.org
@@ -17,18 +18,23 @@ USES=		go:1.26+,modules
 GO_MODULE=	github.com/charmbracelet/crush
 GO_LDFLAGS=	-w -X github.com/charmbracelet/crush/internal/version.Version=${DISTVERSION}
 
+PATCH_STRIP=	-p1
+
 PLIST_FILES=	bin/${PORTNAME}
 PORTDOCS=	README.md
 
-OPTIONS_DEFINE=		COMPLETIONS DOCS MANPAGES
-OPTIONS_DEFAULT=	COMPLETIONS MANPAGES
+OPTIONS_DEFINE=		COMPLETIONS DISABLE_COMMAND_BLOCKING DOCS MANPAGES
+OPTIONS_DEFAULT=	COMPLETIONS DISABLE_COMMAND_BLOCKING MANPAGES
 
-COMPLETIONS_DESC=	Build and/or install shell completions
+COMPLETIONS_DESC=		Build and/or install shell completions
+DISABLE_COMMAND_BLOCKING_DESC=	Support for disabling dangerous command blocking
 
 COMPLETIONS_PLIST_FILES=etc/bash_completion.d/${PORTNAME} \
 			share/fish/completions/${PORTNAME}.fish \
 			share/zsh/site-functions/_${PORTNAME}
 
+DISABLE_COMMAND_BLOCKING_EXTRA_PATCHES=	${PATCHDIR}/extra-patch-disable-command-blocking
+
 MANPAGES_PLIST_FILES=	share/man/man1/${PORTNAME}.1.gz
 
 post-build-COMPLETIONS-on:
diff --git a/misc/crush/files/extra-patch-disable-command-blocking b/misc/crush/files/extra-patch-disable-command-blocking
new file mode 100644
index 000000000000..9b197f1648f0
--- /dev/null
+++ b/misc/crush/files/extra-patch-disable-command-blocking
@@ -0,0 +1,120 @@
+diff --git a/internal/agent/common_test.go b/internal/agent/common_test.go
+index 8eb0f10..568d615 100644
+--- a/internal/agent/common_test.go
++++ b/internal/agent/common_test.go
+@@ -166,7 +166,7 @@ func coderAgent(r *vcr.Recorder, env fakeEnv, large, small fantasy.LanguageModel
+ 	}
+ 
+ 	allTools := []fantasy.AgentTool{
+-		tools.NewBashTool(env.permissions, env.workingDir, cfg.Config().Options.Attribution, modelName),
++		tools.NewBashTool(env.permissions, env.workingDir, cfg.Config().Options.Attribution, modelName, cfg.Config().Options.DisableCommandBlocking),
+ 		tools.NewDownloadTool(env.permissions, env.workingDir, r.GetDefaultClient()),
+ 		tools.NewEditTool(nil, env.permissions, env.history, *env.filetracker, env.workingDir),
+ 		tools.NewMultiEditTool(nil, env.permissions, env.history, *env.filetracker, env.workingDir),
+diff --git a/internal/agent/coordinator.go b/internal/agent/coordinator.go
+index 86ca09e..c07dd45 100644
+--- a/internal/agent/coordinator.go
++++ b/internal/agent/coordinator.go
+@@ -605,7 +605,7 @@ func (c *coordinator) buildTools(ctx context.Context, agent config.Agent, isSubA
+ 
+ 	allTools = append(
+ 		allTools,
+-		tools.NewBashTool(c.permissions, c.cfg.WorkingDir(), c.cfg.Config().Options.Attribution, modelID),
++		tools.NewBashTool(c.permissions, c.cfg.WorkingDir(), c.cfg.Config().Options.Attribution, modelID, c.cfg.Config().Options.DisableCommandBlocking),
+ 		tools.NewCrushInfoTool(c.cfg, c.lspManager, c.allSkills, c.activeSkills, c.skillTracker),
+ 		tools.NewCrushLogsTool(logFile),
+ 		tools.NewJobOutputTool(),
+diff --git a/internal/agent/tools/bash.go b/internal/agent/tools/bash.go
+index 6b91c58..b95bfe1 100644
+--- a/internal/agent/tools/bash.go
++++ b/internal/agent/tools/bash.go
+@@ -142,8 +142,11 @@ var bannedCommands = []string{
+ 	"ufw",
+ }
+ 
+-func bashDescription(attribution *config.Attribution, modelID string) string {
++func bashDescription(attribution *config.Attribution, modelID string, disableBlocking bool) string {
+ 	bannedCommandsStr := strings.Join(bannedCommands, ", ")
++	if disableBlocking {
++		bannedCommandsStr = "none"
++	}
+ 	var out bytes.Buffer
+ 	if err := bashDescriptionTpl.Execute(&out, bashDescriptionData{
+ 		BannedCommands:  bannedCommandsStr,
+@@ -158,7 +161,10 @@ func bashDescription(attribution *config.Attribution, modelID string) string {
+ 	return out.String()
+ }
+ 
+-func blockFuncs() []shell.BlockFunc {
++func blockFuncs(disableBlocking bool) []shell.BlockFunc {
++	if disableBlocking {
++		return nil
++	}
+ 	return []shell.BlockFunc{
+ 		shell.CommandsBlocker(bannedCommands),
+ 
+@@ -190,10 +196,10 @@ func blockFuncs() []shell.BlockFunc {
+ 	}
+ }
+ 
+-func NewBashTool(permissions permission.Service, workingDir string, attribution *config.Attribution, modelID string) fantasy.AgentTool {
++func NewBashTool(permissions permission.Service, workingDir string, attribution *config.Attribution, modelID string, disableBlocking bool) fantasy.AgentTool {
+ 	return fantasy.NewAgentTool(
+ 		BashToolName,
+-		string(bashDescription(attribution, modelID)),
++		string(bashDescription(attribution, modelID, disableBlocking)),
+ 		func(ctx context.Context, params BashParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
+ 			if params.Command == "" {
+ 				return fantasy.NewTextErrorResponse("missing command"), nil
+@@ -247,7 +253,7 @@ func NewBashTool(permissions permission.Service, workingDir string, attribution
+ 				bgManager := shell.GetBackgroundShellManager()
+ 				bgManager.Cleanup()
+ 				// Use background context so it continues after tool returns
+-				bgShell, err := bgManager.Start(context.Background(), execWorkingDir, blockFuncs(), params.Command, params.Description)
++				bgShell, err := bgManager.Start(context.Background(), execWorkingDir, blockFuncs(disableBlocking), params.Command, params.Description)
+ 				if err != nil {
+ 					return fantasy.ToolResponse{}, fmt.Errorf("error starting background shell: %w", err)
+ 				}
+@@ -302,7 +308,7 @@ func NewBashTool(permissions permission.Service, workingDir string, attribution
+ 			// Start with detached context so it can survive if moved to background
+ 			bgManager := shell.GetBackgroundShellManager()
+ 			bgManager.Cleanup()
+-			bgShell, err := bgManager.Start(context.Background(), execWorkingDir, blockFuncs(), params.Command, params.Description)
++			bgShell, err := bgManager.Start(context.Background(), execWorkingDir, blockFuncs(disableBlocking), params.Command, params.Description)
+ 			if err != nil {
+ 				return fantasy.ToolResponse{}, fmt.Errorf("error starting shell: %w", err)
+ 			}
+diff --git a/internal/agent/tools/bash_test.go b/internal/agent/tools/bash_test.go
+index 40169e8..42b7426 100644
+--- a/internal/agent/tools/bash_test.go
++++ b/internal/agent/tools/bash_test.go
+@@ -115,7 +115,7 @@ func (m *recordingPermissionService) SubscribeNotifications(ctx context.Context)
+ func newBashToolForTest(workingDir string) fantasy.AgentTool {
+ 	permissions := &mockBashPermissionService{Broker: pubsub.NewBroker[permission.PermissionRequest]()}
+ 	attribution := &config.Attribution{TrailerStyle: config.TrailerStyleNone}
+-	return NewBashTool(permissions, workingDir, attribution, "test-model")
++	return NewBashTool(permissions, workingDir, attribution, "test-model", false)
+ }
+ 
+ func newBashToolWithRecordingPerms(workingDir string, allow bool) (fantasy.AgentTool, *recordingPermissionService) {
+@@ -124,7 +124,7 @@ func newBashToolWithRecordingPerms(workingDir string, allow bool) (fantasy.Agent
+ 		allow:  allow,
+ 	}
+ 	attribution := &config.Attribution{TrailerStyle: config.TrailerStyleNone}
+-	return NewBashTool(perms, workingDir, attribution, "test-model"), perms
++	return NewBashTool(perms, workingDir, attribution, "test-model", false), perms
+ }
+ 
+ func TestBashTool_ChainedCommandsRequirePermission(t *testing.T) {
+diff --git a/internal/config/config.go b/internal/config/config.go
+index fc3bab3..4a7f732 100644
+--- a/internal/config/config.go
++++ b/internal/config/config.go
+@@ -265,6 +265,7 @@ type Options struct {
+ 	Debug                bool        `json:"debug,omitempty" jsonschema:"description=Enable debug logging,default=false"`
+ 	DebugLSP             bool        `json:"debug_lsp,omitempty" jsonschema:"description=Enable debug logging for LSP servers,default=false"`
+ 	DisableAutoSummarize bool        `json:"disable_auto_summarize,omitempty" jsonschema:"description=Disable automatic conversation summarization,default=false"`
++	DisableCommandBlocking bool      `json:"disable_command_blocking,omitempty" jsonschema:"description=Disable blocking of potentially dangerous commands (unsafe),default=false"`
+ 	// DataDirectory is where Crush keeps per-project state such as
+ 	// the SQLite database and workspace overrides. Relative paths are
+ 	// resolved against the working directory; absolute paths are used
diff --git a/misc/crush/pkg-message b/misc/crush/pkg-message
new file mode 100644
index 000000000000..6f2f70837775
--- /dev/null
+++ b/misc/crush/pkg-message
@@ -0,0 +1,17 @@
+[
+{ type: install
+  message: <<EOM
+By default, crush blocks the execution of potentially dangerous commands
+(like known hazardous system commands or package installations). If you
+operate in a safe environment or need to run these commands, you can
+disable this protection:
+
+{
+  "$schema": "https://charm.land/crush.json",
+  "options": {
+    "disable_command_blocking": true
+  }
+}
+EOM
+}
+]