Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 44 additions & 30 deletions .github/workflows/daily-firewall-report.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

78 changes: 46 additions & 32 deletions .github/workflows/daily-firewall-report.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,49 +161,63 @@ Generate a comprehensive daily report of all rejected domains across all agentic

This prevents unnecessary re-analysis of the same data and significantly reduces token usage.

### Step 1: Identify Workflows with Firewall Feature

1. List all workflows in the repository
2. For each workflow that has `network.firewall: true` in its frontmatter, note the workflow name
3. Create a list of all firewall-enabled workflows

**Example frontmatter structure:**
```yaml
network:
firewall: true
### Step 1: Collect Recent Firewall-Enabled Workflow Runs

Use the `logs` tool from the agentic-workflows MCP server to efficiently collect workflow runs that have firewall enabled:

**Using the logs tool:**
Call the `logs` tool with the following parameters:
- `firewall`: true (boolean - to filter only runs with firewall enabled)
- `start_date`: "-7d" (to get runs from the past 7 days)
- `count`: 100 (to get up to 100 matching runs)

The tool will:
1. Filter runs based on the `steps.firewall` field in `aw_info.json` (e.g., "squid" when enabled)
2. Return only runs where firewall was enabled
3. Limit to runs from the past 7 days
4. Return up to 100 matching runs

**Tool call example:**
```json
{
"firewall": true,
"start_date": "-7d",
"count": 100
}
```

**Note:** The firewall field is under `network`, not `features`.
### Step 2: Analyze Firewall Logs from Collected Runs

### Step 2: Collect Recent Workflow Runs

For each firewall-enabled workflow:
1. Get up to 10 workflow runs that occurred within the past 7 days (if there are fewer than 10 runs in that window, include all available; if there are more, include only the most recent 10)
2. For each run ID, use the `audit` tool from the agentic-workflows MCP server with `--json` flag to get detailed firewall information
3. Store the run ID, workflow name, and timestamp for tracking
For each run collected in Step 1:
1. Use the `audit` tool from the agentic-workflows MCP server to get detailed firewall information
2. Store the run ID, workflow name, and timestamp for tracking

**Using the audit tool:**
```bash
# Get firewall analysis in JSON format
gh aw audit <run-id> --json

# Example jq filter to extract firewall data:
gh aw audit <run-id> --json | jq '{
run_id: .overview.run_id,
workflow: .overview.workflow_name,
firewall: .firewall_analysis // {},
denied_domains: .firewall_analysis.denied_domains // [],
allowed_domains: .firewall_analysis.allowed_domains // [],
total_requests: .firewall_analysis.total_requests // 0,
denied_requests: .firewall_analysis.denied_requests // 0
}'
Call the `audit` tool with the run_id parameter for each run from Step 1.

**Tool call example:**
```json
{
"run_id": 12345678
}
```

**Important:** Do NOT manually download and parse firewall log files. Always use the `audit` tool which provides structured firewall analysis data including:
The audit tool returns structured firewall analysis data including:
- Total requests, allowed requests, denied requests
- Lists of allowed and denied domains
- Request statistics per domain

**Example of extracting firewall data from audit result:**
```javascript
// From the audit tool result, access:
result.firewall_analysis.denied_domains // Array of denied domain names
result.firewall_analysis.allowed_domains // Array of allowed domain names
result.firewall_analysis.total_requests // Total number of network requests
result.firewall_analysis.denied_requests // Number of denied requests
```

**Important:** Do NOT manually download and parse firewall log files. Always use the `audit` tool which provides structured firewall analysis data.

### Step 3: Parse and Analyze Firewall Logs

Use the JSON output from the `audit` tool to extract firewall information. The `firewall_analysis` field in the audit JSON contains:
Expand Down
60 changes: 52 additions & 8 deletions pkg/cli/logs.go
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,8 @@ Examples:
` + constants.CLIExtensionPrefix + ` logs --engine claude # Filter logs by claude engine
` + constants.CLIExtensionPrefix + ` logs --engine codex # Filter logs by codex engine
` + constants.CLIExtensionPrefix + ` logs --engine copilot # Filter logs by copilot engine
` + constants.CLIExtensionPrefix + ` logs --firewall # Filter logs with firewall enabled
` + constants.CLIExtensionPrefix + ` logs --no-firewall # Filter logs without firewall
` + constants.CLIExtensionPrefix + ` logs -o ./my-logs # Custom output directory
` + constants.CLIExtensionPrefix + ` logs --branch main # Filter logs by branch name
` + constants.CLIExtensionPrefix + ` logs --branch feature-xyz # Filter logs by feature branch
Expand Down Expand Up @@ -371,6 +373,8 @@ Examples:
verbose, _ := cmd.Flags().GetBool("verbose")
toolGraph, _ := cmd.Flags().GetBool("tool-graph")
noStaged, _ := cmd.Flags().GetBool("no-staged")
firewallOnly, _ := cmd.Flags().GetBool("firewall")
noFirewall, _ := cmd.Flags().GetBool("no-firewall")
parse, _ := cmd.Flags().GetBool("parse")
jsonOutput, _ := cmd.Flags().GetBool("json")
timeout, _ := cmd.Flags().GetInt("timeout")
Expand Down Expand Up @@ -413,7 +417,16 @@ Examples:
}
}

