diff --git a/pkg/workflow/firewall_args_integration_test.go b/pkg/workflow/firewall_args_integration_test.go index d5536ffa76..7dda6aaaba 100644 --- a/pkg/workflow/firewall_args_integration_test.go +++ b/pkg/workflow/firewall_args_integration_test.go @@ -148,4 +148,88 @@ Test workflow without custom AWF arguments. t.Error("Compiled workflow should contain '--log-level' flag") } }) + + t.Run("workflow with ssl-bump and allow-urls compiles correctly", func(t *testing.T) { + // Create temporary directory for test + tmpDir := testutil.TempDir(t, "test-*") + workflowsDir := filepath.Join(tmpDir, ".github", "workflows") + err := os.MkdirAll(workflowsDir, 0755) + if err != nil { + t.Fatalf("Failed to create workflows directory: %v", err) + } + + // Create test workflow with ssl-bump and allow-urls + workflowContent := `--- +on: workflow_dispatch +permissions: + contents: read +engine: copilot +network: + allowed: + - "github.com" + - "api.github.com" + firewall: + ssl-bump: true + allow-urls: + - "https://github.com/githubnext/*" + - "https://api.github.com/repos/*" + log-level: debug +--- + +# Test SSL Bump Workflow + +Test workflow with SSL bump and allow-urls configuration. +` + + workflowPath := filepath.Join(workflowsDir, "test-ssl-bump.md") + err = os.WriteFile(workflowPath, []byte(workflowContent), 0644) + if err != nil { + t.Fatalf("Failed to write workflow file: %v", err) + } + + // Compile the workflow + compiler := NewCompilerWithVersion("test-ssl-bump") + compiler.SetSkipValidation(true) + + if err := compiler.CompileWorkflow(workflowPath); err != nil { + t.Fatalf("Failed to compile workflow: %v", err) + } + + // Read the compiled workflow + lockPath := filepath.Join(workflowsDir, "test-ssl-bump.lock.yml") + lockContent, err := os.ReadFile(lockPath) + if err != nil { + t.Fatalf("Failed to read compiled workflow: %v", err) + } + + lockYAML := string(lockContent) + + // Verify ssl-bump flag is present + if !strings.Contains(lockYAML, "--ssl-bump") { + t.Error("Compiled workflow should contain '--ssl-bump' flag") + } + + // Verify allow-urls flag is present + if !strings.Contains(lockYAML, "--allow-urls") { + t.Error("Compiled workflow should contain '--allow-urls' flag") + } + + // Verify the URL patterns are present + if !strings.Contains(lockYAML, "https://github.com/githubnext/*") { + t.Error("Compiled workflow should contain URL pattern 'https://github.com/githubnext/*'") + } + + if !strings.Contains(lockYAML, "https://api.github.com/repos/*") { + t.Error("Compiled workflow should contain URL pattern 'https://api.github.com/repos/*'") + } + + // Verify standard AWF flags are still present + if !strings.Contains(lockYAML, "--env-all") { + t.Error("Compiled workflow should still contain '--env-all' flag") + } + + if !strings.Contains(lockYAML, "--log-level debug") { + t.Error("Compiled workflow should contain '--log-level debug'") + } + }) } diff --git a/pkg/workflow/frontmatter_extraction_security.go b/pkg/workflow/frontmatter_extraction_security.go index f471a58f39..a85edcb3dc 100644 --- a/pkg/workflow/frontmatter_extraction_security.go +++ b/pkg/workflow/frontmatter_extraction_security.go @@ -126,6 +126,24 @@ func (c *Compiler) extractFirewallConfig(firewall any) *FirewallConfig { } } + // Extract ssl-bump if present + if sslBump, hasSslBump := firewallObj["ssl-bump"]; hasSslBump { + if sslBumpBool, ok := sslBump.(bool); ok { + config.SSLBump = sslBumpBool + } + } + + // Extract allow-urls if present + if allowUrls, hasAllowUrls := firewallObj["allow-urls"]; hasAllowUrls { + if urlsSlice, ok := allowUrls.([]any); ok { + for _, url := range urlsSlice { + if urlStr, ok := url.(string); ok { + config.AllowURLs = append(config.AllowURLs, urlStr) + } + } + } + } + return config } diff --git a/pkg/workflow/frontmatter_extraction_security_test.go b/pkg/workflow/frontmatter_extraction_security_test.go index d0553b9dff..69804d74c0 100644 --- a/pkg/workflow/frontmatter_extraction_security_test.go +++ b/pkg/workflow/frontmatter_extraction_security_test.go @@ -2,5 +2,118 @@ package workflow -// Tests for security and network extraction functions -// Placeholder - add specific tests for network, firewall, and sandbox extraction as needed +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestExtractFirewallConfig tests the extraction of firewall configuration from frontmatter +func TestExtractFirewallConfig(t *testing.T) { + compiler := &Compiler{} + + t.Run("extracts ssl-bump boolean field", func(t *testing.T) { + firewallObj := map[string]any{ + "ssl-bump": true, + } + + config := compiler.extractFirewallConfig(firewallObj) + require.NotNil(t, config, "Should extract firewall config") + assert.True(t, config.Enabled, "Should be enabled") + assert.True(t, config.SSLBump, "Should have ssl-bump enabled") + }) + + t.Run("extracts allow-urls string array", func(t *testing.T) { + firewallObj := map[string]any{ + "ssl-bump": true, + "allow-urls": []any{ + "https://github.com/githubnext/*", + "https://api.github.com/repos/*", + }, + } + + config := compiler.extractFirewallConfig(firewallObj) + require.NotNil(t, config, "Should extract firewall config") + assert.True(t, config.SSLBump, "Should have ssl-bump enabled") + assert.Len(t, config.AllowURLs, 2, "Should have 2 allow-urls") + assert.Equal(t, "https://github.com/githubnext/*", config.AllowURLs[0], "First URL should match") + assert.Equal(t, "https://api.github.com/repos/*", config.AllowURLs[1], "Second URL should match") + }) + + t.Run("extracts all fields together", func(t *testing.T) { + firewallObj := map[string]any{ + "args": []any{"--custom-arg", "value"}, + "version": "v1.0.0", + "log-level": "debug", + "ssl-bump": true, + "allow-urls": []any{"https://example.com/*"}, + } + + config := compiler.extractFirewallConfig(firewallObj) + require.NotNil(t, config, "Should extract firewall config") + assert.True(t, config.Enabled, "Should be enabled") + assert.Len(t, config.Args, 2, "Should have 2 args") + assert.Equal(t, "v1.0.0", config.Version, "Should extract version") + assert.Equal(t, "debug", config.LogLevel, "Should extract log-level") + assert.True(t, config.SSLBump, "Should have ssl-bump enabled") + assert.Len(t, config.AllowURLs, 1, "Should have 1 allow-url") + assert.Equal(t, "https://example.com/*", config.AllowURLs[0], "Should extract allow-url") + }) + + t.Run("ssl-bump defaults to false when not specified", func(t *testing.T) { + firewallObj := map[string]any{ + "version": "v1.0.0", + } + + config := compiler.extractFirewallConfig(firewallObj) + require.NotNil(t, config, "Should extract firewall config") + assert.False(t, config.SSLBump, "ssl-bump should default to false") + }) + + t.Run("allow-urls defaults to empty when not specified", func(t *testing.T) { + firewallObj := map[string]any{ + "ssl-bump": true, + } + + config := compiler.extractFirewallConfig(firewallObj) + require.NotNil(t, config, "Should extract firewall config") + assert.Nil(t, config.AllowURLs, "allow-urls should be nil when not specified") + }) + + t.Run("handles non-string values in allow-urls gracefully", func(t *testing.T) { + firewallObj := map[string]any{ + "allow-urls": []any{ + "https://github.com/*", + 123, // Invalid: number instead of string + "https://api.github.com/*", + }, + } + + config := compiler.extractFirewallConfig(firewallObj) + require.NotNil(t, config, "Should extract firewall config") + assert.Len(t, config.AllowURLs, 2, "Should skip non-string values") + assert.Equal(t, "https://github.com/*", config.AllowURLs[0], "First valid URL should be extracted") + assert.Equal(t, "https://api.github.com/*", config.AllowURLs[1], "Second valid URL should be extracted") + }) + + t.Run("handles non-boolean ssl-bump gracefully", func(t *testing.T) { + firewallObj := map[string]any{ + "ssl-bump": "true", // String instead of boolean + } + + config := compiler.extractFirewallConfig(firewallObj) + require.NotNil(t, config, "Should extract firewall config") + assert.False(t, config.SSLBump, "Should ignore non-boolean ssl-bump value") + }) + + t.Run("handles non-array allow-urls gracefully", func(t *testing.T) { + firewallObj := map[string]any{ + "allow-urls": "https://github.com/*", // String instead of array + } + + config := compiler.extractFirewallConfig(firewallObj) + require.NotNil(t, config, "Should extract firewall config") + assert.Nil(t, config.AllowURLs, "Should ignore non-array allow-urls value") + }) +}