diff --git a/docs/public/editor/autocomplete-data.json b/docs/public/editor/autocomplete-data.json index e70899cbaba..7ab59c9529f 100644 --- a/docs/public/editor/autocomplete-data.json +++ b/docs/public/editor/autocomplete-data.json @@ -843,6 +843,11 @@ "desc": "Opt into legacy security mode.", "enum": ["enable"], "leaf": true + }, + "allow-host-ports": { + "type": "array", + "desc": "Additional host TCP ports the agent may connect to when legacy-security is enabled.", + "leaf": true } } }, diff --git a/docs/src/content/docs/guides/upgrading.md b/docs/src/content/docs/guides/upgrading.md index 6f28bbbd9b8..6dbea84cc8e 100644 --- a/docs/src/content/docs/guides/upgrading.md +++ b/docs/src/content/docs/guides/upgrading.md @@ -54,6 +54,8 @@ This updates `.github/skills/agentic-workflows/SKILL.md` to the latest template, Run `git diff .github/workflows/` to verify the changes. Typical migrations include `sandbox: false` → `sandbox.agent: false`, `app:` → `github-app:`, `safe-inputs:` → `mcp-scripts:`, `daily at` → `daily around`, and removal of deprecated `network.firewall` and `mcp-scripts.mode` fields. +Workflows that use GitHub Actions `services:` with published ports remain reachable from the agent sandbox only when `sandbox.agent.legacy-security: enable` is set; recompiling regenerates the `--allow-host-service-ports` value used to reach those services. + ## Step 4: Commit and Push Stage and commit your changes: diff --git a/docs/src/content/docs/reference/frontmatter-full.md b/docs/src/content/docs/reference/frontmatter-full.md index 301ff785bed..e913ab1c48c 100644 --- a/docs/src/content/docs/reference/frontmatter-full.md +++ b/docs/src/content/docs/reference/frontmatter-full.md @@ -2227,6 +2227,13 @@ sandbox: # (optional) legacy-security: "enable" + # Additional host TCP ports the agent may connect to when legacy-security is + # enabled. Ports published by `services:` are reached via + # --allow-host-service-ports instead; use this only for host daemons not + # declared there. + # (optional) + allow-host-ports: [] + # Legacy custom Sandbox Runtime configuration (use agent.config instead). Note: # Network configuration is controlled by the top-level 'network' field, not here. # (optional) diff --git a/docs/src/content/docs/reference/sandbox.md b/docs/src/content/docs/reference/sandbox.md index 11f53db488d..6e7ad43b787 100644 --- a/docs/src/content/docs/reference/sandbox.md +++ b/docs/src/content/docs/reference/sandbox.md @@ -118,6 +118,33 @@ All host binaries are available without explicit mounts: system utilities, `gh`, > [!WARNING] > Docker socket is hidden for security. Agents cannot spawn containers. +#### Host Service Ports (`services:`) + +The AWF sandbox reaches GitHub Actions `services:` containers through `--allow-host-service-ports`, which resolves each service's actual (possibly dynamically assigned) host port at runtime. This mechanism, and the explicit `allow-host-ports` escape hatch below, both require `sandbox.agent.legacy-security: enable`: AWF's strict (default) security mode does not provide a route to host services, even when host-access flags are combined. + +```yaml wrap +sandbox: + agent: + legacy-security: enable + +services: + postgres: + image: postgres:18 + ports: + - 5432:5432 +``` + +For host daemons that are not declared in `services:`, add an explicit allowlist (also legacy-security only): + +```yaml wrap +sandbox: + agent: + legacy-security: enable + allow-host-ports: [9000] +``` + +Use `allow-host-ports` only for ports that cannot be represented by `services:`. The compiler rejects values outside the TCP port range `1` through `65535`, and rejects ports AWF always blocks as dangerous (e.g. `22`, `3306`, `5432`, `6379`, `9200`) — reach those through `services:` instead. + #### Environment Variables AWF passes all environment variables via `--env-all`. The host `PATH` is captured as `AWF_HOST_PATH` and restored inside the container, preserving setup action tool paths. diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json index 24aeeadc33c..4bfa6b05ed0 100644 --- a/pkg/parser/schemas/main_workflow_schema.json +++ b/pkg/parser/schemas/main_workflow_schema.json @@ -3600,6 +3600,16 @@ "type": "string", "enum": ["enable"], "description": "Opt into legacy security mode. When set to 'enable', AWF runs with sudo and --enable-host-access for backward compatibility. The default (omitted) uses strict security mode where AWF runs rootless without host-access flags." + }, + "allow-host-ports": { + "type": "array", + "items": { + "type": "integer", + "minimum": 1, + "maximum": 65535 + }, + "uniqueItems": true, + "description": "Additional host TCP ports the agent may connect to when legacy-security is enabled. Ports published by `services:` are reached via --allow-host-service-ports instead; use this only for host daemons not declared there." } }, "additionalProperties": false diff --git a/pkg/workflow/awf_command_builder.go b/pkg/workflow/awf_command_builder.go index 4cee6553adc..37269964993 100644 --- a/pkg/workflow/awf_command_builder.go +++ b/pkg/workflow/awf_command_builder.go @@ -4,10 +4,12 @@ package workflow import ( "fmt" + "os" "sort" "strconv" "strings" + "github.com/github/gh-aw/pkg/console" "github.com/github/gh-aw/pkg/constants" "github.com/github/gh-aw/pkg/workflow/compilerenv" ) @@ -506,7 +508,7 @@ func BuildAWFArgs(config AWFCommandConfig) []string { awfHelpersLog.Print("Added --diagnostic-logs because awf-diagnostic-logs feature flag is enabled") } - // Legacy security mode: emit --legacy-security, --enable-host-access, and --allow-host-ports + // Legacy security mode: emit --legacy-security and --enable-host-access. isLegacy := agentConfig != nil && agentConfig.LegacySecurity if isLegacy { if awfSupportsLegacySecurity(firewallConfig) { @@ -521,18 +523,31 @@ func BuildAWFArgs(config AWFCommandConfig) []string { awfArgs = append(awfArgs, "--enable-host-access") awfHelpersLog.Print("Added --enable-host-access for legacy security mode") - if awfSupportsAllowHostPorts(firewallConfig) { - mcpGatewayPort := int(DefaultMCPGatewayPort) - if config.WorkflowData != nil && config.WorkflowData.SandboxConfig != nil && - config.WorkflowData.SandboxConfig.MCP != nil && config.WorkflowData.SandboxConfig.MCP.Port > 0 { - mcpGatewayPort = config.WorkflowData.SandboxConfig.MCP.Port + // --allow-host-ports requires --enable-host-access, so this is only ever + // emitted in legacy-security mode. AWF's strict security mode (the default) + // does not provide a route to host services even when --allow-host-ports is + // combined with --enable-host-access, so emitting it there would be both + // invalid (strict mode strips --enable-host-access on incompatible runtimes) + // and misleading (it would not make services reachable). + hostPorts := collectAllowedHostPorts(config.WorkflowData, agentConfig) + if len(hostPorts) > 0 { + if awfSupportsAllowHostPorts(firewallConfig) { + hostPortsValue := joinPorts(hostPorts) + awfArgs = append(awfArgs, "--allow-host-ports", hostPortsValue) + awfHelpersLog.Printf("Added --allow-host-ports %s", hostPortsValue) + } else { + warning := fmt.Sprintf("sandbox host ports require AWF %s or newer; skipping --allow-host-ports for AWF version %q", constants.AWFAllowHostPortsMinVersion, getAWFImageTag(firewallConfig)) + fmt.Fprintln(os.Stderr, console.FormatWarningMessage(warning)) + awfHelpersLog.Printf("Warning: %s", warning) } - hostPorts := fmt.Sprintf("80,443,%d", mcpGatewayPort) - awfArgs = append(awfArgs, "--allow-host-ports", hostPorts) - awfHelpersLog.Printf("Added --allow-host-ports %s for legacy security mode", hostPorts) } } else { - awfHelpersLog.Print("Strict security: skipping host-access flags (default)") + awfHelpersLog.Print("Strict security: skipping host-access flag (default)") + if agentConfig != nil && len(agentConfig.AllowHostPorts) > 0 { + warning := "sandbox.agent.allow-host-ports has no effect in strict security mode (the default); set sandbox.agent.legacy-security: enable to reach host ports" + fmt.Fprintln(os.Stderr, console.FormatWarningMessage(warning)) + awfHelpersLog.Printf("Warning: %s", warning) + } } // Skip pulling images since they are pre-downloaded @@ -603,6 +618,59 @@ func BuildAWFArgs(config AWFCommandConfig) []string { return awfArgs } +// collectAllowedHostPorts merges the default host-access ports (80, 443, and the +// MCP gateway port) with any explicit sandbox.agent.allow-host-ports values. +// +// This is only called in legacy-security mode: --allow-host-ports requires +// --enable-host-access, which is legacy-only. GitHub Actions services: ports +// are intentionally NOT derived here — AWF's --allow-host-service-ports flag +// (see ExtractServicePortExpressions) is the correct mechanism for reaching +// services, since it resolves the actual (possibly dynamically assigned) host +// port at runtime via ${{ job.services[''].ports[''] }} expressions +// rather than relying on a static port number. +func collectAllowedHostPorts(workflowData *WorkflowData, agentConfig *AgentSandboxConfig) []int { + ports := map[int]struct{}{ + 80: {}, + 443: {}, + } + ports[getMCPGatewayPort(workflowData)] = struct{}{} + if agentConfig != nil { + for _, port := range agentConfig.AllowHostPorts { + if port < minPort || port > maxPort { + continue + } + // Defense-in-depth: dangerous ports must never reach --allow-host-ports, + // even if validateAllowHostPorts was bypassed or its call order changes. + if _, dangerous := awfDangerousHostPorts[port]; dangerous { + continue + } + ports[port] = struct{}{} + } + } + result := make([]int, 0, len(ports)) + for port := range ports { + result = append(result, port) + } + sort.Ints(result) + return result +} + +func getMCPGatewayPort(workflowData *WorkflowData) int { + if workflowData != nil && workflowData.SandboxConfig != nil && + workflowData.SandboxConfig.MCP != nil && workflowData.SandboxConfig.MCP.Port > 0 { + return workflowData.SandboxConfig.MCP.Port + } + return int(DefaultMCPGatewayPort) +} + +func joinPorts(ports []int) string { + parts := make([]string, len(ports)) + for i, port := range ports { + parts[i] = strconv.Itoa(port) + } + return strings.Join(parts, ",") +} + // GetAWFCommandPrefix determines the AWF command to use (custom or standard). // This extracts the common pattern for determining AWF command from agent config. // diff --git a/pkg/workflow/awf_command_builder_test.go b/pkg/workflow/awf_command_builder_test.go index 76098a935d2..dad9a9007ae 100644 --- a/pkg/workflow/awf_command_builder_test.go +++ b/pkg/workflow/awf_command_builder_test.go @@ -90,7 +90,7 @@ func TestBuildAWFArgsAllowHostPorts(t *testing.T) { argsStr := strings.Join(args, " ") assert.Contains(t, argsStr, "--allow-host-ports", "Should include --allow-host-ports flag") - assert.Contains(t, argsStr, "80,443,8080", "Should allow default gateway port 8080 alongside 80 and 443") + assert.Equal(t, "80,443,8080", argValue(args, "--allow-host-ports"), "Should allow default gateway port 8080 alongside 80 and 443") }) t.Run("uses custom MCP gateway port from sandbox config", func(t *testing.T) { @@ -114,7 +114,7 @@ func TestBuildAWFArgsAllowHostPorts(t *testing.T) { argsStr := strings.Join(args, " ") assert.Contains(t, argsStr, "--allow-host-ports", "Should include --allow-host-ports flag") - assert.Contains(t, argsStr, "80,443,9090", "Should use custom gateway port from sandbox config") + assert.Equal(t, "80,443,9090", argValue(args, "--allow-host-ports"), "Should use custom gateway port from sandbox config") assert.NotContains(t, argsStr, "8080", "Should not include default port when custom port is set") }) @@ -138,7 +138,40 @@ func TestBuildAWFArgsAllowHostPorts(t *testing.T) { assert.NotContains(t, argsStr, "--enable-host-access", "Strict mode (default) should not emit --enable-host-access") }) - t.Run("skips --allow-host-ports when AWF version is too old", func(t *testing.T) { + t.Run("strict mode ignores services and warns when explicit ports are set", func(t *testing.T) { + config := AWFCommandConfig{ + EngineName: "copilot", + WorkflowData: &WorkflowData{ + Name: "test-workflow", + EngineConfig: &EngineConfig{ID: "copilot"}, + NetworkPermissions: &NetworkPermissions{ + Firewall: &FirewallConfig{Enabled: true}, + }, + Services: `services: + postgres: + image: postgres:18 + ports: + - 5432:5432 +`, + SandboxConfig: &SandboxConfig{ + Agent: &AgentSandboxConfig{ID: "awf", AllowHostPorts: []int{9200}}, + }, + }, + AllowedDomains: "github.com", + } + + var args []string + stderr := captureStderr(func() { + args = BuildAWFArgs(config) + }) + argsStr := strings.Join(args, " ") + + assert.NotContains(t, argsStr, "--allow-host-ports", "--allow-host-ports requires --enable-host-access, so strict mode (the default) must not emit it") + assert.NotContains(t, argsStr, "--enable-host-access", "Strict mode should not imply broad host access") + assert.Contains(t, stderr, "sandbox.agent.allow-host-ports", "Should warn that allow-host-ports has no effect in strict mode") + }) + + t.Run("skips --allow-host-ports and warns when AWF version is too old", func(t *testing.T) { config := AWFCommandConfig{ EngineName: "copilot", WorkflowData: &WorkflowData{ @@ -150,14 +183,25 @@ func TestBuildAWFArgsAllowHostPorts(t *testing.T) { Version: "v0.25.23", }, }, + SandboxConfig: &SandboxConfig{ + Agent: &AgentSandboxConfig{ + ID: "awf", + LegacySecurity: true, + AllowHostPorts: []int{9000}, + }, + }, }, AllowedDomains: "github.com", } - args := BuildAWFArgs(config) + var args []string + stderr := captureStderr(func() { + args = BuildAWFArgs(config) + }) argsStr := strings.Join(args, " ") assert.NotContains(t, argsStr, "--allow-host-ports", "Should skip --allow-host-ports for AWF versions below minimum support") + assert.Contains(t, stderr, string(constants.AWFAllowHostPortsMinVersion), "Warning should name the minimum AWF version") }) t.Run("skips host-access flags when network isolation is enabled", func(t *testing.T) { @@ -185,6 +229,39 @@ func TestBuildAWFArgsAllowHostPorts(t *testing.T) { assert.NotContains(t, argsStr, "--enable-host-access", "Should skip --enable-host-access in network isolation mode") assert.NotContains(t, argsStr, "--allow-host-ports", "Should skip --allow-host-ports in network isolation mode") }) + + t.Run("legacy security keeps host access and merges explicit ports, ignoring services", func(t *testing.T) { + config := AWFCommandConfig{ + EngineName: "copilot", + WorkflowData: &WorkflowData{ + Name: "test-workflow", + EngineConfig: &EngineConfig{ID: "copilot"}, + NetworkPermissions: &NetworkPermissions{ + Firewall: &FirewallConfig{Enabled: true}, + }, + Services: `services: + postgres: + image: postgres:18 + ports: + - 5432:5432 +`, + SandboxConfig: &SandboxConfig{ + Agent: &AgentSandboxConfig{ + ID: "awf", + LegacySecurity: true, + AllowHostPorts: []int{9000, 80}, + }, + }, + }, + AllowedDomains: "github.com", + } + + args := BuildAWFArgs(config) + argsStr := strings.Join(args, " ") + + assert.Contains(t, argsStr, "--enable-host-access", "Legacy mode should still emit broad host access") + assert.Equal(t, "80,443,8080,9000", argValue(args, "--allow-host-ports"), "Legacy mode should merge default and explicit ports; services are reached via --allow-host-service-ports, not a static allowlist") + }) } // TestBuildAWFArgsDiagnosticLogs tests that BuildAWFArgs includes --diagnostic-logs @@ -674,3 +751,12 @@ func TestBuildAWFCommand_ServicePortsRequireLegacy(t *testing.T) { assert.NotContains(t, cmd, "--allow-host-service-ports", "Should NOT emit --allow-host-service-ports in strict mode") }) } + +func argValue(args []string, flag string) string { + for i, arg := range args { + if arg == flag && i+1 < len(args) { + return args[i+1] + } + } + return "" +} diff --git a/pkg/workflow/frontmatter_extraction_security.go b/pkg/workflow/frontmatter_extraction_security.go index 6747b154435..d303c84599e 100644 --- a/pkg/workflow/frontmatter_extraction_security.go +++ b/pkg/workflow/frontmatter_extraction_security.go @@ -298,6 +298,27 @@ func (c *Compiler) extractAgentSandboxConfig(agentVal any) *AgentSandboxConfig { } } + // Extract allow-host-ports (additional host TCP ports for the AWF sandbox) + if portsVal, hasPorts := agentObj["allow-host-ports"]; hasPorts { + if portsSlice, ok := portsVal.([]any); ok { + for _, portVal := range portsSlice { + switch v := portVal.(type) { + case int: + agentConfig.AllowHostPorts = append(agentConfig.AllowHostPorts, v) + case int64: + agentConfig.AllowHostPorts = append(agentConfig.AllowHostPorts, int(v)) + case uint64: + agentConfig.AllowHostPorts = append(agentConfig.AllowHostPorts, int(v)) + case float64: + if float64(int(v)) == v { + agentConfig.AllowHostPorts = append(agentConfig.AllowHostPorts, int(v)) + } + } + } + frontmatterExtractionSecurityLog.Printf("Extracted sandbox.agent.allow-host-ports: %v", agentConfig.AllowHostPorts) + } + } + // Extract model-fallback (AWF API proxy model fallback enable/disable flag) if mfVal, hasMF := agentObj["model-fallback"]; hasMF { switch v := mfVal.(type) { diff --git a/pkg/workflow/frontmatter_extraction_security_test.go b/pkg/workflow/frontmatter_extraction_security_test.go index eabe91abc11..59c9338103a 100644 --- a/pkg/workflow/frontmatter_extraction_security_test.go +++ b/pkg/workflow/frontmatter_extraction_security_test.go @@ -139,6 +139,18 @@ func TestExtractAgentSandboxConfigLegacySecurity(t *testing.T) { }) } +func TestExtractAgentSandboxConfigAllowHostPorts(t *testing.T) { + compiler := &Compiler{} + + config := compiler.extractAgentSandboxConfig(map[string]any{ + "id": "awf", + "allow-host-ports": []any{8080, 9090}, + }) + + require.NotNil(t, config, "Should extract agent sandbox config") + assert.Equal(t, []int{8080, 9090}, config.AllowHostPorts) +} + func TestExtractAgentSandboxConfigModelFallback(t *testing.T) { compiler := &Compiler{} diff --git a/pkg/workflow/sandbox.go b/pkg/workflow/sandbox.go index defdc9d1934..81e11ea2111 100644 --- a/pkg/workflow/sandbox.go +++ b/pkg/workflow/sandbox.go @@ -70,6 +70,7 @@ type AgentSandboxConfig struct { NetworkIsolation bool `yaml:"sudo,omitempty"` // Internal: true = isolation mode (AWF --network-isolation). Frontmatter sudo: false (or omitted) maps to NetworkIsolation=true; sudo: true maps to NetworkIsolation=false. SudoExplicitlyEnabled bool `yaml:"-"` // True when sudo: true was explicitly set in frontmatter. Used to emit an error (strict) or warning (non-strict) at compile time. LegacySecurity bool `yaml:"-"` // True when legacy-security: enable was set in frontmatter. Enables sudo, host-access, and iptables-based mode. + AllowHostPorts []int `yaml:"-"` // Additional host TCP ports the agent may connect to. Disabled bool `yaml:"-"` // True when agent is explicitly set to false (disables firewall). This is a runtime flag, not serialized to YAML. DisableReason string `yaml:"-"` // Operator-authored justification from dangerously-disable-sandbox-agent feature; available for diagnostics and audit logging. Config *SandboxRuntimeConfig `yaml:"config,omitempty"` // Custom SRT config (optional) diff --git a/pkg/workflow/sandbox_validation.go b/pkg/workflow/sandbox_validation.go index cc1deba597c..21a4d39882a 100644 --- a/pkg/workflow/sandbox_validation.go +++ b/pkg/workflow/sandbox_validation.go @@ -118,6 +118,12 @@ func validateSandboxConfig(workflowData *WorkflowData) error { } } + if agentConfig != nil && len(agentConfig.AllowHostPorts) > 0 { + if err := validateAllowHostPorts(agentConfig.AllowHostPorts); err != nil { + return err + } + } + // Validate gVisor runtime compatibility if agentConfig != nil && agentConfig.Runtime == AgentRuntimeGVisor { // gVisor is incompatible with ARC/DinD topology: the runner has no access to the @@ -490,6 +496,18 @@ func validateAgentMemoryLimit(memory string) error { return nil } +func validateAllowHostPorts(ports []int) error { + for _, port := range ports { + if port < minPort || port > maxPort { + return fmt.Errorf("invalid allow-host-ports value: %d. Expected a TCP port between 1 and 65535. Example: allow-host-ports: [5432]", port) + } + if service, dangerous := awfDangerousHostPorts[port]; dangerous { + return fmt.Errorf("invalid allow-host-ports value: %d. This port is blocked by AWF as a dangerous port (%s) and cannot be reached via allow-host-ports even in legacy-security mode. To reach a service on this port, declare it under services: with a port mapping and enable sandbox.agent.legacy-security", port, service) + } + } + return nil +} + func getSandboxDisableJustification(workflowData *WorkflowData) (string, error) { if workflowData == nil || workflowData.Features == nil { return "", errors.New("dangerously-disable-sandbox-agent feature is missing") diff --git a/pkg/workflow/sandbox_validation_test.go b/pkg/workflow/sandbox_validation_test.go index 0f01402e60e..b0926f39514 100644 --- a/pkg/workflow/sandbox_validation_test.go +++ b/pkg/workflow/sandbox_validation_test.go @@ -340,3 +340,46 @@ func TestValidateSandboxConfigMemory(t *testing.T) { assert.NoError(t, err, "absent memory should pass validation") }) } + +func TestValidateSandboxConfigAllowHostPorts(t *testing.T) { + t.Run("valid allow-host-ports passes validation", func(t *testing.T) { + workflowData := &WorkflowData{ + Tools: map[string]any{"github": map[string]any{"mode": "remote"}}, + SandboxConfig: &SandboxConfig{ + Agent: &AgentSandboxConfig{AllowHostPorts: []int{8081, 9000}}, + }, + } + + err := validateSandboxConfig(workflowData) + assert.NoError(t, err, "valid allow-host-ports should pass validation") + }) + + t.Run("out-of-range allow-host-ports fails validation", func(t *testing.T) { + workflowData := &WorkflowData{ + Tools: map[string]any{"github": map[string]any{"mode": "remote"}}, + SandboxConfig: &SandboxConfig{ + Agent: &AgentSandboxConfig{AllowHostPorts: []int{0}}, + }, + } + + err := validateSandboxConfig(workflowData) + require.Error(t, err, "out-of-range allow-host-ports should fail validation") + assert.Contains(t, err.Error(), "invalid allow-host-ports value: 0") + assert.Contains(t, err.Error(), "Example: allow-host-ports: [5432]") + }) + + t.Run("dangerous allow-host-ports fails validation", func(t *testing.T) { + workflowData := &WorkflowData{ + Tools: map[string]any{"github": map[string]any{"mode": "remote"}}, + SandboxConfig: &SandboxConfig{ + Agent: &AgentSandboxConfig{AllowHostPorts: []int{5432}}, + }, + } + + err := validateSandboxConfig(workflowData) + require.Error(t, err, "a dangerous port should fail validation") + assert.Contains(t, err.Error(), "invalid allow-host-ports value: 5432") + assert.Contains(t, err.Error(), "PostgreSQL") + assert.Contains(t, err.Error(), "services:") + }) +} diff --git a/pkg/workflow/service_ports.go b/pkg/workflow/service_ports.go index d7120341b6f..3291c911926 100644 --- a/pkg/workflow/service_ports.go +++ b/pkg/workflow/service_ports.go @@ -31,6 +31,42 @@ const ( maxPort = 65535 ) +// awfDangerousHostPorts mirrors AWF's DANGEROUS_PORTS list (gh-aw-firewall +// src/squid/policy-manifest.ts) as of the pinned AWF release +// constants.DefaultFirewallVersion (v0.27.44). These ports are never allowed +// via --allow-host-ports, even with --enable-host-access: AWF blocks them at +// both the iptables and Squid policy layers to prevent the agent sandbox +// from reaching sensitive services directly. --allow-host-service-ports +// intentionally bypasses this list because it restricts traffic to the host +// gateway only (for GitHub Actions services:), but that flag requires +// sandbox.agent.legacy-security: enable. +// +// If the pinned AWF version is bumped and its DANGEROUS_PORTS list changes, +// update this map to match; there is no automated sync with upstream. +var awfDangerousHostPorts = map[int]string{ + 22: "SSH", + 23: "Telnet", + 25: "SMTP", + 110: "POP3", + 143: "IMAP", + 445: "SMB", + 1433: "MS SQL Server", + 1521: "Oracle DB", + 3306: "MySQL", + 3389: "RDP", + 5432: "PostgreSQL", + 5984: "CouchDB", + 6379: "Redis", + 6984: "CouchDB (SSL)", + 8086: "InfluxDB HTTP API", + 8088: "InfluxDB RPC", + 9200: "Elasticsearch HTTP API", + 9300: "Elasticsearch transport", + 27017: "MongoDB", + 27018: "MongoDB sharding", + 28017: "MongoDB web interface", +} + // servicesYAMLWrapper is the top-level YAML wrapper for a services: block. // It provides typed access to the service container map while the YAML is parsed // via goccy/go-yaml with field-level annotations.