Skip to content
Closed
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
14 changes: 0 additions & 14 deletions pkg/workflow/agentic_engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,13 +124,6 @@ type CapabilityProvider interface {
// SupportsMaxContinuations returns true if this engine supports the max-continuations feature
// When true, max-continuations > 1 enables autopilot/multi-run mode for the engine
SupportsMaxContinuations() bool

// SupportsLLMGateway returns the LLM gateway port number for this engine
// Returns the port number (e.g., 10000) if the engine supports an LLM gateway
// Returns -1 if the engine does not support an LLM gateway
// The port is used to configure AWF api-proxy sidecar container
// In strict mode, engines without LLM gateway support require additional security constraints
SupportsLLMGateway() int
}

// WorkflowExecutor handles workflow compilation and execution
Expand Down Expand Up @@ -226,7 +219,6 @@ type BaseEngine struct {
supportsWebFetch bool
supportsWebSearch bool
supportsPlugins bool
supportsLLMGateway bool
}

func (e *BaseEngine) GetID() string {
Expand Down Expand Up @@ -269,12 +261,6 @@ func (e *BaseEngine) SupportsMaxContinuations() bool {
return e.supportsMaxContinuations
}

func (e *BaseEngine) SupportsLLMGateway() int {
// Engines that support LLM gateway must override this method
// to return their specific port number (e.g., 10000, 10001, 10002)
return -1
}

// GetDeclaredOutputFiles returns an empty list by default (engines can override)
func (e *BaseEngine) GetDeclaredOutputFiles() []string {
return []string{}
Expand Down
15 changes: 4 additions & 11 deletions pkg/workflow/claude_engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,13 @@ func NewClaudeEngine() *ClaudeEngine {
description: "Uses Claude Code with full MCP tool support and allow-listing",
experimental: false,
supportsToolsAllowlist: true,
supportsMaxTurns: true, // Claude supports max-turns feature
supportsWebFetch: true, // Claude has built-in WebFetch support
supportsWebSearch: true, // Claude has built-in WebSearch support
supportsLLMGateway: false, // Claude does not support LLM gateway
supportsMaxTurns: true, // Claude supports max-turns feature
supportsWebFetch: true, // Claude has built-in WebFetch support
supportsWebSearch: true, // Claude has built-in WebSearch support
},
}
}

// SupportsLLMGateway returns the LLM gateway port for Claude engine
func (e *ClaudeEngine) SupportsLLMGateway() int {
return constants.ClaudeLLMGatewayPort
}

// GetModelEnvVarName returns the native environment variable name that the Claude Code CLI uses
// for model selection. Setting ANTHROPIC_MODEL is equivalent to passing --model to the CLI.
func (e *ClaudeEngine) GetModelEnvVarName() string {
Expand Down Expand Up @@ -281,8 +275,7 @@ func (e *ClaudeEngine) GetExecutionSteps(workflowData *WorkflowData, logFile str
allowedDomains := GetClaudeAllowedDomainsWithToolsAndRuntimes(workflowData.NetworkPermissions, workflowData.Tools, workflowData.Runtimes)

// Enable API proxy sidecar if this engine supports LLM gateway
llmGatewayPort := e.SupportsLLMGateway()
usesAPIProxy := llmGatewayPort > 0
usesAPIProxy := true

// Build AWF command with all configuration
// AWF v0.15.0+ uses chroot mode by default, providing transparent access to host binaries
Expand Down
10 changes: 1 addition & 9 deletions pkg/workflow/codex_engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,10 @@ func NewCodexEngine() *CodexEngine {
supportsMaxTurns: false, // Codex does not support max-turns feature
supportsWebFetch: false, // Codex does not have built-in web-fetch support
supportsWebSearch: true, // Codex has built-in web-search support
supportsLLMGateway: true, // Codex supports LLM gateway on port 10001
},
}
}

// SupportsLLMGateway returns the LLM gateway port for Codex engine
func (e *CodexEngine) SupportsLLMGateway() int {
return constants.CodexLLMGatewayPort
}

