-
Notifications
You must be signed in to change notification settings - Fork 458
Expand file tree
/
Copy pathfirewall.go
More file actions
263 lines (228 loc) · 10.2 KB
/
Copy pathfirewall.go
File metadata and controls
263 lines (228 loc) · 10.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
package workflow
import (
"slices"
"strings"
"github.com/github/gh-aw/pkg/constants"
"github.com/github/gh-aw/pkg/logger"
)
var firewallLog = logger.New("workflow:firewall")
// FirewallConfig represents AWF (gh-aw-firewall) configuration for network egress control.
// These settings are specific to the AWF sandbox and do not apply to Sandbox Runtime (SRT).
type FirewallConfig struct {
Enabled bool `yaml:"enabled,omitempty"` // Enable/disable AWF (default: true for copilot when network restrictions present)
Version string `yaml:"version,omitempty"` // AWF version (empty = latest)
Args []string `yaml:"args,omitempty"` // Additional arguments to pass to AWF
LogLevel string `yaml:"log_level,omitempty"` // AWF log level (default: "info")
SSLBump bool `yaml:"ssl_bump,omitempty"` // AWF-only: Enable SSL Bump for HTTPS content inspection (allows URL path filtering)
AllowURLs []string `yaml:"allow_urls,omitempty"` // AWF-only: URL patterns to allow for HTTPS (requires SSLBump), e.g., "https://github.com/githubnext/*"
}
// isFirewallDisabledBySandboxAgent checks if the firewall is disabled via sandbox.agent: false
func isFirewallDisabledBySandboxAgent(workflowData *WorkflowData) bool {
return workflowData != nil &&
workflowData.SandboxConfig != nil &&
workflowData.SandboxConfig.Agent != nil &&
workflowData.SandboxConfig.Agent.Disabled
}
// isFirewallEnabled checks if AWF firewall is enabled for the workflow
// Firewall is enabled if:
// - network.firewall is explicitly set to true or an object
// - sandbox is enabled (via sandbox.agent or legacy sandbox.type field)
// Firewall is disabled if sandbox.agent is explicitly set to false
func isFirewallEnabled(workflowData *WorkflowData) bool {
// Check if sandbox.agent: false (new way to disable firewall)
if isFirewallDisabledBySandboxAgent(workflowData) {
firewallLog.Print("Firewall disabled via sandbox.agent: false")
return false
}
// Check network.firewall configuration (deprecated)
if workflowData != nil && workflowData.NetworkPermissions != nil && workflowData.NetworkPermissions.Firewall != nil {
enabled := workflowData.NetworkPermissions.Firewall.Enabled
firewallLog.Printf("Firewall enabled check: %v", enabled)
return enabled
}
// Check if sandbox is enabled (via sandbox.agent or legacy sandbox.type field)
// When sandbox is enabled, the firewall is implicitly enabled
if workflowData != nil && isSandboxEnabled(workflowData.SandboxConfig, workflowData.NetworkPermissions) {
firewallLog.Print("Firewall implicitly enabled via sandbox configuration")
return true
}
firewallLog.Print("Firewall not configured, returning false")
return false
}
// getFirewallConfig returns the firewall configuration from network permissions
func getFirewallConfig(workflowData *WorkflowData) *FirewallConfig {
if workflowData == nil {
return nil
}
agentConfig := getAgentConfig(workflowData)
agentVersion := ""
if agentConfig != nil {
agentVersion = agentConfig.Version
}
// Check resolved firewall configuration from network permissions
if workflowData.NetworkPermissions != nil && workflowData.NetworkPermissions.Firewall != nil {
config := workflowData.NetworkPermissions.Firewall
if agentVersion != "" {
if config.Version == agentVersion {
return config
}
overrideConfig := *config
overrideConfig.Version = agentVersion
if config.Version == "" {
firewallLog.Printf("Using sandbox.agent.version %s for firewall version", agentVersion)
} else {
firewallLog.Printf("Overriding firewall version %s with sandbox.agent.version %s", config.Version, agentVersion)
}
return &overrideConfig
}
if firewallLog.Enabled() {
firewallLog.Printf("Retrieved firewall config: enabled=%v, version=%s, logLevel=%s",
config.Enabled, config.Version, config.LogLevel)
}
return config
}
if agentVersion != "" {
firewallLog.Printf("Using sandbox.agent.version override: %s", agentVersion)
return &FirewallConfig{
Enabled: isFirewallEnabled(workflowData),
Version: agentVersion,
}
}
return nil
}
// getAgentConfig returns the agent sandbox configuration from sandbox config
func getAgentConfig(workflowData *WorkflowData) *AgentSandboxConfig {
if workflowData == nil || workflowData.SandboxConfig == nil {
return nil
}
return workflowData.SandboxConfig.Agent
}
func isAWFNetworkIsolationEnabled(workflowData *WorkflowData) bool {
agentConfig := getAgentConfig(workflowData)
if agentConfig == nil || agentConfig.Disabled {
return false
}
return agentConfig.NetworkIsolation
}
// enableFirewallByDefaultForCopilot enables firewall by default for copilot and codex engines
// when network restrictions are present but no explicit firewall configuration exists
// and no SRT sandbox is configured (SRT and AWF are mutually exclusive)
// and sandbox.agent is not explicitly set to false
//
// The firewall is enabled by default for copilot and codex UNLESS:
// - allowed contains "*" (unrestricted network access)
// - sandbox.agent is explicitly set to false
// - SRT sandbox is configured
func enableFirewallByDefaultForCopilot(engineID string, networkPermissions *NetworkPermissions, sandboxConfig *SandboxConfig) {
// Only apply to copilot and codex engines
if engineID != string(constants.CopilotEngine) && engineID != string(constants.CodexEngine) {
return
}
enableFirewallByDefaultForEngine(engineID, networkPermissions, sandboxConfig)
}
// enableFirewallByDefaultForClaude enables firewall by default for Claude engine
// when network restrictions are present but no explicit firewall configuration exists
// and sandbox.agent is not explicitly set to false
//
// The firewall is enabled by default for Claude UNLESS:
// - allowed contains "*" (unrestricted network access)
// - sandbox.agent is explicitly set to false
func enableFirewallByDefaultForClaude(engineID string, networkPermissions *NetworkPermissions, sandboxConfig *SandboxConfig) {
// Only apply to claude engine
if engineID != string(constants.ClaudeEngine) {
return
}
enableFirewallByDefaultForEngine(engineID, networkPermissions, sandboxConfig)
}
// enableFirewallByDefaultForPi enables firewall by default for Pi engine
// when network restrictions are present but no explicit firewall configuration exists
// and sandbox.agent is not explicitly set to false
//
// The firewall is enabled by default for Pi UNLESS:
// - allowed contains "*" (unrestricted network access)
// - sandbox.agent is explicitly set to false
func enableFirewallByDefaultForPi(engineID string, networkPermissions *NetworkPermissions, sandboxConfig *SandboxConfig) {
// Only apply to pi engine
if engineID != string(constants.PiEngine) {
return
}
enableFirewallByDefaultForEngine(engineID, networkPermissions, sandboxConfig)
}
// enableFirewallByDefaultForEngine enables firewall by default for a given engine
// when network restrictions are present but no explicit firewall configuration exists
// and no SRT sandbox is configured (SRT and AWF are mutually exclusive)
// and sandbox.agent is not explicitly set to false
//
// The firewall is enabled by default for the engine UNLESS:
// - allowed contains "*" (unrestricted network access)
// - sandbox.agent is explicitly set to false
// - SRT sandbox is configured (Copilot only)
func enableFirewallByDefaultForEngine(engineID string, networkPermissions *NetworkPermissions, sandboxConfig *SandboxConfig) {
// Check if network permissions exist
if networkPermissions == nil {
return
}
// Check if sandbox.agent: false is set (disables firewall)
// Use a minimal check here since we don't have WorkflowData
if sandboxConfig != nil && sandboxConfig.Agent != nil && sandboxConfig.Agent.Disabled {
firewallLog.Print("sandbox.agent: false is set, skipping AWF auto-enablement")
return
}
// SRT has been removed, all sandboxes should use AWF now
// This section is no longer needed
// Check if firewall is already configured
if networkPermissions.Firewall != nil {
firewallLog.Print("Firewall already configured, skipping default enablement")
return
}
// Check if allowed contains "*" (unrestricted network access)
// If it does, do NOT enable the firewall by default
if slices.Contains(networkPermissions.Allowed, "*") {
firewallLog.Print("Wildcard '*' in allowed domains, skipping AWF auto-enablement")
return
}
// Enable firewall by default for the engine (copilot, claude, codex)
// This applies to all cases EXCEPT when allowed = "*"
networkPermissions.Firewall = &FirewallConfig{
Enabled: true,
}
firewallLog.Printf("Enabled firewall by default for %s engine", engineID)
}
// getAWFImageTag returns the AWF Docker image tag to use for the --image-tag flag.
// This ensures the AWF binary pulls its matching Docker image version instead of latest.
// Returns the version from firewall config if specified, otherwise returns the default version.
// The version is returned without the 'v' prefix (e.g., "0.7.0" instead of "v0.7.0").
func getAWFImageTag(firewallConfig *FirewallConfig) string {
var version string
if firewallConfig != nil && firewallConfig.Version != "" {
version = firewallConfig.Version
firewallLog.Printf("Using custom AWF image tag: %s", version)
} else {
version = string(constants.DefaultFirewallVersion)
firewallLog.Printf("Using default AWF image tag: %s", version)
}
// Strip the 'v' prefix if present (AWF expects version without 'v' prefix)
return strings.TrimPrefix(version, "v")
}
// getSSLBumpArgs returns the AWF arguments for SSL Bump configuration.
// Returns arguments for --ssl-bump and --allow-urls flags if SSL Bump is enabled.
// SSL Bump enables HTTPS content inspection (v0.9.0+), allowing URL path filtering
// instead of domain-only filtering.
//
// Note: These features are specific to AWF (Agent Workflow Firewall) and do not
// apply to Sandbox Runtime (SRT) or other sandbox configurations.
func getSSLBumpArgs(firewallConfig *FirewallConfig) []string {
if firewallConfig == nil || !firewallConfig.SSLBump {
return nil
}
var args []string
args = append(args, "--ssl-bump")
firewallLog.Print("Added --ssl-bump for HTTPS content inspection")
// Add allow-urls if specified (requires SSL Bump)
if len(firewallConfig.AllowURLs) > 0 {
allowURLs := strings.Join(firewallConfig.AllowURLs, ",")
args = append(args, "--allow-urls", allowURLs)
firewallLog.Printf("Added --allow-urls: %s", allowURLs)
}
return args
}