fix(sandbox): create per-subagent ExecTool to avoid leaking allowPatterns

The shared ExecTool instance (sm.execTool) was mutated by
SetAllowPatterns when spawning subagents, which leaked sandbox
restrictions to the conductor. Basic commands like pwd and ls were
blocked on the main agent after any subagent spawned with a preset.

Create a new ExecTool per subagent instead of sharing a single
instance. Remove the now-unused execTool field from SubagentManager.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
dj-oyu 2026-02-25 16:49:11 +09:00
parent 3817aad8cf
commit 73560dbf66

View file

@ -3,7 +3,6 @@ package tools
import ( import (
"context" "context"
"fmt" "fmt"
"log"
"sync" "sync"
"time" "time"
@ -33,7 +32,6 @@ type SubagentManager struct {
workspace string workspace string
tools *ToolRegistry tools *ToolRegistry
webSearchOpts WebSearchToolOptions webSearchOpts WebSearchToolOptions
execTool *ExecTool // Shared exec tool for all presets
maxIterations int maxIterations int
maxTokens int maxTokens int
temperature float64 temperature float64
@ -53,11 +51,6 @@ func NewSubagentManager(
if reporter == nil { if reporter == nil {
reporter = orch.Noop reporter = orch.Noop
} }
// Create a shared exec tool for all presets
execTool, err := NewExecTool(workspace, true)
if err != nil {
log.Printf("subagent: failed to create exec tool: %v (exec disabled for subagents)", err)
}
return &SubagentManager{ return &SubagentManager{
tasks: make(map[string]*SubagentTask), tasks: make(map[string]*SubagentTask),
provider: provider, provider: provider,
@ -66,7 +59,6 @@ func NewSubagentManager(
workspace: workspace, workspace: workspace,
tools: NewToolRegistry(), tools: NewToolRegistry(),
webSearchOpts: webSearchOpts, webSearchOpts: webSearchOpts,
execTool: execTool,
maxIterations: 10, maxIterations: 10,
nextID: 1, nextID: 1,
reporter: reporter, reporter: reporter,
@ -306,10 +298,19 @@ func (sm *SubagentManager) buildPresetRegistry(preset Preset, writeRoot string)
registry.Register(NewAppendFileTool(writeRoot, true)) registry.Register(NewAppendFileTool(writeRoot, true))
} }
// Register exec and bg_monitor if allowed // Register exec and bg_monitor if allowed.
// Each subagent gets its own ExecTool to avoid mutating the shared instance's
// allowPatterns (which would leak sandbox restrictions to the conductor).
if config.AllowedTools["exec"] { if config.AllowedTools["exec"] {
// Use the shared exec tool but set allow patterns execWorkDir := writeRoot
execTool := sm.execTool if execWorkDir == "" {
execWorkDir = sm.workspace
}
execTool, err := NewExecTool(execWorkDir, true)
if err != nil {
// exec disabled for this subagent; skip registration
return registry
}
if config.ExecPolicy != nil { if config.ExecPolicy != nil {
_ = execTool.SetAllowPatterns([]string{config.ExecPolicy.AllowPattern}) _ = execTool.SetAllowPatterns([]string{config.ExecPolicy.AllowPattern})
execTool.SetLocalNetOnly(config.ExecPolicy.LocalNetOnly) execTool.SetLocalNetOnly(config.ExecPolicy.LocalNetOnly)