// GetModelEnvVarName returns an empty string because the Codex CLI does not support
// selecting the model via a native environment variable. Model selection for Codex
// is done via the -c model=... configuration override in the shell command.
Expand Down Expand Up @@ -206,9 +200,7 @@ func (e *CodexEngine) GetExecutionSteps(workflowData *WorkflowData, logFile stri
// Get allowed domains (Codex defaults + network permissions + HTTP MCP server URLs + runtime ecosystem domains)
allowedDomains := GetCodexAllowedDomainsWithToolsAndRuntimes(workflowData.NetworkPermissions, workflowData.Tools, workflowData.Runtimes)

// Enable API proxy sidecar if this engine supports LLM gateway
llmGatewayPort := e.SupportsLLMGateway()
usesAPIProxy := llmGatewayPort > 0
usesAPIProxy := true

// Build the command with agent file handling if specified
// INSTRUCTION reading is done inside the AWF command to avoid Docker Compose interpolation
Expand Down
6 changes: 0 additions & 6 deletions pkg/workflow/copilot_engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ func NewCopilotEngine() *CopilotEngine {
supportsWebFetch: true, // Copilot CLI has built-in web-fetch support
supportsWebSearch: false, // Copilot CLI does not have built-in web-search support
supportsPlugins: true, // Copilot supports plugin installation
supportsLLMGateway: true, // Copilot supports LLM gateway on port 10003
},
}
}
Expand All @@ -61,11 +60,6 @@ func (e *CopilotEngine) GetModelEnvVarName() string {
return constants.CopilotCLIModelEnvVar
}

// SupportsLLMGateway returns the LLM gateway port for Copilot engine
func (e *CopilotEngine) SupportsLLMGateway() int {
return constants.CopilotLLMGatewayPort
}

