fix: Address high severity security and reliability issues

Security:
- Configure CORS to only allow local origins (localhost, 127.0.0.1, wails://)
- Add CLI args validation for TTMiner to block shell metacharacters
- Add HTTPPort validation (must be 1024-65535)

Reliability:
- Manager.Stop() now stops all running miners before shutdown
- Close stdin pipe on Start() error to prevent resource leak (xmrig, ttminer)
- Fix node_service query parameter parsing (was dead code)

Feature:
- Add TTMiner support in service layer (install, update check, info cache)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
snider 2025-12-31 01:02:23 +00:00
parent 006dd3712e
commit 68c0033c55
5 changed files with 75 additions and 5 deletions

View file

@ -212,6 +212,13 @@ func (m *Manager) StartMiner(minerType string, config *Config) (Miner, error) {
return nil, fmt.Errorf("a miner with a similar configuration is already running: %s", instanceName)
}
// Validate user-provided HTTPPort if specified
if config.HTTPPort != 0 {
if config.HTTPPort < 1024 || config.HTTPPort > 65535 {
return nil, fmt.Errorf("HTTPPort must be between 1024 and 65535, got %d", config.HTTPPort)
}
}
apiPort, err := findAvailablePort()
if err != nil {
return nil, fmt.Errorf("failed to find an available port for the miner API: %w", err)
@ -471,8 +478,17 @@ func (m *Manager) GetMinerHashrateHistory(name string) ([]HashratePoint, error)
return miner.GetHashrateHistory(), nil
}
// Stop stops the manager and its background goroutines.
// Stop stops all running miners, background goroutines, and closes resources.
func (m *Manager) Stop() {
// Stop all running miners first
m.mu.Lock()
for name, miner := range m.miners {
if err := miner.Stop(); err != nil {
log.Printf("Warning: failed to stop miner %s: %v", name, err)
}
}
m.mu.Unlock()
close(m.stopChan)
m.waitGroup.Wait()

View file

@ -3,6 +3,7 @@ package mining
import (
"encoding/json"
"net/http"
"strconv"
"github.com/Snider/Mining/pkg/node"
"github.com/gin-gonic/gin"
@ -407,8 +408,8 @@ func (ns *NodeService) handleRemoteLogs(c *gin.Context) {
minerName := c.Param("miner")
lines := 100
if l := c.Query("lines"); l != "" {
if _, err := c.GetQuery("lines"); err {
// Use default
if parsed, err := strconv.Atoi(l); err == nil && parsed > 0 {
lines = parsed
}
}

View file

@ -81,7 +81,25 @@ func NewService(manager ManagerInterface, listenAddr string, displayAddr string,
// After calling InitRouter, you can use the Router field directly as an http.Handler.
func (s *Service) InitRouter() {
s.Router = gin.Default()
s.Router.Use(cors.Default())
// Configure CORS to only allow local origins
corsConfig := cors.Config{
AllowOrigins: []string{
"http://localhost:4200", // Angular dev server
"http://127.0.0.1:4200",
"http://localhost:9090", // Default API port
"http://127.0.0.1:9090",
"http://localhost:" + strings.Split(s.Server.Addr, ":")[len(strings.Split(s.Server.Addr, ":"))-1],
"http://127.0.0.1:" + strings.Split(s.Server.Addr, ":")[len(strings.Split(s.Server.Addr, ":"))-1],
"wails://wails", // Wails desktop app
},
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowHeaders: []string{"Origin", "Content-Type", "Accept", "Authorization"},
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,
MaxAge: 12 * time.Hour,
}
s.Router.Use(cors.New(corsConfig))
s.SetupRoutes()
}
@ -207,6 +225,8 @@ func (s *Service) updateInstallationCache() (*SystemInfo, error) {
switch availableMiner.Name {
case "xmrig":
miner = NewXMRigMiner()
case "tt-miner":
miner = NewTTMiner()
default:
continue
}
@ -265,6 +285,8 @@ func (s *Service) handleUpdateCheck(c *gin.Context) {
switch availableMiner.Name {
case "xmrig":
miner = NewXMRigMiner()
case "tt-miner":
miner = NewTTMiner()
default:
continue
}
@ -360,6 +382,8 @@ func (s *Service) handleInstallMiner(c *gin.Context) {
switch minerType {
case "xmrig":
miner = NewXMRigMiner()
case "tt-miner":
miner = NewTTMiner()
default:
c.JSON(http.StatusBadRequest, gin.H{"error": "unknown miner type"})
return

View file

@ -58,6 +58,7 @@ func (m *TTMiner) Start(config *Config) error {
}
if err := m.cmd.Start(); err != nil {
stdinPipe.Close()
return fmt.Errorf("failed to start TT-Miner: %w", err)
}
@ -131,6 +132,33 @@ func addTTMinerCliArgs(config *Config, args *[]string) {
// Add any extra arguments passed via CLIArgs
if config.CLIArgs != "" {
extraArgs := strings.Fields(config.CLIArgs)
*args = append(*args, extraArgs...)
for _, arg := range extraArgs {
// Skip potentially dangerous arguments
if isValidCLIArg(arg) {
*args = append(*args, arg)
} else {
log.Printf("Warning: skipping invalid CLI argument: %s", arg)
}
}
}
}
// isValidCLIArg validates CLI arguments to prevent injection or dangerous patterns
func isValidCLIArg(arg string) bool {
// Block shell metacharacters and dangerous patterns
dangerousPatterns := []string{";", "|", "&", "`", "$", "(", ")", "{", "}", "<", ">", "\n", "\r"}
for _, p := range dangerousPatterns {
if strings.Contains(arg, p) {
return false
}
}
// Block arguments that could override security-related settings
blockedArgs := []string{"--api-access-token", "--api-worker-id"}
lowerArg := strings.ToLower(arg)
for _, blocked := range blockedArgs {
if strings.HasPrefix(lowerArg, blocked) {
return false
}
}
return true
}

View file

@ -81,6 +81,7 @@ func (m *XMRigMiner) Start(config *Config) error {
}
if err := m.cmd.Start(); err != nil {
stdinPipe.Close()
return err
}