Skip to content

Commit 81ede6d

Browse files
Copilotpelikhan
andcommitted
Implement proper Codex sandbox modes and network access configuration
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
1 parent 6b73f9e commit 81ede6d

11 files changed

+1636
-444
lines changed

.github/workflows/final-test-wildcard.lock.yml

Lines changed: 555 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
on: push
3+
engine: codex
4+
network:
5+
allowed: ["*"]
6+
---
7+
# Test

.github/workflows/final-test.lock.yml

Lines changed: 558 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.github/workflows/final-test.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
on: push
3+
engine: codex
4+
network: {}
5+
---
6+
# Test

pkg/workflow/codex_engine.go

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,8 @@ func (e *CodexEngine) GetExecutionSteps(workflowData *WorkflowData, logFile stri
100100
webSearchParam = "--search "
101101
}
102102

103-
// See https://github.com/githubnext/gh-aw/issues/892
104-
fullAutoParam := "--dangerously-bypass-approvals-and-sandbox "
103+
// Build sandbox parameter based on network permissions
104+
sandboxParam := e.getSandboxParam(workflowData.NetworkPermissions)
105105

106106
command := fmt.Sprintf(`set -o pipefail
107107
INSTRUCTION=$(cat /tmp/aw-prompts/prompt.txt)
@@ -120,7 +120,7 @@ codex --version
120120
codex login --api-key "$OPENAI_API_KEY"
121121
122122
# Run codex with log capture - pipefail ensures codex exit code is preserved
123-
codex %s%s--full-auto exec %s"$INSTRUCTION" 2>&1 | tee %s`, modelParam, webSearchParam, fullAutoParam, logFile)
123+
codex %s%s--full-auto exec %s"$INSTRUCTION" 2>&1 | tee %s`, modelParam, webSearchParam, sandboxParam, logFile)
124124

125125
env := map[string]string{
126126
"OPENAI_API_KEY": "${{ secrets.OPENAI_API_KEY }}",
@@ -615,33 +615,59 @@ func (e *CodexEngine) GetDefaultNetworkPermissions() *NetworkPermissions {
615615
}
616616
}
617617

618-
// renderNetworkConfig generates network configuration for codex config.toml
618+
// renderNetworkConfig generates sandbox configuration for codex config.toml
619619
func (e *CodexEngine) renderNetworkConfig(yaml *strings.Builder, networkPermissions *NetworkPermissions) {
620620
yaml.WriteString(" \n")
621-
yaml.WriteString(" [sandbox]\n")
622621

623622
// Default network setting if no permissions specified (equivalent to network: defaults for other engines)
624623
if networkPermissions == nil {
625-
yaml.WriteString(" # Network access enabled (default)\n")
626-
yaml.WriteString(" network_access = true\n")
624+
yaml.WriteString(" # Default sandbox mode with network access enabled\n")
625+
yaml.WriteString(" sandbox_mode = \"danger-full-access\"\n")
627626
return
628627
}
629628

630-
// Handle empty allowed list - means no network access
629+
// Handle empty allowed list - means no network access, workspace-write mode
631630
if len(networkPermissions.Allowed) == 0 {
632-
yaml.WriteString(" # Network access disabled (empty)\n")
631+
yaml.WriteString(" # Workspace-write mode with no network access\n")
632+
yaml.WriteString(" sandbox_mode = \"workspace-write\"\n")
633+
yaml.WriteString(" \n")
634+
yaml.WriteString(" [sandbox_workspace_write]\n")
633635
yaml.WriteString(" network_access = false\n")
634636
return
635637
}
636638

637-
// Handle wildcard - means full network access
639+
// Handle wildcard - means full access, danger-full-access mode
638640
if len(networkPermissions.Allowed) == 1 && networkPermissions.Allowed[0] == "*" {
639-
yaml.WriteString(" # Network access enabled (*)\n")
640-
yaml.WriteString(" network_access = true\n")
641+
yaml.WriteString(" # Full access mode (danger)\n")
642+
yaml.WriteString(" sandbox_mode = \"danger-full-access\"\n")
641643
return
642644
}
643645

644646
// This should not happen due to validation, but handle gracefully
645-
yaml.WriteString(" # Network access disabled (fallback)\n")
647+
yaml.WriteString(" # Fallback to workspace-write mode with no network access\n")
648+
yaml.WriteString(" sandbox_mode = \"workspace-write\"\n")
649+
yaml.WriteString(" \n")
650+
yaml.WriteString(" [sandbox_workspace_write]\n")
646651
yaml.WriteString(" network_access = false\n")
647652
}
653+
654+
// getSandboxParam generates the appropriate sandbox CLI parameter based on network permissions
655+
func (e *CodexEngine) getSandboxParam(networkPermissions *NetworkPermissions) string {
656+
// Default (nil permissions) - use danger-full-access mode
657+
if networkPermissions == nil {
658+
return "--sandbox danger-full-access "
659+
}
660+
661+
// Empty allowed list - means no network access, use workspace-write mode
662+
if len(networkPermissions.Allowed) == 0 {
663+
return "--sandbox workspace-write "
664+
}
665+
666+
// Wildcard - means full access, use danger-full-access mode
667+
if len(networkPermissions.Allowed) == 1 && networkPermissions.Allowed[0] == "*" {
668+
return "--sandbox danger-full-access "
669+
}
670+
671+
// Fallback to workspace-write mode
672+
return "--sandbox workspace-write "
673+
}

pkg/workflow/codex_engine_test.go

Lines changed: 49 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -290,9 +290,8 @@ func TestCodexEngineRenderMCPConfig(t *testing.T) {
290290
"[history]",
291291
"persistence = \"none\"",
292292
"",
293-
"[sandbox]",
294-
"# Network access enabled by default",
295-
"network = true",
293+
"# Default sandbox mode with network access enabled",
294+
"sandbox_mode = \"danger-full-access\"",
296295
"",
297296
"[mcp_servers.github]",
298297
"user_agent = \"test-workflow\"",
@@ -653,15 +652,12 @@ func TestCodexEngineNetworkConfigGeneration(t *testing.T) {
653652
engine.renderNetworkConfig(&yaml, nil)
654653
output := yaml.String()
655654

656-
if !strings.Contains(output, "network = true") {
657-
t.Error("Expected config.toml to contain 'network = true' for nil permissions")
655+
if !strings.Contains(output, "sandbox_mode = \"danger-full-access\"") {
656+
t.Error("Expected config.toml to contain 'sandbox_mode = \"danger-full-access\"' for nil permissions")
658657
}
659-
if !strings.Contains(output, "Network access enabled by default") {
658+
if !strings.Contains(output, "Default sandbox mode with network access enabled") {
660659
t.Error("Expected comment about default access")
661660
}
662-
if !strings.Contains(output, "[sandbox]") {
663-
t.Error("Expected config.toml to contain '[sandbox]' section")
664-
}
665661
})
666662

667663
t.Run("renderNetworkConfig - empty allowed list", func(t *testing.T) {
@@ -672,14 +668,14 @@ func TestCodexEngineNetworkConfigGeneration(t *testing.T) {
672668
engine.renderNetworkConfig(&yaml, permissions)
673669
output := yaml.String()
674670

675-
if !strings.Contains(output, "network = false") {
676-
t.Error("Expected config.toml to contain 'network = false' for empty allowed list")
671+
if !strings.Contains(output, "sandbox_mode = \"workspace-write\"") {
672+
t.Error("Expected config.toml to contain 'sandbox_mode = \"workspace-write\"' for empty allowed list")
677673
}
678-
if !strings.Contains(output, "Network access disabled") {
679-
t.Error("Expected comment about disabled access")
674+
if !strings.Contains(output, "network_access = false") {
675+
t.Error("Expected config.toml to contain 'network_access = false' for empty allowed list")
680676
}
681-
if !strings.Contains(output, "[sandbox]") {
682-
t.Error("Expected config.toml to contain '[sandbox]' section")
677+
if !strings.Contains(output, "Workspace-write mode with no network access") {
678+
t.Error("Expected comment about workspace-write mode")
683679
}
684680
})
685681

@@ -691,14 +687,11 @@ func TestCodexEngineNetworkConfigGeneration(t *testing.T) {
691687
engine.renderNetworkConfig(&yaml, permissions)
692688
output := yaml.String()
693689

694-
if !strings.Contains(output, "network = true") {
695-
t.Error("Expected config.toml to contain 'network = true' for wildcard")
696-
}
697-
if !strings.Contains(output, "Network access enabled") {
698-
t.Error("Expected comment about enabled access")
690+
if !strings.Contains(output, "sandbox_mode = \"danger-full-access\"") {
691+
t.Error("Expected config.toml to contain 'sandbox_mode = \"danger-full-access\"' for wildcard")
699692
}
700-
if !strings.Contains(output, "[sandbox]") {
701-
t.Error("Expected config.toml to contain '[sandbox]' section")
693+
if !strings.Contains(output, "Full access mode (danger)") {
694+
t.Error("Expected comment about full access mode")
702695
}
703696
})
704697
}
@@ -720,3 +713,37 @@ func TestCodexEngineGetDefaultNetworkPermissions(t *testing.T) {
720713
t.Errorf("Expected Codex default to have empty mode, got: %s", defaults.Mode)
721714
}
722715
}
716+
717+
func TestCodexEngineGetSandboxParam(t *testing.T) {
718+
engine := NewCodexEngine()
719+
720+
t.Run("getSandboxParam - nil permissions", func(t *testing.T) {
721+
param := engine.getSandboxParam(nil)
722+
expected := "--sandbox danger-full-access "
723+
if param != expected {
724+
t.Errorf("Expected '%s', got '%s'", expected, param)
725+
}
726+
})
727+
728+
t.Run("getSandboxParam - empty allowed list", func(t *testing.T) {
729+
permissions := &NetworkPermissions{
730+
Allowed: []string{},
731+
}
732+
param := engine.getSandboxParam(permissions)
733+
expected := "--sandbox workspace-write "
734+
if param != expected {
735+
t.Errorf("Expected '%s', got '%s'", expected, param)
736+
}
737+
})
738+
739+
t.Run("getSandboxParam - wildcard allowed", func(t *testing.T) {
740+
permissions := &NetworkPermissions{
741+
Allowed: []string{"*"},
742+
}
743+
param := engine.getSandboxParam(permissions)
744+
expected := "--sandbox danger-full-access "
745+
if param != expected {
746+
t.Errorf("Expected '%s', got '%s'", expected, param)
747+
}
748+
})
749+
}

pkg/workflow/compiler_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2393,8 +2393,8 @@ This is a test workflow with wildcard network permissions and codex engine.
23932393
t.Fatalf("Failed to read lock file: %v", err)
23942394
}
23952395

2396-
if !strings.Contains(string(lockContentEmpty), "network = false") {
2397-
t.Error("Should contain 'network = false' for empty network permissions")
2396+
if !strings.Contains(string(lockContentEmpty), "network_access = false") {
2397+
t.Error("Should contain 'network_access = false' for empty network permissions")
23982398
}
23992399

24002400
// Compile the wildcard network workflow
@@ -2410,8 +2410,8 @@ This is a test workflow with wildcard network permissions and codex engine.
24102410
t.Fatalf("Failed to read lock file: %v", err)
24112411
}
24122412

2413-
if !strings.Contains(string(lockContentWildcard), "network = true") {
2414-
t.Error("Should contain 'network = true' for wildcard network permissions")
2413+
if !strings.Contains(string(lockContentWildcard), "sandbox_mode = \"danger-full-access\"") {
2414+
t.Error("Should contain 'sandbox_mode = \"danger-full-access\"' for wildcard network permissions")
24152415
}
24162416
})
24172417

0 commit comments

Comments
 (0)