Execute executa um comando e retorna o resultado
(ctx context.Context, command string, interactive bool)
| 53 | |
| 54 | // Execute executa um comando e retorna o resultado |
| 55 | func (e *CommandExecutor) Execute(ctx context.Context, command string, interactive bool) (*ExecutionResult, error) { |
| 56 | // Security (H8): Allowlist-based command validation (defense in depth) |
| 57 | if e.allowlist.GetMode() == SecurityModeStrict { |
| 58 | if allowed, _, reason := e.allowlist.IsAllowed(command); !allowed { |
| 59 | e.logger.Warn("Command blocked by allowlist", zap.String("command", command), zap.String("reason", reason)) |
| 60 | return &ExecutionResult{ |
| 61 | Command: command, |
| 62 | Error: reason, |
| 63 | }, fmt.Errorf("command blocked: %s (set CHATCLI_AGENT_SECURITY_MODE=permissive to use denylist fallback)", reason) |
| 64 | } |
| 65 | } else if e.allowlist.GetMode() == SecurityModePermissive { |
| 66 | // In permissive mode, check allowlist first; if not allowed, fall through to denylist |
| 67 | if allowed, _, _ := e.allowlist.IsAllowed(command); !allowed { |
| 68 | e.logger.Debug("Command not in allowlist, falling through to denylist", zap.String("command", command)) |
| 69 | } |
| 70 | } |
| 71 | |
| 72 | // Legacy denylist validation (always active as second layer) |
| 73 | if err := e.validator.ValidateCommand(command); err != nil { |
| 74 | e.logger.Warn("Comando inválido", zap.String("command", command), zap.Error(err)) |
| 75 | return &ExecutionResult{ |
| 76 | Command: command, |
| 77 | Error: err.Error(), |
| 78 | }, err |
| 79 | } |
| 80 | |
| 81 | // --- LÓGICA DE SELEÇÃO DE SHELL E FLAG --- |
| 82 | shell := os.Getenv("SHELL") |
| 83 | shellFlag := "-c" // Padrão Unix (bash, zsh, sh) |
| 84 | |
| 85 | if shell == "" { |
| 86 | if runtime.GOOS == "windows" { |
| 87 | shell = "powershell.exe" // Fallback seguro para Windows |
| 88 | } else { |
| 89 | shell = "/bin/sh" |
| 90 | } |
| 91 | } |
| 92 | |
| 93 | // Validate $SHELL against known shell binaries to prevent env manipulation |
| 94 | if runtime.GOOS != "windows" { |
| 95 | shellBase := filepath.Base(shell) |
| 96 | allowedShells := map[string]bool{ |
| 97 | "sh": true, "bash": true, "zsh": true, "dash": true, |
| 98 | "fish": true, "ksh": true, "csh": true, "tcsh": true, |
| 99 | } |
| 100 | if !allowedShells[shellBase] { |
| 101 | e.logger.Warn("Unrecognized shell, falling back to /bin/sh", |
| 102 | zap.String("shell", shell)) |
| 103 | shell = "/bin/sh" |
| 104 | } |
| 105 | } |
| 106 | |
| 107 | // Ajuste da flag baseado no binário do shell |
| 108 | if runtime.GOOS == "windows" { |
| 109 | lowerShell := strings.ToLower(shell) |
| 110 | if strings.Contains(lowerShell, "powershell") || strings.Contains(lowerShell, "pwsh") { |
| 111 | shellFlag = "-Command" |
| 112 | } else if strings.Contains(lowerShell, "cmd") { |
nothing calls this directly
no test coverage detected