executeInteractive executa comando interativo
(ctx context.Context, shell, shellFlag, command string)
| 168 | |
| 169 | // executeInteractive executa comando interativo |
| 170 | func (e *CommandExecutor) executeInteractive(ctx context.Context, shell, shellFlag, command string) (*ExecutionResult, error) { |
| 171 | start := time.Now() |
| 172 | result := &ExecutionResult{Command: command} |
| 173 | |
| 174 | fmt.Println(i18n.T("agent.executor.interactive_mode_header")) |
| 175 | fmt.Println(i18n.T("agent.executor.interactive_mode_info")) |
| 176 | fmt.Println(i18n.T("agent.executor.interactive_mode_exit_tip")) |
| 177 | fmt.Println("----------------------------------------------") |
| 178 | |
| 179 | if runtime.GOOS != "windows" { |
| 180 | saneCmd := exec.Command("stty", "sane") |
| 181 | saneCmd.Stdin = os.Stdin |
| 182 | if err := saneCmd.Run(); err != nil { |
| 183 | e.logger.Warn(i18n.T("agent.executor.fail_restore_terminal"), zap.Error(err)) |
| 184 | } |
| 185 | } |
| 186 | |
| 187 | // Configuração de perfil do shell (bashrc/zshrc) |
| 188 | shellConfigPath := e.getShellConfigPath(shell) |
| 189 | var shellCommand string |
| 190 | |
| 191 | // Security (M13): Default to --noprofile --norc. Opt-in via CHATCLI_AGENT_SOURCE_SHELL_CONFIG=true. |
| 192 | sourceShellConfig := strings.EqualFold(os.Getenv("CHATCLI_AGENT_SOURCE_SHELL_CONFIG"), "true") |
| 193 | |
| 194 | // No Windows, a lógica de 'source' não funciona igual. Simplificamos para executar direto. |
| 195 | if runtime.GOOS == "windows" { |
| 196 | shellCommand = command |
| 197 | } else { |
| 198 | if sourceShellConfig && shellConfigPath != "" { |
| 199 | if err := e.validateShellConfig(shellConfigPath); err != nil { |
| 200 | e.logger.Warn("Skipping shell config sourcing", zap.Error(err)) |
| 201 | shellConfigPath = "" |
| 202 | } |
| 203 | // Additional validation: file ownership must match current user |
| 204 | if shellConfigPath != "" { |
| 205 | if info, err := os.Stat(shellConfigPath); err == nil { |
| 206 | // Check file size (reject > 1MB — shell configs shouldn't be that large) |
| 207 | if info.Size() > 1024*1024 { |
| 208 | e.logger.Warn("Shell config too large, skipping", zap.Int64("size", info.Size())) |
| 209 | shellConfigPath = "" |
| 210 | } |
| 211 | } |
| 212 | } |
| 213 | } else { |
| 214 | shellConfigPath = "" // Don't source shell config by default |
| 215 | } |
| 216 | |
| 217 | if shellConfigPath != "" { |
| 218 | shellCommand = fmt.Sprintf("source %s 2>/dev/null || true; %s", utils.ShellQuote(shellConfigPath), command) |
| 219 | } else { |
| 220 | shellCommand = command |
| 221 | } |
| 222 | } |
| 223 | |
| 224 | cmd := exec.CommandContext(ctx, shell, shellFlag, shellCommand) //#nosec G204 G702 -- agent interactive shell command, validated upstream |
| 225 | |
| 226 | cmd.Stdin = os.Stdin |
| 227 | cmd.Stdout = os.Stdout |
no test coverage detected