From 1f0ea3de777c4d2b13339ad0968fc59d31786ff2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 30 Jun 2026 16:06:38 +0000 Subject: [PATCH 1/4] Initial plan From c2fec6369e68e268dbf583761d29dc6b33437642 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 30 Jun 2026 16:29:14 +0000 Subject: [PATCH 2/4] fix: restore access.log capture in sandbox/firewall/logs/squid-logs/ subdirectory AWF writes Squid access.log to {proxy-logs-dir}/squid-logs/access.log (a squid-logs/ subdirectory within --proxy-logs-dir), not directly at the top level. The analysis and parsing code used a non-recursive *.log glob at sandbox/firewall/logs/ which missed the subdirectory. - analyzeFirewallLogs: check sandbox/firewall/logs/squid-logs/ first, then fall back to sandbox/firewall/logs/ for older AWF layout - parseFirewallLogs: add sandbox/firewall/logs/squid-logs/ as the primary path, keeping legacy squid-logs/ and workflow-logs/squid-logs/ as fallbacks - Add tests: TestAnalyzeFirewallLogsSandboxSquidSubdir and TestAnalyzeFirewallLogsSandboxFallbackToTopLevel Closes #42507 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/cli/firewall_log.go | 14 ++++++- pkg/cli/firewall_log_test.go | 72 ++++++++++++++++++++++++++++++++ pkg/cli/logs_parsing_firewall.go | 12 +++++- 3 files changed, 95 insertions(+), 3 deletions(-) diff --git a/pkg/cli/firewall_log.go b/pkg/cli/firewall_log.go index 66442e683d1..cdebe5b8e85 100644 --- a/pkg/cli/firewall_log.go +++ b/pkg/cli/firewall_log.go @@ -372,9 +372,21 @@ func analyzeFirewallLogs(runDir string, verbose bool) (*FirewallAnalysis, error) // First, check for sandbox/firewall/logs/ directory (new path after artifact download) // Firewall logs are uploaded from /tmp/gh-aw/sandbox/firewall/logs/ and the common parent - // /tmp/gh-aw/ is stripped during artifact upload, resulting in sandbox/firewall/logs/ after download + // /tmp/gh-aw/ is stripped during artifact upload, resulting in sandbox/firewall/logs/ after download. + // Within that directory, AWF writes Squid access logs to a squid-logs/ subdirectory. sandboxFirewallLogsDir := filepath.Join(runDir, "sandbox", "firewall", "logs") if fileutil.DirExists(sandboxFirewallLogsDir) { + // AWF writes the Squid access.log to a squid-logs/ subdirectory within --proxy-logs-dir. + // Check for that subdirectory first before falling back to the parent directory. + squidSubDir := filepath.Join(sandboxFirewallLogsDir, "squid-logs") + if fileutil.DirExists(squidSubDir) { + firewallLogLog.Printf("Found firewall logs directory: sandbox/firewall/logs/squid-logs") + if verbose { + fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Found firewall logs directory: sandbox/firewall/logs/squid-logs")) + } + return analyzeMultipleFirewallLogs(squidSubDir, verbose) + } + // Fall back to direct *.log files at the sandbox/firewall/logs/ level (older AWF layout). firewallLogLog.Printf("Found firewall logs directory: sandbox/firewall/logs") if verbose { fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Found firewall logs directory: sandbox/firewall/logs")) diff --git a/pkg/cli/firewall_log_test.go b/pkg/cli/firewall_log_test.go index 4cc3f977051..3c094736500 100644 --- a/pkg/cli/firewall_log_test.go +++ b/pkg/cli/firewall_log_test.go @@ -1009,6 +1009,78 @@ func TestFirewallAnalysisAddMetricsMergesDomains(t *testing.T) { } } +// TestAnalyzeFirewallLogsSandboxSquidSubdir verifies that analyzeFirewallLogs +// correctly finds access.log in the sandbox/firewall/logs/squid-logs/ subdirectory. +// AWF writes Squid logs to {proxy-logs-dir}/squid-logs/access.log, so after the +// agent artifact is downloaded and flattened the path is: +// {runDir}/sandbox/firewall/logs/squid-logs/access.log +func TestAnalyzeFirewallLogsSandboxSquidSubdir(t *testing.T) { + tmpDir := testutil.TempDir(t, "test-sandbox-squid-*") + + squidLogsDir := filepath.Join(tmpDir, "sandbox", "firewall", "logs", "squid-logs") + if err := os.MkdirAll(squidLogsDir, 0755); err != nil { + t.Fatalf("Failed to create squid-logs directory: %v", err) + } + + logContent := `1761332530.474 172.30.0.20:35288 api.enterprise.githubcopilot.com:443 140.82.112.22:443 1.1 CONNECT 200 TCP_TUNNEL:HIER_DIRECT api.enterprise.githubcopilot.com:443 "-" +1761332531.123 172.30.0.20:35289 blocked.example.com:443 140.82.112.23:443 1.1 CONNECT 403 NONE_NONE:HIER_NONE blocked.example.com:443 "-" +1761332532.456 172.30.0.20:35290 api.github.com:443 140.82.112.5:443 1.1 CONNECT 200 TCP_TUNNEL:HIER_DIRECT api.github.com:443 "-" +` + if err := os.WriteFile(filepath.Join(squidLogsDir, "access.log"), []byte(logContent), 0644); err != nil { + t.Fatalf("Failed to write access.log: %v", err) + } + + analysis, err := analyzeFirewallLogs(tmpDir, false) + if err != nil { + t.Fatalf("analyzeFirewallLogs failed: %v", err) + } + if analysis == nil { + t.Fatal("Expected firewall analysis but got nil - access.log in squid-logs/ subdirectory was not found") + } + + if analysis.TotalRequests != 3 { + t.Errorf("TotalRequests: got %d, want 3", analysis.TotalRequests) + } + if analysis.AllowedRequests != 2 { + t.Errorf("AllowedRequests: got %d, want 2", analysis.AllowedRequests) + } + if analysis.BlockedRequests != 1 { + t.Errorf("BlockedRequests: got %d, want 1", analysis.BlockedRequests) + } + if len(analysis.BlockedDomains) != 1 || analysis.BlockedDomains[0] != "blocked.example.com:443" { + t.Errorf("BlockedDomains: got %v, want [blocked.example.com:443]", analysis.BlockedDomains) + } +} + +// TestAnalyzeFirewallLogsSandboxFallbackToTopLevel verifies that when sandbox/firewall/logs/ +// exists but has no squid-logs/ subdirectory, analyzeFirewallLogs falls back to looking for +// *.log files directly in sandbox/firewall/logs/ (backward compatibility for older AWF layout). +func TestAnalyzeFirewallLogsSandboxFallbackToTopLevel(t *testing.T) { + tmpDir := testutil.TempDir(t, "test-sandbox-fallback-*") + + sandboxLogsDir := filepath.Join(tmpDir, "sandbox", "firewall", "logs") + if err := os.MkdirAll(sandboxLogsDir, 0755); err != nil { + t.Fatalf("Failed to create sandbox/firewall/logs directory: %v", err) + } + + logContent := `1761332530.474 172.30.0.20:35288 api.github.com:443 140.82.112.5:443 1.1 CONNECT 200 TCP_TUNNEL:HIER_DIRECT api.github.com:443 "-" +` + if err := os.WriteFile(filepath.Join(sandboxLogsDir, "access.log"), []byte(logContent), 0644); err != nil { + t.Fatalf("Failed to write access.log: %v", err) + } + + analysis, err := analyzeFirewallLogs(tmpDir, false) + if err != nil { + t.Fatalf("analyzeFirewallLogs failed: %v", err) + } + if analysis == nil { + t.Fatal("Expected firewall analysis but got nil") + } + if analysis.TotalRequests != 1 { + t.Errorf("TotalRequests: got %d, want 1", analysis.TotalRequests) + } +} + func TestFirewallAnalysisAddMetricsDeduplicatesDomains(t *testing.T) { base := &FirewallAnalysis{ TotalRequests: 1, diff --git a/pkg/cli/logs_parsing_firewall.go b/pkg/cli/logs_parsing_firewall.go index 5394b7edd43..5d95de1cf91 100644 --- a/pkg/cli/logs_parsing_firewall.go +++ b/pkg/cli/logs_parsing_firewall.go @@ -40,7 +40,12 @@ func parseFirewallLogs(runDir string, verbose bool) error { } // Check if squid logs directory exists in the run directory - // The logs could be in workflow-logs subdirectory or directly in the run directory + // The logs could be in several locations depending on the AWF version and artifact structure. + + // Primary path: sandbox/firewall/logs/squid-logs/ (AWF writes Squid logs here within --proxy-logs-dir) + sandboxSquidLogsDir := filepath.Join(runDir, "sandbox", "firewall", "logs", "squid-logs") + + // Legacy path: squid-logs/ directly in the run directory (older artifact format) squidLogsDir := filepath.Join(runDir, "squid-logs") // Also check for squid logs in workflow-logs directory @@ -48,7 +53,10 @@ func parseFirewallLogs(runDir string, verbose bool) error { // Determine which directory to use var logsDir string - if fileutil.DirExists(squidLogsDir) { + if fileutil.DirExists(sandboxSquidLogsDir) { + logsDir = sandboxSquidLogsDir + logsParsingFirewallLog.Printf("Found firewall logs in sandbox/firewall/logs/squid-logs directory") + } else if fileutil.DirExists(squidLogsDir) { logsDir = squidLogsDir logsParsingFirewallLog.Printf("Found firewall logs in squid-logs directory") } else if fileutil.DirExists(workflowLogsSquidDir) { From 60b5712c7eef7fe41878ab342682641f35d65c30 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 30 Jun 2026 18:40:47 +0000 Subject: [PATCH 3/4] fix: fall back when sandbox firewall log dirs are empty Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/cli/firewall_log.go | 5 +- pkg/cli/firewall_log_test.go | 26 ++++++++++ pkg/cli/logs_firewall_parse_test.go | 23 +++++++++ pkg/cli/logs_parsing_firewall.go | 75 +++++++++++++++++++++-------- 4 files changed, 107 insertions(+), 22 deletions(-) diff --git a/pkg/cli/firewall_log.go b/pkg/cli/firewall_log.go index cdebe5b8e85..afd73ecc955 100644 --- a/pkg/cli/firewall_log.go +++ b/pkg/cli/firewall_log.go @@ -384,7 +384,10 @@ func analyzeFirewallLogs(runDir string, verbose bool) (*FirewallAnalysis, error) if verbose { fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Found firewall logs directory: sandbox/firewall/logs/squid-logs")) } - return analyzeMultipleFirewallLogs(squidSubDir, verbose) + analysis, err := analyzeMultipleFirewallLogs(squidSubDir, verbose) + if err != nil || analysis != nil { + return analysis, err + } } // Fall back to direct *.log files at the sandbox/firewall/logs/ level (older AWF layout). firewallLogLog.Printf("Found firewall logs directory: sandbox/firewall/logs") diff --git a/pkg/cli/firewall_log_test.go b/pkg/cli/firewall_log_test.go index 3c094736500..91958137a3f 100644 --- a/pkg/cli/firewall_log_test.go +++ b/pkg/cli/firewall_log_test.go @@ -1081,6 +1081,32 @@ func TestAnalyzeFirewallLogsSandboxFallbackToTopLevel(t *testing.T) { } } +func TestAnalyzeFirewallLogsSandboxEmptySquidSubdirFallsBackToTopLevel(t *testing.T) { + tmpDir := testutil.TempDir(t, "test-sandbox-empty-squid-subdir-*") + + sandboxLogsDir := filepath.Join(tmpDir, "sandbox", "firewall", "logs") + if err := os.MkdirAll(filepath.Join(sandboxLogsDir, "squid-logs"), 0755); err != nil { + t.Fatalf("Failed to create sandbox/firewall/logs directories: %v", err) + } + + logContent := `1761332530.474 172.30.0.20:35288 api.github.com:443 140.82.112.5:443 1.1 CONNECT 200 TCP_TUNNEL:HIER_DIRECT api.github.com:443 "-" +` + if err := os.WriteFile(filepath.Join(sandboxLogsDir, "access.log"), []byte(logContent), 0644); err != nil { + t.Fatalf("Failed to write access.log: %v", err) + } + + analysis, err := analyzeFirewallLogs(tmpDir, false) + if err != nil { + t.Fatalf("analyzeFirewallLogs failed: %v", err) + } + if analysis == nil { + t.Fatal("Expected firewall analysis but got nil") + } + if analysis.TotalRequests != 1 { + t.Errorf("TotalRequests: got %d, want 1", analysis.TotalRequests) + } +} + func TestFirewallAnalysisAddMetricsDeduplicatesDomains(t *testing.T) { base := &FirewallAnalysis{ TotalRequests: 1, diff --git a/pkg/cli/logs_firewall_parse_test.go b/pkg/cli/logs_firewall_parse_test.go index 1fffe997a9d..deb91570af3 100644 --- a/pkg/cli/logs_firewall_parse_test.go +++ b/pkg/cli/logs_firewall_parse_test.go @@ -26,3 +26,26 @@ func TestParseFirewallLogsNoLogs(t *testing.T) { t.Errorf("firewall.md should not be created when no logs are present") } } + +func TestFindFirewallLogsDirSandboxFallbackToTopLevel(t *testing.T) { + tempDir := testutil.TempDir(t, "test-firewall-parse-*") + + sandboxLogsDir := filepath.Join(tempDir, "sandbox", "firewall", "logs") + if err := os.MkdirAll(filepath.Join(sandboxLogsDir, "squid-logs"), 0755); err != nil { + t.Fatalf("Failed to create firewall log directories: %v", err) + } + + logContent := `1761332531.123 172.30.0.20:35289 blocked.example.com:443 140.82.112.23:443 1.1 CONNECT 403 NONE_NONE:HIER_NONE blocked.example.com:443 "-" +` + if err := os.WriteFile(filepath.Join(sandboxLogsDir, "access.log"), []byte(logContent), 0644); err != nil { + t.Fatalf("Failed to write access.log: %v", err) + } + + logsDir, err := findFirewallLogsDir(tempDir) + if err != nil { + t.Fatalf("findFirewallLogsDir failed: %v", err) + } + if logsDir != sandboxLogsDir { + t.Fatalf("findFirewallLogsDir returned %q, want %q", logsDir, sandboxLogsDir) + } +} diff --git a/pkg/cli/logs_parsing_firewall.go b/pkg/cli/logs_parsing_firewall.go index 5d95de1cf91..353f52e65bd 100644 --- a/pkg/cli/logs_parsing_firewall.go +++ b/pkg/cli/logs_parsing_firewall.go @@ -42,27 +42,11 @@ func parseFirewallLogs(runDir string, verbose bool) error { // Check if squid logs directory exists in the run directory // The logs could be in several locations depending on the AWF version and artifact structure. - // Primary path: sandbox/firewall/logs/squid-logs/ (AWF writes Squid logs here within --proxy-logs-dir) - sandboxSquidLogsDir := filepath.Join(runDir, "sandbox", "firewall", "logs", "squid-logs") - - // Legacy path: squid-logs/ directly in the run directory (older artifact format) - squidLogsDir := filepath.Join(runDir, "squid-logs") - - // Also check for squid logs in workflow-logs directory - workflowLogsSquidDir := filepath.Join(runDir, "workflow-logs", "squid-logs") - - // Determine which directory to use - var logsDir string - if fileutil.DirExists(sandboxSquidLogsDir) { - logsDir = sandboxSquidLogsDir - logsParsingFirewallLog.Printf("Found firewall logs in sandbox/firewall/logs/squid-logs directory") - } else if fileutil.DirExists(squidLogsDir) { - logsDir = squidLogsDir - logsParsingFirewallLog.Printf("Found firewall logs in squid-logs directory") - } else if fileutil.DirExists(workflowLogsSquidDir) { - logsDir = workflowLogsSquidDir - logsParsingFirewallLog.Printf("Found firewall logs in workflow-logs/squid-logs directory") - } else { + logsDir, err := findFirewallLogsDir(runDir) + if err != nil { + return err + } + if logsDir == "" { logsParsingFirewallLog.Print("No firewall logs found, skipping parsing") // No firewall logs found - this is not an error, just skip parsing if verbose { @@ -247,3 +231,52 @@ originalMain(); return nil } + +func dirHasMatchingFiles(dir string, globPattern string) (bool, error) { + if !fileutil.DirExists(dir) { + return false, nil + } + + files, err := filepath.Glob(filepath.Join(dir, globPattern)) + if err != nil { + return false, fmt.Errorf("failed to find firewall log files in %s: %w", dir, err) + } + + return len(files) > 0, nil +} + +func findFirewallLogsDir(runDir string) (string, error) { + for _, candidate := range []struct { + path string + description string + }{ + { + path: filepath.Join(runDir, "sandbox", "firewall", "logs", "squid-logs"), + description: "sandbox/firewall/logs/squid-logs", + }, + { + path: filepath.Join(runDir, "sandbox", "firewall", "logs"), + description: "sandbox/firewall/logs", + }, + { + path: filepath.Join(runDir, "squid-logs"), + description: "squid-logs", + }, + { + path: filepath.Join(runDir, "workflow-logs", "squid-logs"), + description: "workflow-logs/squid-logs", + }, + } { + hasLogs, err := dirHasMatchingFiles(candidate.path, "*.log") + if err != nil { + return "", err + } + if !hasLogs { + continue + } + logsParsingFirewallLog.Printf("Found firewall logs in %s directory", candidate.description) + return candidate.path, nil + } + + return "", nil +} From 2d5d517535c30997210a0f4b0aeeb5dc9531b169 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 30 Jun 2026 18:43:24 +0000 Subject: [PATCH 4/4] refactor: make firewall fallback explicit Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/cli/firewall_log.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pkg/cli/firewall_log.go b/pkg/cli/firewall_log.go index afd73ecc955..bf5704656d4 100644 --- a/pkg/cli/firewall_log.go +++ b/pkg/cli/firewall_log.go @@ -385,8 +385,11 @@ func analyzeFirewallLogs(runDir string, verbose bool) (*FirewallAnalysis, error) fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Found firewall logs directory: sandbox/firewall/logs/squid-logs")) } analysis, err := analyzeMultipleFirewallLogs(squidSubDir, verbose) - if err != nil || analysis != nil { - return analysis, err + if err != nil { + return nil, err + } + if analysis != nil { + return analysis, nil } } // Fall back to direct *.log files at the sandbox/firewall/logs/ level (older AWF layout).