From 14a737a36b1937816a249df3ad862f21d011cd4f Mon Sep 17 00:00:00 2001 From: JY Tan Date: Thu, 18 Dec 2025 17:02:09 -0800 Subject: [PATCH] Lint project --- CONTRIBUTING.md | 2 +- cmd/fence/main.go | 11 ++++++----- internal/config/config.go | 2 +- internal/proxy/http.go | 28 ++++++++++++++++++---------- internal/sandbox/linux.go | 32 ++++++++++++++++++-------------- internal/sandbox/macos.go | 9 +++++++-- internal/sandbox/manager.go | 14 +++++++------- internal/sandbox/monitor.go | 4 ++-- internal/sandbox/utils.go | 11 ++++++----- 9 files changed, 66 insertions(+), 47 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 67cc208..12ecb5a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -44,7 +44,7 @@ See [ARCHITECTURE.md](ARCHITECTURE.md) for the full project structure and compon - Keep edits focused and covered by tests where possible. - Update [ARCHITECTURE.md](ARCHITECTURE.md) when adding features or changing behavior. - Prefer small, reviewable PRs with a clear rationale. -- Run `make fmt` and `make lint` before committing. +- Run `make fmt` and `make lint` before committing. This project uses `golangci-lint` v1.64.8. ## Testing diff --git a/cmd/fence/main.go b/cmd/fence/main.go index a4e8b95..25856f4 100644 --- a/cmd/fence/main.go +++ b/cmd/fence/main.go @@ -77,11 +77,12 @@ Configuration file format (~/.fence.json): func runCommand(cmd *cobra.Command, args []string) error { var command string - if cmdString != "" { + switch { + case cmdString != "": command = cmdString - } else if len(args) > 0 { + case len(args) > 0: command = strings.Join(args, " ") - } else { + default: return fmt.Errorf("no command specified. Use -c or provide command arguments") } @@ -148,7 +149,7 @@ func runCommand(cmd *cobra.Command, args []string) error { fmt.Fprintf(os.Stderr, "[fence] Sandboxed command: %s\n", sandboxedCommand) } - execCmd := exec.Command("sh", "-c", sandboxedCommand) + execCmd := exec.Command("sh", "-c", sandboxedCommand) //nolint:gosec // sandboxedCommand is constructed from user input - intentional execCmd.Stdin = os.Stdin execCmd.Stdout = os.Stdout execCmd.Stderr = os.Stderr @@ -159,7 +160,7 @@ func runCommand(cmd *cobra.Command, args []string) error { go func() { sig := <-sigChan if execCmd.Process != nil { - execCmd.Process.Signal(sig) + _ = execCmd.Process.Signal(sig) } // Give child time to exit, then cleanup will happen via defer }() diff --git a/internal/config/config.go b/internal/config/config.go index c989645..f8f18ff 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -63,7 +63,7 @@ func DefaultConfigPath() string { // Load loads configuration from a file path. func Load(path string) (*Config, error) { - data, err := os.ReadFile(path) + data, err := os.ReadFile(path) //nolint:gosec // user-provided config path - intentional if err != nil { if os.IsNotExist(err) { return nil, nil diff --git a/internal/proxy/http.go b/internal/proxy/http.go index 495b792..ea9cfa3 100644 --- a/internal/proxy/http.go +++ b/internal/proxy/http.go @@ -9,6 +9,7 @@ import ( "net/http" "net/url" "os" + "strconv" "strings" "sync" "time" @@ -50,7 +51,8 @@ func (p *HTTPProxy) Start() (int, error) { p.listener = listener p.server = &http.Server{ - Handler: http.HandlerFunc(p.handleRequest), + Handler: http.HandlerFunc(p.handleRequest), + ReadHeaderTimeout: 10 * time.Second, } p.mu.Lock() @@ -109,7 +111,9 @@ func (p *HTTPProxy) handleConnect(w http.ResponseWriter, r *http.Request) { port := 443 if portStr != "" { - fmt.Sscanf(portStr, "%d", &port) + if p, err := strconv.Atoi(portStr); err == nil { + port = p + } } // Check if allowed @@ -128,7 +132,7 @@ func (p *HTTPProxy) handleConnect(w http.ResponseWriter, r *http.Request) { http.Error(w, "Bad Gateway", http.StatusBadGateway) return } - defer targetConn.Close() + defer func() { _ = targetConn.Close() }() // Hijack the connection hijacker, ok := w.(http.Hijacker) @@ -142,9 +146,11 @@ func (p *HTTPProxy) handleConnect(w http.ResponseWriter, r *http.Request) { http.Error(w, "Failed to hijack connection", http.StatusInternalServerError) return } - defer clientConn.Close() + defer func() { _ = clientConn.Close() }() - clientConn.Write([]byte("HTTP/1.1 200 Connection Established\r\n\r\n")) + if _, err := clientConn.Write([]byte("HTTP/1.1 200 Connection Established\r\n\r\n")); err != nil { + return + } // Pipe data bidirectionally var wg sync.WaitGroup @@ -152,12 +158,12 @@ func (p *HTTPProxy) handleConnect(w http.ResponseWriter, r *http.Request) { go func() { defer wg.Done() - io.Copy(targetConn, clientConn) + _, _ = io.Copy(targetConn, clientConn) }() go func() { defer wg.Done() - io.Copy(clientConn, targetConn) + _, _ = io.Copy(clientConn, targetConn) }() wg.Wait() @@ -175,7 +181,9 @@ func (p *HTTPProxy) handleHTTP(w http.ResponseWriter, r *http.Request) { host := targetURL.Hostname() port := 80 if targetURL.Port() != "" { - fmt.Sscanf(targetURL.Port(), "%d", &port) + if p, err := strconv.Atoi(targetURL.Port()); err == nil { + port = p + } } else if targetURL.Scheme == "https" { port = 443 } @@ -216,7 +224,7 @@ func (p *HTTPProxy) handleHTTP(w http.ResponseWriter, r *http.Request) { http.Error(w, "Bad Gateway", http.StatusBadGateway) return } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() // Copy response headers for key, values := range resp.Header { @@ -226,7 +234,7 @@ func (p *HTTPProxy) handleHTTP(w http.ResponseWriter, r *http.Request) { } w.WriteHeader(resp.StatusCode) - io.Copy(w, resp.Body) + _, _ = io.Copy(w, resp.Body) p.logRequest(r.Method, r.RequestURI, host, resp.StatusCode, "ALLOWED", time.Since(start)) } diff --git a/internal/sandbox/linux.go b/internal/sandbox/linux.go index a85ab3f..5f07126 100644 --- a/internal/sandbox/linux.go +++ b/internal/sandbox/linux.go @@ -38,7 +38,9 @@ func NewLinuxBridge(httpProxyPort, socksProxyPort int, debug bool) (*LinuxBridge } id := make([]byte, 8) - rand.Read(id) + if _, err := rand.Read(id); err != nil { + return nil, fmt.Errorf("failed to generate socket ID: %w", err) + } socketID := hex.EncodeToString(id) tmpDir := os.TempDir() @@ -56,7 +58,7 @@ func NewLinuxBridge(httpProxyPort, socksProxyPort int, debug bool) (*LinuxBridge fmt.Sprintf("UNIX-LISTEN:%s,fork,reuseaddr", httpSocketPath), fmt.Sprintf("TCP:localhost:%d", httpProxyPort), } - bridge.httpProcess = exec.Command("socat", httpArgs...) + bridge.httpProcess = exec.Command("socat", httpArgs...) //nolint:gosec // args constructed from trusted input if debug { fmt.Fprintf(os.Stderr, "[fence:linux] Starting HTTP bridge: socat %s\n", strings.Join(httpArgs, " ")) } @@ -69,7 +71,7 @@ func NewLinuxBridge(httpProxyPort, socksProxyPort int, debug bool) (*LinuxBridge fmt.Sprintf("UNIX-LISTEN:%s,fork,reuseaddr", socksSocketPath), fmt.Sprintf("TCP:localhost:%d", socksProxyPort), } - bridge.socksProcess = exec.Command("socat", socksArgs...) + bridge.socksProcess = exec.Command("socat", socksArgs...) //nolint:gosec // args constructed from trusted input if debug { fmt.Fprintf(os.Stderr, "[fence:linux] Starting SOCKS bridge: socat %s\n", strings.Join(socksArgs, " ")) } @@ -98,17 +100,17 @@ func NewLinuxBridge(httpProxyPort, socksProxyPort int, debug bool) (*LinuxBridge // Cleanup stops the bridge processes and removes socket files. func (b *LinuxBridge) Cleanup() { if b.httpProcess != nil && b.httpProcess.Process != nil { - b.httpProcess.Process.Kill() - b.httpProcess.Wait() + _ = b.httpProcess.Process.Kill() + _ = b.httpProcess.Wait() } if b.socksProcess != nil && b.socksProcess.Process != nil { - b.socksProcess.Process.Kill() - b.socksProcess.Wait() + _ = b.socksProcess.Process.Kill() + _ = b.socksProcess.Wait() } // Clean up socket files - os.Remove(b.HTTPSocketPath) - os.Remove(b.SOCKSSocketPath) + _ = os.Remove(b.HTTPSocketPath) + _ = os.Remove(b.SOCKSSocketPath) if b.debug { fmt.Fprintf(os.Stderr, "[fence:linux] Bridges cleaned up\n") @@ -127,7 +129,9 @@ func NewReverseBridge(ports []int, debug bool) (*ReverseBridge, error) { } id := make([]byte, 8) - rand.Read(id) + if _, err := rand.Read(id); err != nil { + return nil, fmt.Errorf("failed to generate socket ID: %w", err) + } socketID := hex.EncodeToString(id) tmpDir := os.TempDir() @@ -147,7 +151,7 @@ func NewReverseBridge(ports []int, debug bool) (*ReverseBridge, error) { fmt.Sprintf("TCP-LISTEN:%d,fork,reuseaddr", port), fmt.Sprintf("UNIX-CONNECT:%s,retry=50,interval=0.1", socketPath), } - proc := exec.Command("socat", args...) + proc := exec.Command("socat", args...) //nolint:gosec // args constructed from trusted input if debug { fmt.Fprintf(os.Stderr, "[fence:linux] Starting reverse bridge for port %d: socat %s\n", port, strings.Join(args, " ")) } @@ -169,14 +173,14 @@ func NewReverseBridge(ports []int, debug bool) (*ReverseBridge, error) { func (b *ReverseBridge) Cleanup() { for _, proc := range b.processes { if proc != nil && proc.Process != nil { - proc.Process.Kill() - proc.Wait() + _ = proc.Process.Kill() + _ = proc.Wait() } } // Clean up socket files for _, socketPath := range b.SocketPaths { - os.Remove(socketPath) + _ = os.Remove(socketPath) } if b.debug { diff --git a/internal/sandbox/macos.go b/internal/sandbox/macos.go index 98bce0e..4e99afc 100644 --- a/internal/sandbox/macos.go +++ b/internal/sandbox/macos.go @@ -18,7 +18,9 @@ var sessionSuffix = generateSessionSuffix() func generateSessionSuffix() string { bytes := make([]byte, 8) - rand.Read(bytes) + if _, err := rand.Read(bytes); err != nil { + panic("failed to generate session suffix: " + err.Error()) + } return "_" + hex.EncodeToString(bytes)[:9] + "_SBX" } @@ -175,7 +177,10 @@ func generateWriteRules(allowPaths, denyPaths []string, allowGitConfig bool, log // Combine user-specified and mandatory deny patterns cwd, _ := os.Getwd() - allDenyPaths := append(denyPaths, GetMandatoryDenyPatterns(cwd, allowGitConfig)...) + mandatoryDeny := GetMandatoryDenyPatterns(cwd, allowGitConfig) + allDenyPaths := make([]string, 0, len(denyPaths)+len(mandatoryDeny)) + allDenyPaths = append(allDenyPaths, denyPaths...) + allDenyPaths = append(allDenyPaths, mandatoryDeny...) for _, pathPattern := range allDenyPaths { normalized := NormalizePath(pathPattern) diff --git a/internal/sandbox/manager.go b/internal/sandbox/manager.go index 8598deb..208650d 100644 --- a/internal/sandbox/manager.go +++ b/internal/sandbox/manager.go @@ -60,7 +60,7 @@ func (m *Manager) Initialize() error { m.socksProxy = proxy.NewSOCKSProxy(filter, m.debug, m.monitor) socksPort, err := m.socksProxy.Start() if err != nil { - m.httpProxy.Stop() + _ = m.httpProxy.Stop() return fmt.Errorf("failed to start SOCKS proxy: %w", err) } m.socksPort = socksPort @@ -69,8 +69,8 @@ func (m *Manager) Initialize() error { if platform.Detect() == platform.Linux { bridge, err := NewLinuxBridge(m.httpPort, m.socksPort, m.debug) if err != nil { - m.httpProxy.Stop() - m.socksProxy.Stop() + _ = m.httpProxy.Stop() + _ = m.socksProxy.Stop() return fmt.Errorf("failed to initialize Linux bridge: %w", err) } m.linuxBridge = bridge @@ -80,8 +80,8 @@ func (m *Manager) Initialize() error { reverseBridge, err := NewReverseBridge(m.exposedPorts, m.debug) if err != nil { m.linuxBridge.Cleanup() - m.httpProxy.Stop() - m.socksProxy.Stop() + _ = m.httpProxy.Stop() + _ = m.socksProxy.Stop() return fmt.Errorf("failed to initialize reverse bridge: %w", err) } m.reverseBridge = reverseBridge @@ -121,10 +121,10 @@ func (m *Manager) Cleanup() { m.linuxBridge.Cleanup() } if m.httpProxy != nil { - m.httpProxy.Stop() + _ = m.httpProxy.Stop() } if m.socksProxy != nil { - m.socksProxy.Stop() + _ = m.socksProxy.Stop() } m.logDebug("Sandbox manager cleaned up") } diff --git a/internal/sandbox/monitor.go b/internal/sandbox/monitor.go index b481fa5..6e78bd2 100644 --- a/internal/sandbox/monitor.go +++ b/internal/sandbox/monitor.go @@ -94,8 +94,8 @@ func (m *LogMonitor) Stop() { } if m.cmd != nil && m.cmd.Process != nil { - m.cmd.Process.Kill() - m.cmd.Wait() + _ = m.cmd.Process.Kill() + _ = m.cmd.Wait() } m.running = false diff --git a/internal/sandbox/utils.go b/internal/sandbox/utils.go index 785533e..6a03880 100644 --- a/internal/sandbox/utils.go +++ b/internal/sandbox/utils.go @@ -26,14 +26,15 @@ func NormalizePath(pathPattern string) string { normalized := pathPattern - // Expand ~ to home directory - if pathPattern == "~" { + // Expand ~ and relative paths + switch { + case pathPattern == "~": normalized = home - } else if strings.HasPrefix(pathPattern, "~/") { + case strings.HasPrefix(pathPattern, "~/"): normalized = filepath.Join(home, pathPattern[2:]) - } else if strings.HasPrefix(pathPattern, "./") || strings.HasPrefix(pathPattern, "../") { + case strings.HasPrefix(pathPattern, "./"), strings.HasPrefix(pathPattern, "../"): normalized, _ = filepath.Abs(filepath.Join(cwd, pathPattern)) - } else if !filepath.IsAbs(pathPattern) && !ContainsGlobChars(pathPattern) { + case !filepath.IsAbs(pathPattern) && !ContainsGlobChars(pathPattern): normalized, _ = filepath.Abs(filepath.Join(cwd, pathPattern)) }