From 330bc4e3de3686da47e260094c2a00f83db2f6b5 Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Thu, 22 Jan 2026 06:05:55 +1100 Subject: [PATCH] fix(security): prevent command injection in setup wizard The handleInstallDependency endpoint was vulnerable to command injection attacks. User-provided commands were split and executed directly without validation, allowing attackers to run arbitrary commands. Changes: - Add whitelist of allowed commands (package managers only) - Validate commands against whitelist before execution - Handle privilege escalation commands (sudo/pkexec/doas) by also validating the elevated command - Reject any command not in the whitelist with a clear error message The whitelist includes common package managers across platforms: - Linux: apt, dnf, pacman, zypper, emerge, eopkg, nix-env - macOS: brew, port - Windows: winget, choco, scoop Co-Authored-By: Claude Opus 4.5 --- v3/internal/setupwizard/wizard.go | 81 ++++++++++++++++++++++++++++++- 1 file changed, 80 insertions(+), 1 deletion(-) diff --git a/v3/internal/setupwizard/wizard.go b/v3/internal/setupwizard/wizard.go index 7073f1fe1..6ac4881ea 100644 --- a/v3/internal/setupwizard/wizard.go +++ b/v3/internal/setupwizard/wizard.go @@ -544,6 +544,77 @@ type InstallResponse struct { Error string `json:"error,omitempty"` } +// allowedCommands is a whitelist of commands that can be executed for dependency installation. +// This prevents arbitrary command execution from user-controlled input. +var allowedCommands = map[string]bool{ + // Package managers (may be called directly or via sudo) + "apt": true, + "apt-get": true, + "dnf": true, + "yum": true, + "pacman": true, + "zypper": true, + "emerge": true, + "eopkg": true, + "nix-env": true, + "brew": true, + "port": true, // MacPorts + "winget": true, + "choco": true, + "scoop": true, + "sudo": true, // sudo is allowed as it prefixes package manager commands + "pkexec": true, // polkit alternative to sudo + "doas": true, // OpenBSD sudo alternative + "xcode-select": true, // macOS Xcode CLI tools +} + +// allowedSudoCommands are commands allowed to be run after sudo/pkexec/doas +var allowedSudoCommands = map[string]bool{ + "apt": true, + "apt-get": true, + "dnf": true, + "yum": true, + "pacman": true, + "zypper": true, + "emerge": true, + "eopkg": true, + "nix-env": true, + "brew": true, + "port": true, + "xcode-select": true, +} + +// isCommandAllowed checks if a command is in the whitelist. +// For sudo/pkexec/doas, it also validates the command being elevated. +func isCommandAllowed(parts []string) bool { + if len(parts) == 0 { + return false + } + + cmd := parts[0] + if !allowedCommands[cmd] { + return false + } + + // If it's a privilege escalation command, validate the actual command + if cmd == "sudo" || cmd == "pkexec" || cmd == "doas" { + if len(parts) < 2 { + return false + } + // Skip sudo flags like -E, -S, etc. + actualCmdIndex := 1 + for actualCmdIndex < len(parts) && strings.HasPrefix(parts[actualCmdIndex], "-") { + actualCmdIndex++ + } + if actualCmdIndex >= len(parts) { + return false + } + return allowedSudoCommands[parts[actualCmdIndex]] + } + + return true +} + func (w *Wizard) handleInstallDependency(rw http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(rw, "Method not allowed", http.StatusMethodNotAllowed) @@ -558,7 +629,6 @@ func (w *Wizard) handleInstallDependency(rw http.ResponseWriter, r *http.Request rw.Header().Set("Content-Type", "application/json") - // Execute the install command // Split the command into parts parts := strings.Fields(req.Command) if len(parts) == 0 { @@ -569,6 +639,15 @@ func (w *Wizard) handleInstallDependency(rw http.ResponseWriter, r *http.Request return } + // Validate command against whitelist to prevent arbitrary command execution + if !isCommandAllowed(parts) { + json.NewEncoder(rw).Encode(InstallResponse{ + Success: false, + Error: "Command not allowed: only package manager commands are permitted", + }) + return + } + cmd := exec.Command(parts[0], parts[1:]...) output, err := cmd.CombinedOutput()