Files
docker-agent/pkg/shellpath/shellpath_test.go
T
David Gageot 24d947c2da fix: prevent PATH hijacking and TOCTOU in shell/binary resolution (CWE-426)
Introduce pkg/shellpath to centralize safe shell binary resolution:
- WindowsCmdExe() resolves cmd.exe via ComSpec or %SystemRoot%\System32
  instead of bare PATH lookup
- DetectShell()/DetectWindowsShell() use exec.LookPath (absolute paths)
  for PowerShell, falling back to WindowsCmdExe()

Replace vulnerable bare cmd.exe resolution in three critical locations:
- pkg/tools/builtin/shell.go (detectShell)
- pkg/hooks/executor.go (initShell)
- pkg/tui/handlers.go (startShell)

Fix TOCTOU race in credential providers:
- lookupBinary() now returns the resolved absolute path
- KeychainProvider and PassProvider store and reuse the resolved path
  instead of re-resolving bare binary names at invocation time

Assisted-By: docker-agent
2026-03-12 13:25:39 +01:00

104 lines
2.7 KiB
Go

package shellpath
import (
"path/filepath"
"runtime"
"testing"
)
func TestWindowsCmdExe_ComSpec(t *testing.T) {
t.Setenv("ComSpec", `C:\Custom\cmd.exe`)
got := WindowsCmdExe()
if got != `C:\Custom\cmd.exe` {
t.Errorf("WindowsCmdExe() = %q, want %q", got, `C:\Custom\cmd.exe`)
}
}
func TestWindowsCmdExe_SystemRoot(t *testing.T) {
t.Setenv("ComSpec", "")
t.Setenv("SystemRoot", `C:\Windows`)
got := WindowsCmdExe()
want := `C:\Windows` + string(filepath.Separator) + filepath.Join("System32", "cmd.exe")
if got != want {
t.Errorf("WindowsCmdExe() = %q, want %q", got, want)
}
}
func TestWindowsCmdExe_Fallback(t *testing.T) {
t.Setenv("ComSpec", "")
t.Setenv("SystemRoot", "")
got := WindowsCmdExe()
if got != "cmd.exe" {
t.Errorf("WindowsCmdExe() = %q, want %q", got, "cmd.exe")
}
}
func TestDetectShell_Unix(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("Unix-only test")
}
t.Setenv("SHELL", "/bin/zsh")
shell, args := DetectShell()
if shell != "/bin/zsh" {
t.Errorf("DetectShell() shell = %q, want /bin/zsh", shell)
}
if len(args) != 1 || args[0] != "-c" {
t.Errorf("DetectShell() args = %v, want [-c]", args)
}
}
func TestDetectShell_Unix_Fallback(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("Unix-only test")
}
t.Setenv("SHELL", "")
shell, _ := DetectShell()
if shell != "/bin/sh" {
t.Errorf("DetectShell() shell = %q, want /bin/sh", shell)
}
}
func TestDefaultUnixShell(t *testing.T) {
t.Setenv("SHELL", "/usr/local/bin/fish")
got := defaultUnixShell()
if got != "/usr/local/bin/fish" {
t.Errorf("defaultUnixShell() = %q, want /usr/local/bin/fish", got)
}
t.Setenv("SHELL", "")
got = defaultUnixShell()
if got != "/bin/sh" {
t.Errorf("defaultUnixShell() = %q, want /bin/sh", got)
}
}
func TestWindowsCmdExe_PrefersComSpecOverSystemRoot(t *testing.T) {
// When both are set, ComSpec should take priority
t.Setenv("ComSpec", `D:\Tools\cmd.exe`)
t.Setenv("SystemRoot", `C:\Windows`)
got := WindowsCmdExe()
if got != `D:\Tools\cmd.exe` {
t.Errorf("WindowsCmdExe() = %q, want %q (ComSpec should take priority)", got, `D:\Tools\cmd.exe`)
}
}
func TestDetectWindowsShell_FallbackUsesAbsolutePath(t *testing.T) {
// Simulate an environment where no PowerShell is found:
// set PATH to empty so LookPath won't find pwsh.exe or powershell.exe
t.Setenv("PATH", "")
t.Setenv("ComSpec", "")
t.Setenv("SystemRoot", `C:\Windows`)
shell, args := DetectWindowsShell()
want := `C:\Windows` + string(filepath.Separator) + filepath.Join("System32", "cmd.exe")
if shell != want {
t.Errorf("DetectWindowsShell() shell = %q, want %q", shell, want)
}
if len(args) != 1 || args[0] != "/C" {
t.Errorf("DetectWindowsShell() args = %v, want [/C]", args)
}
}