if err := DownloadWorkflowLogs(workflowName, count, startDate, endDate, outputDir, engine, branch, beforeRunID, afterRunID, verbose, toolGraph, noStaged, parse, jsonOutput, timeout); err != nil {
// Validate firewall parameters
if firewallOnly && noFirewall {
fmt.Fprintln(os.Stderr, console.FormatError(console.CompilerError{
Type: "error",
Message: "cannot specify both --firewall and --no-firewall flags",
}))
os.Exit(1)
}

if err := DownloadWorkflowLogs(workflowName, count, startDate, endDate, outputDir, engine, branch, beforeRunID, afterRunID, verbose, toolGraph, noStaged, firewallOnly, noFirewall, parse, jsonOutput, timeout); err != nil {
fmt.Fprintln(os.Stderr, console.FormatError(console.CompilerError{
Type: "error",
Message: err.Error(),
Expand All @@ -434,6 +447,8 @@ Examples:
logsCmd.Flags().Int64("after-run-id", 0, "Filter runs with database ID after this value (exclusive)")
logsCmd.Flags().Bool("tool-graph", false, "Generate Mermaid tool sequence graph from agent logs")
logsCmd.Flags().Bool("no-staged", false, "Filter out staged workflow runs (exclude runs with staged: true in aw_info.json)")
logsCmd.Flags().Bool("firewall", false, "Filter to only runs with firewall enabled")
logsCmd.Flags().Bool("no-firewall", false, "Filter to only runs without firewall enabled")
logsCmd.Flags().Bool("parse", false, "Run JavaScript parsers on agent logs and firewall logs, writing markdown to log.md and firewall.md")
logsCmd.Flags().Bool("json", false, "Output logs data as JSON instead of formatted console tables")
logsCmd.Flags().Int("timeout", 0, "Maximum time in seconds to spend downloading logs (0 = no timeout)")
Expand All @@ -442,7 +457,7 @@ Examples:
}

// DownloadWorkflowLogs downloads and analyzes workflow logs with metrics
func DownloadWorkflowLogs(workflowName string, count int, startDate, endDate, outputDir, engine, branch string, beforeRunID, afterRunID int64, verbose bool, toolGraph bool, noStaged bool, parse bool, jsonOutput bool, timeout int) error {
func DownloadWorkflowLogs(workflowName string, count int, startDate, endDate, outputDir, engine, branch string, beforeRunID, afterRunID int64, verbose bool, toolGraph bool, noStaged bool, firewallOnly bool, noFirewall bool, parse bool, jsonOutput bool, timeout int) error {
logsLog.Printf("Starting workflow log download: workflow=%s, count=%d, startDate=%s, endDate=%s, outputDir=%s", workflowName, count, startDate, endDate, outputDir)
if verbose {
fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Fetching workflow runs from GitHub Actions..."))
Expand Down Expand Up @@ -560,10 +575,19 @@ func DownloadWorkflowLogs(workflowName string, count int, startDate, endDate, ou
continue
}

// Parse aw_info.json once for all filters that need it (optimization)
var awInfo *AwInfo
var awInfoErr error
awInfoPath := filepath.Join(result.LogsPath, "aw_info.json")

// Only parse if we need it for any filter
if engine != "" || noStaged || firewallOnly || noFirewall {
awInfo, awInfoErr = parseAwInfo(awInfoPath, verbose)
}

// Apply engine filtering if specified
if engine != "" {
// Check if the run's engine matches the filter
awInfoPath := filepath.Join(result.LogsPath, "aw_info.json")
detectedEngine := extractEngineFromAwInfo(awInfoPath, verbose)

var engineMatches bool
Expand Down Expand Up @@ -599,12 +623,9 @@ func DownloadWorkflowLogs(workflowName string, count int, startDate, endDate, ou

// Apply staged filtering if --no-staged flag is specified
if noStaged {
// Check if the run is staged
awInfoPath := filepath.Join(result.LogsPath, "aw_info.json")
info, err := parseAwInfo(awInfoPath, verbose)
var isStaged bool
if err == nil && info != nil {
isStaged = info.Staged
if awInfoErr == nil && awInfo != nil {
isStaged = awInfo.Staged
}

if isStaged {
Expand All @@ -615,6 +636,29 @@ func DownloadWorkflowLogs(workflowName string, count int, startDate, endDate, ou
}
}

// Apply firewall filtering if --firewall or --no-firewall flag is specified
if firewallOnly || noFirewall {
var hasFirewall bool
if awInfoErr == nil && awInfo != nil {
// Firewall is enabled if steps.firewall is non-empty (e.g., "squid")
hasFirewall = awInfo.Steps.Firewall != ""
}

// Check if the run matches the filter
if firewallOnly && !hasFirewall {
if verbose {
fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Skipping run %d: workflow does not use firewall (filtered by --firewall)", result.Run.DatabaseID)))
}
continue
}
if noFirewall && hasFirewall {
if verbose {
fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Skipping run %d: workflow uses firewall (filtered by --no-firewall)", result.Run.DatabaseID)))
}
continue
}
}

// Update run with metrics and path
run := result.Run
run.TokenUsage = result.Metrics.TokenUsage
Expand Down
Loading
Loading