diff --git a/.github/workflows/dictation-prompt.lock.yml b/.github/workflows/dictation-prompt.lock.yml index 8f1b650a84..3e59499ed5 100644 --- a/.github/workflows/dictation-prompt.lock.yml +++ b/.github/workflows/dictation-prompt.lock.yml @@ -29,7 +29,6 @@ name: "Dictation Prompt Generator" "on": schedule: - cron: "0 6 * * 0" - # Friendly format: weekly on wednesday at 12:00 workflow_dispatch: permissions: diff --git a/pkg/cli/firewall_log.go b/pkg/cli/firewall_log.go index bdcc11ac0e..ec225ffd92 100644 --- a/pkg/cli/firewall_log.go +++ b/pkg/cli/firewall_log.go @@ -55,13 +55,8 @@ var ( // - Comment lines (starting with #) are skipped // - Empty lines are skipped // - Lines with fewer than 10 fields are rejected -// - Field validation uses regex patterns matching the JavaScript parser: -// * timestamp: must be numeric with optional decimal point -// * client_ip:port: must be IP:port format or "-" -// * domain: must be domain:port format or "-" -// * dest_ip:port: must be IP:port format or "-" -// * status: must be numeric or "-" -// * decision: must contain ":" or be "-" +// - Only timestamp field is validated (must be numeric with optional decimal point) +// - Other fields are accepted as-is without validation (matches JavaScript parser behavior) // - User agent quotes are automatically stripped // // # Request Classification @@ -164,49 +159,19 @@ func parseFirewallLogLine(line string) *FirewallLogEntry { return nil } - // Validate timestamp format (should be numeric with optional decimal point) + // Only validate timestamp (essential for log format detection) + // This matches the JavaScript parser behavior which only validates timestamp timestamp := fields[0] if matched, _ := regexp.MatchString(`^\d+(\.\d+)?$`, timestamp); !matched { return nil } - // Validate client IP:port format (should be IP:port or "-") + // Extract fields without validation (matches JavaScript parser) clientIPPort := fields[1] - if clientIPPort != "-" { - if matched, _ := regexp.MatchString(`^[\d.]+:\d+$`, clientIPPort); !matched { - return nil - } - } - - // Validate domain format (should be domain:port or "-") domain := fields[2] - if domain != "-" { - if matched, _ := regexp.MatchString(`^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)*:\d+$`, domain); !matched { - return nil - } - } - - // Validate dest IP:port format (should be IP:port or "-") destIPPort := fields[3] - if destIPPort != "-" { - if matched, _ := regexp.MatchString(`^[\d.]+:\d+$`, destIPPort); !matched { - return nil - } - } - - // Validate status code (should be numeric or "-") status := fields[6] - if status != "-" { - if matched, _ := regexp.MatchString(`^\d+$`, status); !matched { - return nil - } - } - - // Validate decision format (should contain ":" or be "-") decision := fields[7] - if decision != "-" && !strings.Contains(decision, ":") { - return nil - } // Remove quotes from user agent userAgent := fields[9] diff --git a/pkg/cli/firewall_log_test.go b/pkg/cli/firewall_log_test.go index fcd0e21db9..dfc50bbdd5 100644 --- a/pkg/cli/firewall_log_test.go +++ b/pkg/cli/firewall_log_test.go @@ -64,29 +64,84 @@ func TestParseFirewallLogLine(t *testing.T) { expected: nil, }, { - name: "invalid client IP:port format", - line: `1761332530.474 Accepting api.github.com:443 140.82.112.22:443 1.1 CONNECT 200 TCP_TUNNEL:HIER_DIRECT api.github.com:443 "-"`, - expected: nil, + name: "non-standard client IP:port format is accepted", + line: `1761332530.474 Accepting api.github.com:443 140.82.112.22:443 1.1 CONNECT 200 TCP_TUNNEL:HIER_DIRECT api.github.com:443 "-"`, + expected: &FirewallLogEntry{ + Timestamp: "1761332530.474", + ClientIPPort: "Accepting", + Domain: "api.github.com:443", + DestIPPort: "140.82.112.22:443", + Proto: "1.1", + Method: "CONNECT", + Status: "200", + Decision: "TCP_TUNNEL:HIER_DIRECT", + URL: "api.github.com:443", + UserAgent: "-", + }, }, { - name: "invalid domain format (no port)", - line: `1761332530.474 172.30.0.20:35288 DNS 140.82.112.22:443 1.1 CONNECT 200 TCP_TUNNEL:HIER_DIRECT api.github.com:443 "-"`, - expected: nil, + name: "non-standard domain format is accepted", + line: `1761332530.474 172.30.0.20:35288 DNS 140.82.112.22:443 1.1 CONNECT 200 TCP_TUNNEL:HIER_DIRECT api.github.com:443 "-"`, + expected: &FirewallLogEntry{ + Timestamp: "1761332530.474", + ClientIPPort: "172.30.0.20:35288", + Domain: "DNS", + DestIPPort: "140.82.112.22:443", + Proto: "1.1", + Method: "CONNECT", + Status: "200", + Decision: "TCP_TUNNEL:HIER_DIRECT", + URL: "api.github.com:443", + UserAgent: "-", + }, }, { - name: "invalid dest IP:port format", - line: `1761332530.474 172.30.0.20:35288 api.github.com:443 Local 1.1 CONNECT 200 TCP_TUNNEL:HIER_DIRECT api.github.com:443 "-"`, - expected: nil, + name: "non-standard dest IP:port format is accepted", + line: `1761332530.474 172.30.0.20:35288 api.github.com:443 Local 1.1 CONNECT 200 TCP_TUNNEL:HIER_DIRECT api.github.com:443 "-"`, + expected: &FirewallLogEntry{ + Timestamp: "1761332530.474", + ClientIPPort: "172.30.0.20:35288", + Domain: "api.github.com:443", + DestIPPort: "Local", + Proto: "1.1", + Method: "CONNECT", + Status: "200", + Decision: "TCP_TUNNEL:HIER_DIRECT", + URL: "api.github.com:443", + UserAgent: "-", + }, }, { - name: "invalid status code (non-numeric)", - line: `1761332530.474 172.30.0.20:35288 api.github.com:443 140.82.112.22:443 1.1 CONNECT Swap TCP_TUNNEL:HIER_DIRECT api.github.com:443 "-"`, - expected: nil, + name: "non-numeric status code is accepted", + line: `1761332530.474 172.30.0.20:35288 api.github.com:443 140.82.112.22:443 1.1 CONNECT Swap TCP_TUNNEL:HIER_DIRECT api.github.com:443 "-"`, + expected: &FirewallLogEntry{ + Timestamp: "1761332530.474", + ClientIPPort: "172.30.0.20:35288", + Domain: "api.github.com:443", + DestIPPort: "140.82.112.22:443", + Proto: "1.1", + Method: "CONNECT", + Status: "Swap", + Decision: "TCP_TUNNEL:HIER_DIRECT", + URL: "api.github.com:443", + UserAgent: "-", + }, }, { - name: "invalid decision format (no colon)", - line: `1761332530.474 172.30.0.20:35288 api.github.com:443 140.82.112.22:443 1.1 CONNECT 200 Waiting api.github.com:443 "-"`, - expected: nil, + name: "decision format without colon is accepted", + line: `1761332530.474 172.30.0.20:35288 api.github.com:443 140.82.112.22:443 1.1 CONNECT 200 Waiting api.github.com:443 "-"`, + expected: &FirewallLogEntry{ + Timestamp: "1761332530.474", + ClientIPPort: "172.30.0.20:35288", + Domain: "api.github.com:443", + DestIPPort: "140.82.112.22:443", + Proto: "1.1", + Method: "CONNECT", + Status: "200", + Decision: "Waiting", + URL: "api.github.com:443", + UserAgent: "-", + }, }, { name: "fewer than 10 fields", @@ -94,9 +149,20 @@ func TestParseFirewallLogLine(t *testing.T) { expected: nil, }, { - name: "line with pipe character in domain position", - line: `1761332530.474 172.30.0.20:35288 pinger|test 140.82.112.22:443 1.1 CONNECT 200 TCP_TUNNEL:HIER_DIRECT api.github.com:443 "-"`, - expected: nil, + name: "line with pipe character in domain position is accepted", + line: `1761332530.474 172.30.0.20:35288 pinger|test 140.82.112.22:443 1.1 CONNECT 200 TCP_TUNNEL:HIER_DIRECT api.github.com:443 "-"`, + expected: &FirewallLogEntry{ + Timestamp: "1761332530.474", + ClientIPPort: "172.30.0.20:35288", + Domain: "pinger|test", + DestIPPort: "140.82.112.22:443", + Proto: "1.1", + Method: "CONNECT", + Status: "200", + Decision: "TCP_TUNNEL:HIER_DIRECT", + URL: "api.github.com:443", + UserAgent: "-", + }, }, } @@ -334,14 +400,19 @@ Invalid line with not enough fields t.Fatalf("Failed to parse firewall log: %v", err) } - // Should only have parsed 2 valid lines - if analysis.TotalRequests != 2 { - t.Errorf("TotalRequests: got %d, want 2 (should skip malformed lines)", analysis.TotalRequests) + // Should have parsed 3 valid lines (relaxed validation accepts INVALID_IP like JavaScript parser) + // Lines with valid timestamps and 10 fields are accepted, even if field formats are non-standard + if analysis.TotalRequests != 3 { + t.Errorf("TotalRequests: got %d, want 3 (non-standard formats accepted)", 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) + } } func TestParseFirewallLogPartialMissingFields(t *testing.T) {