Somewhat okay refactoring
This commit is contained in:
110
internal/executor/executor.go
Normal file
110
internal/executor/executor.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package executor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"nannyagentv2/internal/types"
|
||||
)
|
||||
|
||||
// CommandExecutor handles safe execution of diagnostic commands
|
||||
type CommandExecutor struct {
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
// NewCommandExecutor creates a new command executor with specified timeout
|
||||
func NewCommandExecutor(timeout time.Duration) *CommandExecutor {
|
||||
return &CommandExecutor{
|
||||
timeout: timeout,
|
||||
}
|
||||
}
|
||||
|
||||
// Execute executes a command safely with timeout and validation
|
||||
func (ce *CommandExecutor) Execute(cmd types.Command) types.CommandResult {
|
||||
result := types.CommandResult{
|
||||
ID: cmd.ID,
|
||||
Command: cmd.Command,
|
||||
}
|
||||
|
||||
// Validate command safety
|
||||
if err := ce.validateCommand(cmd.Command); err != nil {
|
||||
result.Error = fmt.Sprintf("unsafe command: %s", err.Error())
|
||||
result.ExitCode = 1
|
||||
return result
|
||||
}
|
||||
|
||||
// Create context with timeout
|
||||
ctx, cancel := context.WithTimeout(context.Background(), ce.timeout)
|
||||
defer cancel()
|
||||
|
||||
// Execute command using shell for proper handling of pipes, redirects, etc.
|
||||
execCmd := exec.CommandContext(ctx, "/bin/bash", "-c", cmd.Command)
|
||||
|
||||
output, err := execCmd.CombinedOutput()
|
||||
result.Output = string(output)
|
||||
|
||||
if err != nil {
|
||||
result.Error = err.Error()
|
||||
if exitError, ok := err.(*exec.ExitError); ok {
|
||||
result.ExitCode = exitError.ExitCode()
|
||||
} else {
|
||||
result.ExitCode = 1
|
||||
}
|
||||
} else {
|
||||
result.ExitCode = 0
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// validateCommand checks if a command is safe to execute
|
||||
func (ce *CommandExecutor) validateCommand(command string) error {
|
||||
// Convert to lowercase for case-insensitive checking
|
||||
cmd := strings.ToLower(strings.TrimSpace(command))
|
||||
|
||||
// List of dangerous commands/patterns
|
||||
dangerousPatterns := []string{
|
||||
"rm ", "rm\t", "rm\n",
|
||||
"mv ", "mv\t", "mv\n",
|
||||
"dd ", "dd\t", "dd\n",
|
||||
"mkfs", "fdisk", "parted",
|
||||
"shutdown", "reboot", "halt", "poweroff",
|
||||
"passwd", "userdel", "usermod",
|
||||
"chmod", "chown", "chgrp",
|
||||
"systemctl stop", "systemctl disable", "systemctl mask",
|
||||
"service stop", "service disable",
|
||||
"kill ", "killall", "pkill",
|
||||
"crontab -r", "crontab -e",
|
||||
"iptables -F", "iptables -D", "iptables -I",
|
||||
"umount ", "unmount ", // Allow mount but not umount
|
||||
"wget ", "curl ", // Prevent network operations
|
||||
"| dd", "| rm", "| mv", // Prevent piping to dangerous commands
|
||||
}
|
||||
|
||||
// Check for dangerous patterns
|
||||
for _, pattern := range dangerousPatterns {
|
||||
if strings.Contains(cmd, pattern) {
|
||||
return fmt.Errorf("command contains dangerous pattern: %s", pattern)
|
||||
}
|
||||
}
|
||||
|
||||
// Additional checks for commands that start with dangerous operations
|
||||
if strings.HasPrefix(cmd, "rm ") || strings.HasPrefix(cmd, "rm\t") {
|
||||
return fmt.Errorf("rm command not allowed")
|
||||
}
|
||||
|
||||
// Check for sudo usage (we want to avoid automated sudo commands)
|
||||
if strings.HasPrefix(cmd, "sudo ") {
|
||||
return fmt.Errorf("sudo commands not allowed for automated execution")
|
||||
}
|
||||
|
||||
// Check for dangerous redirections (but allow safe ones like 2>/dev/null)
|
||||
if strings.Contains(cmd, ">") && !strings.Contains(cmd, "2>/dev/null") && !strings.Contains(cmd, ">/dev/null") {
|
||||
return fmt.Errorf("file redirection not allowed except to /dev/null")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user