diff --git a/.github/workflows/dev.lock.yml b/.github/workflows/dev.lock.yml index a11def33c3..9a80674fb2 100644 --- a/.github/workflows/dev.lock.yml +++ b/.github/workflows/dev.lock.yml @@ -171,7 +171,7 @@ jobs: awf_version: "", awmg_version: "v0.1.4", steps: { - firewall: "" + firewall: "squid" }, created_at: new Date().toISOString() }; @@ -191,6 +191,8 @@ jobs: COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - name: Install GitHub Copilot CLI run: /opt/gh-aw/actions/install_copilot_cli.sh 0.0.410 + - name: Install awf binary + run: bash /opt/gh-aw/actions/install_awf_binary.sh v0.18.0 - name: Determine automatic lockdown mode for GitHub MCP Server id: determine-automatic-lockdown uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 @@ -202,7 +204,7 @@ jobs: const determineAutomaticLockdown = require('/opt/gh-aw/actions/determine_automatic_lockdown.cjs'); await determineAutomaticLockdown(github, context, core); - name: Download container images - run: bash /opt/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-mcpg:v0.1.4 ghcr.io/github/github-mcp-server:v0.30.3 node:lts-alpine + run: bash /opt/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.18.0 ghcr.io/github/gh-aw-firewall/api-proxy:0.18.0 ghcr.io/github/gh-aw-firewall/squid:0.18.0 ghcr.io/github/gh-aw-mcpg:v0.1.4 ghcr.io/github/github-mcp-server:v0.30.3 node:lts-alpine - name: Write Safe Outputs Config run: | mkdir -p /opt/gh-aw/safeoutputs diff --git a/.github/workflows/issue-classifier.lock.yml b/.github/workflows/issue-classifier.lock.yml index aafd966b38..eb59de120d 100644 --- a/.github/workflows/issue-classifier.lock.yml +++ b/.github/workflows/issue-classifier.lock.yml @@ -186,7 +186,7 @@ jobs: awf_version: "", awmg_version: "v0.1.4", steps: { - firewall: "" + firewall: "squid" }, created_at: new Date().toISOString() }; @@ -210,7 +210,7 @@ jobs: const determineAutomaticLockdown = require('/opt/gh-aw/actions/determine_automatic_lockdown.cjs'); await determineAutomaticLockdown(github, context, core); - name: Download container images - run: bash /opt/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-mcpg:v0.1.4 ghcr.io/github/github-mcp-server:v0.30.3 node:lts-alpine + run: bash /opt/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.18.0 ghcr.io/github/gh-aw-firewall/squid:0.18.0 ghcr.io/github/gh-aw-mcpg:v0.1.4 ghcr.io/github/github-mcp-server:v0.30.3 node:lts-alpine - name: Write Safe Outputs Config run: | mkdir -p /opt/gh-aw/safeoutputs diff --git a/.github/workflows/smoke-copilot-sdk.lock.yml b/.github/workflows/smoke-copilot-sdk.lock.yml index 6771bfcb64..db8c0bc287 100644 --- a/.github/workflows/smoke-copilot-sdk.lock.yml +++ b/.github/workflows/smoke-copilot-sdk.lock.yml @@ -236,7 +236,7 @@ jobs: awf_version: "", awmg_version: "v0.1.4", steps: { - firewall: "" + firewall: "squid" }, created_at: new Date().toISOString() }; @@ -256,6 +256,8 @@ jobs: COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - name: Install GitHub Copilot CLI run: /opt/gh-aw/actions/install_copilot_cli.sh 0.0.410 + - name: Install awf binary + run: bash /opt/gh-aw/actions/install_awf_binary.sh v0.18.0 - name: Determine automatic lockdown mode for GitHub MCP Server id: determine-automatic-lockdown uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 @@ -267,7 +269,7 @@ jobs: const determineAutomaticLockdown = require('/opt/gh-aw/actions/determine_automatic_lockdown.cjs'); await determineAutomaticLockdown(github, context, core); - name: Download container images - run: bash /opt/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-mcpg:v0.1.4 ghcr.io/github/github-mcp-server:v0.30.3 node:lts-alpine + run: bash /opt/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.18.0 ghcr.io/github/gh-aw-firewall/api-proxy:0.18.0 ghcr.io/github/gh-aw-firewall/squid:0.18.0 ghcr.io/github/gh-aw-mcpg:v0.1.4 ghcr.io/github/github-mcp-server:v0.30.3 node:lts-alpine - name: Install gh-aw extension env: GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/smoke-opencode.lock.yml b/.github/workflows/smoke-opencode.lock.yml index 6582396478..55c6e812eb 100644 --- a/.github/workflows/smoke-opencode.lock.yml +++ b/.github/workflows/smoke-opencode.lock.yml @@ -209,7 +209,7 @@ jobs: awf_version: "", awmg_version: "v0.1.4", steps: { - firewall: "" + firewall: "squid" }, created_at: new Date().toISOString() }; @@ -233,7 +233,7 @@ jobs: const determineAutomaticLockdown = require('/opt/gh-aw/actions/determine_automatic_lockdown.cjs'); await determineAutomaticLockdown(github, context, core); - name: Download container images - run: bash /opt/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-mcpg:v0.1.4 ghcr.io/github/github-mcp-server:v0.30.3 node:lts-alpine + run: bash /opt/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.18.0 ghcr.io/github/gh-aw-firewall/squid:0.18.0 ghcr.io/github/gh-aw-mcpg:v0.1.4 ghcr.io/github/github-mcp-server:v0.30.3 node:lts-alpine - name: Write Safe Outputs Config run: | mkdir -p /opt/gh-aw/safeoutputs diff --git a/pkg/cli/workflows/test-custom-agent.md b/pkg/cli/workflows/test-custom-agent.md index 6f10a1aa7f..0fd3877ab6 100644 --- a/pkg/cli/workflows/test-custom-agent.md +++ b/pkg/cli/workflows/test-custom-agent.md @@ -10,11 +10,11 @@ network: - example.com firewall: true -# Custom Agent Configuration (works for both AWF and SRT) +# Custom Agent Configuration (works for AWF) # This example shows how to use a custom command to replace the standard AWF binary sandbox: agent: - id: awf # Agent identifier (awf or srt) + id: awf # Agent identifier (awf) command: "docker run --rm -it my-custom-awf-image" # Custom command replaces AWF binary download args: - "--custom-logging" # Additional arguments appended to AWF command @@ -33,9 +33,9 @@ tools: # Custom Agent Configuration Example -This workflow demonstrates the custom agent configuration capabilities that work for **both AWF and SRT**: +This workflow demonstrates the custom agent configuration capabilities for **AWF** (Agent Workflow Firewall): -1. **Custom Command**: Replace the standard AWF or SRT installation with any command (e.g., Docker container, custom script) +1. **Custom Command**: Replace the standard AWF installation with any command (e.g., Docker container, custom script) 2. **Custom Args**: Add additional arguments that are appended to the command 3. **Custom Env**: Set environment variables on the execution step @@ -47,12 +47,6 @@ This workflow demonstrates the custom agent configuration capabilities that work - **Testing**: Use a modified AWF binary for testing new features - **Debugging**: Add debug flags and environment variables for troubleshooting -### For SRT (Sandbox Runtime) -- **Custom SRT Wrapper**: Use a custom wrapper around the Anthropic Sandbox Runtime -- **Pre-configured Container**: Run SRT from a Docker image with custom settings -- **Custom Isolation**: Implement custom sandboxing logic that wraps SRT -- **Testing & Development**: Use a modified SRT setup for testing - ## Example Configurations ### AWF with Custom Command @@ -67,24 +61,10 @@ sandbox: AWF_LOG_LEVEL: "debug" ``` -### SRT with Custom Command - -```yaml -features: - sandbox-runtime: true -sandbox: - agent: - id: srt - command: "custom-srt-wrapper" - args: ["--custom-arg"] - env: - SRT_DEBUG: "true" -``` - ## Configuration Reference The `sandbox.agent` object supports: -- `id`: Agent identifier ("awf" or "srt") +- `id`: Agent identifier ("awf") - `command`: Custom command to replace the default installation (optional) - `args`: Array of additional arguments to append (optional) - `env`: Object with environment variables to set (optional) @@ -101,4 +81,6 @@ sandbox: type: awf # Still works! ``` +Legacy `srt` and `sandbox-runtime` values are automatically migrated to `awf`. + Review the changes in this pull request. diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json index a89292d4f5..1cf4404042 100644 --- a/pkg/parser/schemas/main_workflow_schema.json +++ b/pkg/parser/schemas/main_workflow_schema.json @@ -2229,12 +2229,12 @@ ] }, "sandbox": { - "description": "Sandbox configuration for AI engines. Controls agent sandbox (AWF or Sandbox Runtime) and MCP gateway. The MCP gateway is always enabled and cannot be disabled.", + "description": "Sandbox configuration for AI engines. Controls agent sandbox (AWF) and MCP gateway. The MCP gateway is always enabled and cannot be disabled.", "oneOf": [ { "type": "string", - "enum": ["default", "awf", "srt", "sandbox-runtime"], - "description": "Legacy string format for sandbox type: 'default' for no sandbox, 'awf' for Agent Workflow Firewall, 'srt' and 'sandbox-runtime' are deprecated and will be migrated to 'awf'" + "enum": ["default", "awf"], + "description": "String format for sandbox type: 'default' for no sandbox, 'awf' for Agent Workflow Firewall. Note: Legacy 'srt' and 'sandbox-runtime' values are automatically migrated to 'awf'" }, { "type": "object", @@ -2242,8 +2242,8 @@ "properties": { "type": { "type": "string", - "enum": ["default", "awf", "srt", "sandbox-runtime"], - "description": "Legacy sandbox type field (use agent instead). 'srt' and 'sandbox-runtime' are deprecated and will be migrated to 'awf'" + "enum": ["default", "awf"], + "description": "Legacy sandbox type field (use agent instead). Note: Legacy 'srt' and 'sandbox-runtime' values are automatically migrated to 'awf'" }, "agent": { "description": "Agent sandbox type: 'awf' uses AWF (Agent Workflow Firewall), or false to disable agent sandbox. Defaults to 'awf' if not specified. Note: Disabling the agent sandbox (false) removes firewall protection but keeps the MCP gateway enabled.", diff --git a/pkg/workflow/compiler_safe_outputs.go b/pkg/workflow/compiler_safe_outputs.go index 9cba1fbc59..6df173cd13 100644 --- a/pkg/workflow/compiler_safe_outputs.go +++ b/pkg/workflow/compiler_safe_outputs.go @@ -326,7 +326,7 @@ func (c *Compiler) applyDefaultTools(tools map[string]any, safeOutputs *SafeOutp // Enable edit and bash tools by default when sandbox is enabled // The sandbox is enabled when: - // 1. Explicitly configured via sandbox.agent (awf/srt) + // 1. Explicitly configured via sandbox.agent (awf) // 2. Auto-enabled by firewall default enablement (when network restrictions are present) if isSandboxEnabled(sandboxConfig, networkPermissions) { compilerSafeOutputsLog.Print("Sandbox enabled, applying default edit and bash tools") @@ -467,9 +467,8 @@ func needsGitCommands(safeOutputs *SafeOutputsConfig) bool { // isSandboxEnabled checks if the sandbox is enabled (either explicitly or auto-enabled) // Returns true when: -// - sandbox.agent is explicitly set to a sandbox type (awf, srt, etc.) +// - sandbox.agent is explicitly set to awf // - Firewall is auto-enabled (networkPermissions.Firewall is set and enabled) -// - SRT sandbox is enabled // Returns false when: // - sandbox.agent is false (explicitly disabled) // - No sandbox configuration and no auto-enabled firewall diff --git a/pkg/workflow/copilot_engine.go b/pkg/workflow/copilot_engine.go index 38737a424d..03cf3ae851 100644 --- a/pkg/workflow/copilot_engine.go +++ b/pkg/workflow/copilot_engine.go @@ -7,7 +7,6 @@ // - copilot_engine_tools.go: Tool permissions, arguments, and error patterns // - copilot_logs.go: Log parsing, metrics extraction, and log management // - copilot_mcp.go: MCP server configuration rendering -// - copilot_srt.go: Sandbox Runtime (SRT) integration // - copilot_participant_steps.go: Copilot CLI participant steps // // This modular organization improves maintainability and makes it easier diff --git a/pkg/workflow/frontmatter_extraction_security.go b/pkg/workflow/frontmatter_extraction_security.go index 680859bb41..2f75133c9c 100644 --- a/pkg/workflow/frontmatter_extraction_security.go +++ b/pkg/workflow/frontmatter_extraction_security.go @@ -168,7 +168,7 @@ func (c *Compiler) extractSandboxConfig(frontmatter map[string]any) *SandboxConf return nil } - // Handle legacy string format: "default" or "sandbox-runtime" + // Handle legacy string format: "default" or "awf" (legacy srt/sandbox-runtime are auto-migrated) if sandboxStr, ok := sandbox.(string); ok { frontmatterExtractionSecurityLog.Printf("Sandbox string format: type=%s", sandboxStr) sandboxType := SandboxType(sandboxStr) @@ -239,7 +239,7 @@ func (c *Compiler) extractAgentSandboxConfig(agentVal any) *AgentSandboxConfig { return nil } - // Handle string format: "awf" or "srt" + // Handle string format: "awf" or false (legacy srt values are auto-migrated) if agentStr, ok := agentVal.(string); ok { agentType := SandboxType(agentStr) if isSupportedSandboxType(agentType) { diff --git a/pkg/workflow/sandbox_agent_tools_default_test.go b/pkg/workflow/sandbox_agent_tools_default_test.go index 4dc70ca200..3ca6f32fca 100644 --- a/pkg/workflow/sandbox_agent_tools_default_test.go +++ b/pkg/workflow/sandbox_agent_tools_default_test.go @@ -53,23 +53,21 @@ Test workflow to verify sandbox.agent: awf enables edit and bash tools. assert.Contains(t, lockStr, "bash", "Expected bash tool to be enabled by default with sandbox.agent: awf") }) - t.Run("sandbox.agent: srt enables edit and bash tools by default", func(t *testing.T) { + t.Run("sandbox.agent: awf (migrated from srt) enables edit and bash tools by default", func(t *testing.T) { // Create temp directory for test workflows workflowsDir := t.TempDir() markdown := `--- engine: copilot sandbox: - agent: srt -features: - sandbox-runtime: true + agent: awf on: workflow_dispatch --- -Test workflow to verify sandbox.agent: srt enables edit and bash tools. +Test workflow to verify sandbox.agent: awf enables edit and bash tools. ` - workflowPath := filepath.Join(workflowsDir, "test-agent-srt.md") + workflowPath := filepath.Join(workflowsDir, "test-agent-awf.md") err := os.WriteFile(workflowPath, []byte(markdown), 0644) require.NoError(t, err, "Failed to write workflow file") @@ -81,17 +79,17 @@ Test workflow to verify sandbox.agent: srt enables edit and bash tools. require.NoError(t, err, "Compilation failed") // Read the compiled workflow - lockPath := filepath.Join(workflowsDir, "test-agent-srt.lock.yml") + lockPath := filepath.Join(workflowsDir, "test-agent-awf.lock.yml") lockContent, err := os.ReadFile(lockPath) require.NoError(t, err, "Failed to read compiled workflow") lockStr := string(lockContent) // Verify that edit tool is present - assert.Contains(t, lockStr, "edit", "Expected edit tool to be enabled by default with sandbox.agent: srt") + assert.Contains(t, lockStr, "edit", "Expected edit tool to be enabled by default with sandbox.agent: awf") // Verify that bash tool is present - assert.Contains(t, lockStr, "bash", "Expected bash tool to be enabled by default with sandbox.agent: srt") + assert.Contains(t, lockStr, "bash", "Expected bash tool to be enabled by default with sandbox.agent: awf") }) t.Run("default sandbox (awf) does NOT enable edit and bash tools", func(t *testing.T) { @@ -308,7 +306,7 @@ func TestIsSandboxEnabled(t *testing.T) { expected: true, }, { - name: "agent srt explicitly configured", + name: "agent awf explicitly configured", sandboxConfig: &SandboxConfig{ Agent: &AgentSandboxConfig{ Type: SandboxTypeAWF, @@ -338,7 +336,7 @@ func TestIsSandboxEnabled(t *testing.T) { expected: true, }, { - name: "agent with ID srt", + name: "agent with ID awf (new format)", sandboxConfig: &SandboxConfig{ Agent: &AgentSandboxConfig{ ID: "awf", diff --git a/pkg/workflow/sandbox_custom_agent_test.go b/pkg/workflow/sandbox_custom_agent_test.go index 99b0967631..028969facc 100644 --- a/pkg/workflow/sandbox_custom_agent_test.go +++ b/pkg/workflow/sandbox_custom_agent_test.go @@ -248,9 +248,9 @@ sandbox: } }) - t.Run("custom command and args for SRT", func(t *testing.T) { + t.Run("custom command and args for AWF", func(t *testing.T) { // Create temp directory for test - tmpDir, err := os.MkdirTemp("", "custom-srt-test") + tmpDir, err := os.MkdirTemp("", "custom-awf-test") if err != nil { t.Fatal(err) } @@ -260,21 +260,19 @@ sandbox: on: workflow_dispatch: engine: copilot -features: - sandbox-runtime: true sandbox: agent: id: awf - command: "custom-srt-wrapper" + command: "custom-awf-wrapper" args: - - "--custom-srt-arg" + - "--custom-awf-arg" - "--debug" env: - SRT_CUSTOM_VAR: "test_value" - SRT_DEBUG: "true" + AWF_CUSTOM_VAR: "test_value" + AWF_DEBUG: "true" --- -# Test Custom SRT +# Test Custom AWF ` testFile := filepath.Join(tmpDir, "test-workflow.md") @@ -300,30 +298,30 @@ sandbox: } lockStr := string(lockContent) - // Verify custom SRT command is used - if !strings.Contains(lockStr, "custom-srt-wrapper") { - t.Error("Expected custom SRT command 'custom-srt-wrapper' in compiled workflow") + // Verify custom AWF command is used + if !strings.Contains(lockStr, "custom-awf-wrapper") { + t.Error("Expected custom AWF command 'custom-awf-wrapper' in compiled workflow") } // Verify custom args are included - if !strings.Contains(lockStr, "--custom-srt-arg") { - t.Error("Expected custom arg '--custom-srt-arg' in compiled workflow") + if !strings.Contains(lockStr, "--custom-awf-arg") { + t.Error("Expected custom arg '--custom-awf-arg' in compiled workflow") } if !strings.Contains(lockStr, "--debug") { t.Error("Expected custom arg '--debug' in compiled workflow") } // Verify custom env is included - if !strings.Contains(lockStr, "SRT_CUSTOM_VAR: test_value") { - t.Error("Expected custom env 'SRT_CUSTOM_VAR: test_value' in compiled workflow") + if !strings.Contains(lockStr, "AWF_CUSTOM_VAR: test_value") { + t.Error("Expected custom env 'AWF_CUSTOM_VAR: test_value' in compiled workflow") } - if !strings.Contains(lockStr, "SRT_DEBUG: true") { - t.Error("Expected custom env 'SRT_DEBUG: true' in compiled workflow") + if !strings.Contains(lockStr, "AWF_DEBUG: true") { + t.Error("Expected custom env 'AWF_DEBUG: true' in compiled workflow") } // Verify installation steps were skipped - if strings.Contains(lockStr, "Install Sandbox Runtime") { - t.Error("Expected SRT installation step to be skipped when custom command is specified") + if strings.Contains(lockStr, "Install AWF") { + t.Error("Expected AWF installation step to be skipped when custom command is specified") } }) } diff --git a/pkg/workflow/sandbox_experimental_warning_test.go b/pkg/workflow/sandbox_experimental_warning_test.go deleted file mode 100644 index 7a3fa2d256..0000000000 --- a/pkg/workflow/sandbox_experimental_warning_test.go +++ /dev/null @@ -1,318 +0,0 @@ -//go:build integration - -package workflow - -import ( - "bytes" - "io" - "os" - "path/filepath" - "strings" - "testing" - - "github.com/github/gh-aw/pkg/testutil" -) - -// TestSandboxRuntimeExperimentalWarning tests that the sandbox-runtime feature -// emits an experimental warning when enabled. -func TestSandboxRuntimeExperimentalWarning(t *testing.T) { - tests := []struct { - name string - content string - expectWarning bool - }{ - { - name: "sandbox-runtime enabled produces experimental warning", - content: `--- -on: workflow_dispatch -engine: copilot -sandbox: sandbox-runtime -features: - sandbox-runtime: true -permissions: - contents: read - issues: read - pull-requests: read ---- - -# Test Workflow -`, - expectWarning: true, - }, - { - name: "sandbox default does not produce experimental warning", - content: `--- -on: workflow_dispatch -engine: copilot -sandbox: default -permissions: - contents: read - issues: read - pull-requests: read ---- - -# Test Workflow -`, - expectWarning: false, - }, - { - name: "no sandbox config does not produce experimental warning", - content: `--- -on: workflow_dispatch -engine: copilot -permissions: - contents: read - issues: read - pull-requests: read ---- - -# Test Workflow -`, - expectWarning: false, - }, - { - name: "sandbox-runtime with custom config produces experimental warning", - content: `--- -on: workflow_dispatch -engine: copilot -sandbox: - type: sandbox-runtime - config: - filesystem: - allowWrite: - - "." - - "/tmp" -features: - sandbox-runtime: true -permissions: - contents: read - issues: read - pull-requests: read ---- - -# Test Workflow -`, - expectWarning: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tmpDir := testutil.TempDir(t, "sandbox-experimental-warning-test") - - testFile := filepath.Join(tmpDir, "test-workflow.md") - if err := os.WriteFile(testFile, []byte(tt.content), 0644); err != nil { - t.Fatal(err) - } - - // Capture stderr to check for warnings - oldStderr := os.Stderr - r, w, _ := os.Pipe() - os.Stderr = w - - compiler := NewCompiler() - compiler.SetStrictMode(false) - err := compiler.CompileWorkflow(testFile) - - // Restore stderr - w.Close() - os.Stderr = oldStderr - var buf bytes.Buffer - io.Copy(&buf, r) - stderrOutput := buf.String() - - if err != nil { - t.Errorf("Expected compilation to succeed but it failed: %v", err) - return - } - - expectedMessage := "Using experimental feature: sandbox-runtime firewall" - - if tt.expectWarning { - if !strings.Contains(stderrOutput, expectedMessage) { - t.Errorf("Expected warning containing '%s', got stderr:\n%s", expectedMessage, stderrOutput) - } - } else { - if strings.Contains(stderrOutput, expectedMessage) { - t.Errorf("Did not expect warning '%s', but got stderr:\n%s", expectedMessage, stderrOutput) - } - } - - // Verify warning count includes sandbox-runtime warning - if tt.expectWarning { - warningCount := compiler.GetWarningCount() - if warningCount == 0 { - t.Error("Expected warning count > 0 but got 0") - } - } - }) - } -} - -// TestSandboxRuntimeFeatureFlagRequired tests that sandbox-runtime requires -// the feature flag to be enabled, otherwise compilation fails. -func TestSandboxRuntimeFeatureFlagRequired(t *testing.T) { - tests := []struct { - name string - content string - expectError bool - errorMessage string - }{ - { - name: "sandbox-runtime without feature flag fails", - content: `--- -on: workflow_dispatch -engine: copilot -sandbox: sandbox-runtime -permissions: - contents: read - issues: read - pull-requests: read ---- - -# Test Workflow -`, - expectError: true, - errorMessage: "sandbox-runtime feature is experimental and requires the feature flag to be enabled", - }, - { - name: "sandbox-runtime with feature flag succeeds", - content: `--- -on: workflow_dispatch -engine: copilot -sandbox: sandbox-runtime -features: - sandbox-runtime: true -permissions: - contents: read - issues: read - pull-requests: read ---- - -# Test Workflow -`, - expectError: false, - }, - { - name: "sandbox-runtime with feature flag disabled fails", - content: `--- -on: workflow_dispatch -engine: copilot -sandbox: sandbox-runtime -features: - sandbox-runtime: false -permissions: - contents: read - issues: read - pull-requests: read ---- - -# Test Workflow -`, - expectError: true, - errorMessage: "sandbox-runtime feature is experimental and requires the feature flag to be enabled", - }, - { - name: "sandbox default does not require feature flag", - content: `--- -on: workflow_dispatch -engine: copilot -sandbox: default -permissions: - contents: read - issues: read - pull-requests: read ---- - -# Test Workflow -`, - expectError: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tmpDir := testutil.TempDir(t, "sandbox-feature-flag-test") - - testFile := filepath.Join(tmpDir, "test-workflow.md") - if err := os.WriteFile(testFile, []byte(tt.content), 0644); err != nil { - t.Fatal(err) - } - - // Capture stderr - oldStderr := os.Stderr - r, w, _ := os.Pipe() - os.Stderr = w - - compiler := NewCompiler() - compiler.SetStrictMode(false) - err := compiler.CompileWorkflow(testFile) - - // Restore stderr - w.Close() - os.Stderr = oldStderr - var buf bytes.Buffer - io.Copy(&buf, r) - - if tt.expectError { - if err == nil { - t.Error("Expected compilation to fail but it succeeded") - return - } - if !strings.Contains(err.Error(), tt.errorMessage) { - t.Errorf("Expected error containing '%s', got: %v", tt.errorMessage, err) - } - } else { - if err != nil { - t.Errorf("Expected compilation to succeed but it failed: %v", err) - } - } - }) - } -} - -// TestSandboxRuntimeFeatureFlagViaEnv tests that the sandbox-runtime feature -// can be enabled via the GH_AW_FEATURES environment variable. -func TestSandboxRuntimeFeatureFlagViaEnv(t *testing.T) { - content := `--- -on: workflow_dispatch -engine: copilot -sandbox: sandbox-runtime -permissions: - contents: read - issues: read - pull-requests: read ---- - -# Test Workflow -` - - tmpDir := testutil.TempDir(t, "sandbox-feature-flag-env-test") - - testFile := filepath.Join(tmpDir, "test-workflow.md") - if err := os.WriteFile(testFile, []byte(content), 0644); err != nil { - t.Fatal(err) - } - - // Set the feature flag via environment variable - t.Setenv("GH_AW_FEATURES", "sandbox-runtime") - - // Capture stderr - oldStderr := os.Stderr - r, w, _ := os.Pipe() - os.Stderr = w - - compiler := NewCompiler() - compiler.SetStrictMode(false) - err := compiler.CompileWorkflow(testFile) - - // Restore stderr - w.Close() - os.Stderr = oldStderr - var buf bytes.Buffer - io.Copy(&buf, r) - - if err != nil { - t.Errorf("Expected compilation to succeed with GH_AW_FEATURES=sandbox-runtime but it failed: %v", err) - } -}