diff --git a/internal/fs/git_command.go b/internal/fs/git_command.go index 6a12411..d45c595 100644 --- a/internal/fs/git_command.go +++ b/internal/fs/git_command.go @@ -28,5 +28,6 @@ func UntrustedRepositoryGitCommand(root string, args ...string) *exec.Cmd { } command := exec.Command("git", append(hardened, args...)...) command.Env = append(os.Environ(), "GIT_OPTIONAL_LOCKS=0") + hideChildConsole(command) return command } diff --git a/internal/fs/git_command_other.go b/internal/fs/git_command_other.go new file mode 100644 index 0000000..2044569 --- /dev/null +++ b/internal/fs/git_command_other.go @@ -0,0 +1,8 @@ +//go:build !windows + +package fs + +import "os/exec" + +// hideChildConsole 在非 Windows 平台为空操作;可见控制台窗口问题仅 Windows 存在。 +func hideChildConsole(*exec.Cmd) {} diff --git a/internal/fs/git_command_windows.go b/internal/fs/git_command_windows.go new file mode 100644 index 0000000..eca2040 --- /dev/null +++ b/internal/fs/git_command_windows.go @@ -0,0 +1,28 @@ +//go:build windows + +// Windows 控制台抑制 —— 与 git_command.go 同包的平台侧实现 +// +// 威胁: aoci 常以无控制台形态运行(如 MCP stdio 由宿主拉起)。此时 exec 拉起 +// 控制台子进程(git.exe)时,Windows 会为子进程新分配一个可见控制台窗口—— +// 刷新期间反复弹窗并抢占用户焦点。 +// 边界: CREATE_NO_WINDOW 只抑制控制台分配,不改变 stdio 句柄继承;宿主提供的 +// 管道与真实终端下的交互输出不受影响。HideWindow(STARTF_USESHOWWINDOW+SW_HIDE) +// 作为兜底。调用方环境变量属操作者自身信任域,不在此处覆盖。 +package fs + +import ( + "os/exec" + "syscall" +) + +// createNoWindow 即 Win32 CREATE_NO_WINDOW(0x08000000)。 +const createNoWindow = 0x08000000 + +// hideChildConsole 阻止 Windows 为 git.exe 子进程新建可见控制台窗口。 +func hideChildConsole(cmd *exec.Cmd) { + if cmd.SysProcAttr == nil { + cmd.SysProcAttr = &syscall.SysProcAttr{} + } + cmd.SysProcAttr.HideWindow = true + cmd.SysProcAttr.CreationFlags |= createNoWindow +} diff --git a/internal/fs/git_command_windows_test.go b/internal/fs/git_command_windows_test.go new file mode 100644 index 0000000..d8014d3 --- /dev/null +++ b/internal/fs/git_command_windows_test.go @@ -0,0 +1,25 @@ +//go:build windows + +package fs + +import ( + "testing" +) + +// 无控制台父进程(如 MCP stdio 宿主)拉起 git.exe 时,Windows 会为子进程新分配 +// 可见控制台窗口。构造出的命令必须携带 CREATE_NO_WINDOW+HideWindow;本断言把 +// 窗口抑制固定为构造器不变量,防止后续重构静默丢失。 +func TestUntrustedRepositoryGitCommandSuppressesChildConsoleWindow(t *testing.T) { + command := UntrustedRepositoryGitCommand(t.TempDir(), "rev-parse", "--show-toplevel") + attr := command.SysProcAttr + if attr == nil { + t.Fatalf("git 调用必须设置 SysProcAttr 以抑制子进程控制台窗口") + } + if !attr.HideWindow { + t.Fatalf("git 调用必须设置 HideWindow") + } + if attr.CreationFlags&createNoWindow == 0 { + t.Fatalf("git 调用必须携带 CREATE_NO_WINDOW(0x%x),实际 CreationFlags=0x%x", + createNoWindow, attr.CreationFlags) + } +}