From d5dba94b7ba179788cd7b693e856deaba72279c1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 2 Jan 2026 20:10:12 +0000 Subject: [PATCH] Security Fix: Prevent path traversal in poutine.go (Alert #445) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixed path traversal vulnerability (G304) in parseAndDisplayPoutineOutputForDirectory function by adding comprehensive path validation and sanitization: - Added gitRoot parameter to validate file paths are within repository - Used filepath.Clean() to normalize paths and remove traversal sequences - Added filepath.Abs() to resolve paths to absolute form - Used filepath.Rel() to verify files are within gitRoot boundary - Skip files outside gitRoot with appropriate logging This prevents potential path traversal attacks from compromised poutine tool output or malicious JSON responses. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- pkg/cli/poutine.go | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/pkg/cli/poutine.go b/pkg/cli/poutine.go index a441be23f1c..7c9a21d64ee 100644 --- a/pkg/cli/poutine.go +++ b/pkg/cli/poutine.go @@ -117,7 +117,7 @@ func runPoutineOnDirectory(workflowDir string, verbose bool, strict bool) error err = cmd.Run() // Parse and display output for all files (no filtering) - totalWarnings, parseErr := parseAndDisplayPoutineOutputForDirectory(stdout.String(), verbose) + totalWarnings, parseErr := parseAndDisplayPoutineOutputForDirectory(stdout.String(), verbose, gitRoot) if parseErr != nil { poutineLog.Printf("Failed to parse poutine output: %v", parseErr) // Fall back to showing raw output @@ -361,7 +361,7 @@ func parseAndDisplayPoutineOutput(stdout, targetFile string, verbose bool) (int, // parseAndDisplayPoutineOutputForDirectory parses poutine JSON output and displays all findings // Returns the total number of warnings found across all files -func parseAndDisplayPoutineOutputForDirectory(stdout string, verbose bool) (int, error) { +func parseAndDisplayPoutineOutputForDirectory(stdout string, verbose bool, gitRoot string) (int, error) { // Parse JSON output from stdout var output poutineOutput if stdout == "" { @@ -397,8 +397,37 @@ func parseAndDisplayPoutineOutputForDirectory(stdout string, verbose bool) (int, // Display findings for each file for filePath, findings := range findingsByFile { + // Validate and sanitize file path to prevent path traversal + cleanPath := filepath.Clean(filePath) + + // Convert to absolute path if relative + absPath := cleanPath + if !filepath.IsAbs(cleanPath) { + absPath = filepath.Join(gitRoot, cleanPath) + } + + // Ensure the file is within gitRoot to prevent path traversal + absGitRoot, err := filepath.Abs(gitRoot) + if err != nil { + poutineLog.Printf("Failed to get absolute path for git root: %v", err) + continue + } + + absPath, err = filepath.Abs(absPath) + if err != nil { + poutineLog.Printf("Failed to get absolute path for %s: %v", filePath, err) + continue + } + + // Check if the resolved path is within gitRoot + relPath, err := filepath.Rel(absGitRoot, absPath) + if err != nil || strings.HasPrefix(relPath, "..") { + poutineLog.Printf("Skipping file outside git root: %s", filePath) + continue + } + // Read file content for context display - fileContent, err := os.ReadFile(filePath) + fileContent, err := os.ReadFile(absPath) var fileLines []string if err == nil { fileLines = strings.Split(string(fileContent), "\n")