From 5e11b083ed50687deddcf30150c910b52a79b2d8 Mon Sep 17 00:00:00 2001 From: seagochen Date: Wed, 25 Feb 2026 12:40:51 +0900 Subject: [PATCH] fix: harden cmd mode cd redirection and allow ./executable in guard - handleCdCommand: redirect cd, cd ~, cd /, cd /path to workspace directory instead of $HOME or system root for safety - guardCommand: fix path regex false positive that extracted "/exe" from "./exe.sh" and blocked it as absolute path outside workspace Co-Authored-By: Claude Opus 4.6 --- pkg/agent/loop.go | 19 ++++++++++++------- pkg/tools/shell.go | 11 +++++++++-- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/pkg/agent/loop.go b/pkg/agent/loop.go index 531c53548..0aac2fc86 100644 --- a/pkg/agent/loop.go +++ b/pkg/agent/loop.go @@ -1400,25 +1400,30 @@ func (al *AgentLoop) executeCmdMode(ctx context.Context, agent *AgentInstance, c } // handleCdCommand handles the cd command in command mode, updating per-session working directory. +// Special paths (cd, cd ~, cd /, cd /xxx) are redirected to the workspace directory for safety. func (al *AgentLoop) handleCdCommand(content, sessionKey string, agent *AgentInstance) string { parts := strings.Fields(content) + workspace := agent.Workspace var target string - if len(parts) < 2 || parts[1] == "~" { - home, _ := os.UserHomeDir() - target = home + if len(parts) < 2 || parts[1] == "~" || parts[1] == "/" { + // cd, cd ~, cd / → always go to workspace + target = workspace } else { target = parts[1] - // Expand ~ prefix + // Expand ~ prefix: treat ~ as workspace root (not $HOME) if strings.HasPrefix(target, "~/") { - home, _ := os.UserHomeDir() - target = home + target[1:] + target = workspace + target[1:] + } + // Absolute paths (e.g. cd /etc) → redirect to workspace + if filepath.IsAbs(target) { + target = workspace } // Resolve relative paths if !filepath.IsAbs(target) { currentDir := al.getSessionWorkDir(sessionKey) if currentDir == "" { - currentDir = agent.Workspace + currentDir = workspace } target = filepath.Join(currentDir, target) } diff --git a/pkg/tools/shell.go b/pkg/tools/shell.go index 6883172cd..c578e571e 100644 --- a/pkg/tools/shell.go +++ b/pkg/tools/shell.go @@ -290,9 +290,16 @@ func (t *ExecTool) guardCommand(command, cwd string) string { } pathPattern := regexp.MustCompile(`[A-Za-z]:\\[^\\\"']+|/[^\s\"']+`) - matches := pathPattern.FindAllString(cmd, -1) + matchIndices := pathPattern.FindAllStringIndex(cmd, -1) + + for _, loc := range matchIndices { + raw := cmd[loc[0]:loc[1]] + // Skip relative paths like ./executable — the regex extracts + // "/executable" from "./executable" but it's not an absolute path. + if loc[0] > 0 && cmd[loc[0]-1] == '.' { + continue + } - for _, raw := range matches { p, err := filepath.Abs(raw) if err != nil { continue