// GetRequiredSecretNames returns the list of secrets required by the Copilot engine
// This includes COPILOT_GITHUB_TOKEN and optionally MCP_GATEWAY_API_KEY
func (e *CopilotEngine) GetRequiredSecretNames(workflowData *WorkflowData) []string {
Expand Down
4 changes: 1 addition & 3 deletions pkg/workflow/copilot_engine_execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,9 +203,7 @@ func (e *CopilotEngine) GetExecutionSteps(workflowData *WorkflowData, logFile st
allowedDomains := GetCopilotAllowedDomainsWithToolsAndRuntimes(workflowData.NetworkPermissions, workflowData.Tools, workflowData.Runtimes)

// Build AWF command with all configuration
// Enable API proxy sidecar if this engine supports LLM gateway
llmGatewayPort := e.SupportsLLMGateway()
usesAPIProxy := llmGatewayPort > 0
usesAPIProxy := true

// AWF v0.15.0+ uses chroot mode by default, providing transparent access to host binaries
// AWF v0.15.0+ with --env-all handles PATH natively (chroot mode is default):
Expand Down
9 changes: 3 additions & 6 deletions pkg/workflow/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,19 +105,16 @@ func collectDockerImages(tools map[string]any, workflowData *WorkflowData, actio
dockerLog.Printf("Added AWF agent container: %s", agentImage)
}

// Add api-proxy sidecar container for engines that support LLM gateway
// Add api-proxy sidecar container for LLM gateway support
// The api-proxy holds LLM API keys securely and proxies requests through Squid
// Each engine uses its own dedicated port for communication
// Check if the engine supports LLM gateway by querying the engine registry
if workflowData != nil && workflowData.AI != "" {
registry := GetGlobalEngineRegistry()
engine, err := registry.GetEngine(workflowData.AI)
if err == nil && engine.SupportsLLMGateway() > 0 {
if _, err := registry.GetEngine(workflowData.AI); err == nil {
apiProxyImage := constants.DefaultFirewallRegistry + "/api-proxy:" + awfImageTag
if !imageSet[apiProxyImage] {
images = append(images, apiProxyImage)
imageSet[apiProxyImage] = true
dockerLog.Printf("Added AWF api-proxy sidecar container for engine with LLM gateway support: %s", apiProxyImage)
dockerLog.Printf("Added AWF api-proxy sidecar container: %s", apiProxyImage)
}
}
}
Expand Down
10 changes: 1 addition & 9 deletions pkg/workflow/gemini_engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,10 @@ func NewGeminiEngine() *GeminiEngine {
supportsWebFetch: false,
supportsWebSearch: false,
supportsPlugins: false,
supportsLLMGateway: true, // Gemini supports LLM gateway on port 10003
},
}
}

// SupportsLLMGateway returns the LLM gateway port for Gemini engine
func (e *GeminiEngine) SupportsLLMGateway() int {
return constants.GeminiLLMGatewayPort
}

// GetModelEnvVarName returns the native environment variable name that the Gemini CLI uses
// for model selection. Setting GEMINI_MODEL is equivalent to passing --model to the CLI.
func (e *GeminiEngine) GetModelEnvVarName() string {
Expand Down Expand Up @@ -233,9 +227,7 @@ func (e *GeminiEngine) GetExecutionSteps(workflowData *WorkflowData, logFile str
npmPathSetup := GetNpmBinPathSetup()
geminiCommandWithPath := fmt.Sprintf("%s && %s", npmPathSetup, geminiCommand)

// Enable API proxy sidecar if this engine supports LLM gateway
llmGatewayPort := e.SupportsLLMGateway()
usesAPIProxy := llmGatewayPort > 0
usesAPIProxy := true

command = BuildAWFCommand(AWFCommandConfig{
EngineName: "gemini",
Expand Down
1 change: 0 additions & 1 deletion pkg/workflow/gemini_engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ func TestGeminiEngine(t *testing.T) {
assert.False(t, engine.SupportsWebFetch(), "Should not support built-in web fetch")
assert.False(t, engine.SupportsWebSearch(), "Should not support built-in web search")
assert.False(t, engine.SupportsPlugins(), "Should not support plugins")
assert.Equal(t, 10003, engine.SupportsLLMGateway(), "Should support LLM gateway on port 10003")
})

t.Run("required secrets", func(t *testing.T) {
Expand Down
49 changes: 0 additions & 49 deletions pkg/workflow/strict_mode_llm_gateway_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ package workflow
import (
"strings"
"testing"

"github.com/github/gh-aw/pkg/constants"
)

// TestValidateStrictFirewall_LLMGatewaySupport tests the LLM gateway validation in strict mode
Expand Down Expand Up @@ -263,53 +261,6 @@ func TestValidateStrictFirewall_LLMGatewaySupport(t *testing.T) {
})
}

// TestSupportsLLMGateway tests the SupportsLLMGateway method for each engine
func TestSupportsLLMGateway(t *testing.T) {
registry := NewEngineRegistry()

tests := []struct {
engineID string
expectedPort int
description string
}{
{
engineID: "codex",
expectedPort: constants.CodexLLMGatewayPort,
description: "Codex engine uses dedicated port for LLM gateway",
},
{
engineID: "claude",
expectedPort: constants.ClaudeLLMGatewayPort,
description: "Claude engine uses dedicated port for LLM gateway",
},
{
engineID: "copilot",
expectedPort: constants.CopilotLLMGatewayPort,
description: "Copilot engine uses dedicated port for LLM gateway",
},
{
engineID: "gemini",
expectedPort: constants.GeminiLLMGatewayPort,
description: "Gemini engine uses dedicated port for LLM gateway",
},
}

for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
engine, err := registry.GetEngine(tt.engineID)
if err != nil {
t.Fatalf("Failed to get engine '%s': %v", tt.engineID, err)
}

llmGatewayPort := engine.SupportsLLMGateway()
if llmGatewayPort != tt.expectedPort {
t.Errorf("Engine '%s': expected SupportsLLMGateway() = %d, got %d",
tt.engineID, tt.expectedPort, llmGatewayPort)
}
})
}
}

// TestValidateStrictFirewall_EcosystemSuggestions tests ecosystem suggestions in warning messages
func TestValidateStrictFirewall_EcosystemSuggestions(t *testing.T) {
t.Run("warns with ecosystem suggestion when individual domain from ecosystem is used", func(t *testing.T) {
Expand Down
12 changes: 2 additions & 10 deletions pkg/workflow/strict_mode_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -445,27 +445,19 @@ func (c *Compiler) validateStrictFirewall(engineID string, networkPermissions *N
return nil
}

// Get the engine instance to check LLM gateway support
agenticEngine, err := c.engineRegistry.GetEngine(engineID)
// Validate that the engine ID exists in the registry
_, err := c.engineRegistry.GetEngine(engineID)
if err != nil {
strictModeValidationLog.Printf("Failed to get engine: %v", err)
return fmt.Errorf("internal error: failed to get engine '%s': %w", engineID, err)
}

// Check if engine supports LLM gateway
llmGatewayPort := agenticEngine.SupportsLLMGateway()
strictModeValidationLog.Printf("Engine '%s' LLM gateway port: %d", engineID, llmGatewayPort)

// Check if sandbox.agent: false is set (explicitly disabled)
sandboxAgentDisabled := sandboxConfig != nil && sandboxConfig.Agent != nil && sandboxConfig.Agent.Disabled

// In strict mode, sandbox.agent: false is not allowed for any engine as it disables the agent sandbox firewall
if sandboxAgentDisabled {
strictModeValidationLog.Printf("sandbox.agent: false is set, refusing in strict mode")
// For engines without LLM gateway support, provide more specific error message
if llmGatewayPort < 0 {
return fmt.Errorf("strict mode: engine '%s' does not support LLM gateway and requires 'sandbox.agent' to be enabled for security. Remove 'sandbox.agent: false' or set 'strict: false'. See: https://github.github.com/gh-aw/reference/sandbox/", engineID)
}
return errors.New("strict mode: 'sandbox.agent: false' is not allowed because it disables the agent sandbox firewall. This removes important security protections. Remove 'sandbox.agent: false' or set 'strict: false' to disable strict mode. See: https://github.github.com/gh-aw/reference/sandbox/")
}

Expand Down