Skip to content
Merged
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
13 changes: 5 additions & 8 deletions .github/workflows/agentic-token-audit.lock.yml

Large diffs are not rendered by default.

14 changes: 6 additions & 8 deletions .github/workflows/agentic-token-optimizer.lock.yml

Large diffs are not rendered by default.

14 changes: 6 additions & 8 deletions .github/workflows/ci-doctor.lock.yml

Large diffs are not rendered by default.

13 changes: 5 additions & 8 deletions .github/workflows/daily-team-status.lock.yml

Large diffs are not rendered by default.

13 changes: 5 additions & 8 deletions .github/workflows/dependabot-repair.lock.yml

Large diffs are not rendered by default.

13 changes: 5 additions & 8 deletions .github/workflows/smoke-agent-scoped-approved.lock.yml

Large diffs are not rendered by default.

14 changes: 6 additions & 8 deletions .github/workflows/smoke-antigravity.lock.yml

Large diffs are not rendered by default.

13 changes: 5 additions & 8 deletions .github/workflows/smoke-call-workflow.lock.yml

Large diffs are not rendered by default.

13 changes: 5 additions & 8 deletions .github/workflows/smoke-ci.lock.yml

Large diffs are not rendered by default.

14 changes: 6 additions & 8 deletions .github/workflows/smoke-claude-on-copilot.lock.yml

Large diffs are not rendered by default.

14 changes: 6 additions & 8 deletions .github/workflows/smoke-codex.lock.yml

Large diffs are not rendered by default.

14 changes: 6 additions & 8 deletions .github/workflows/smoke-copilot-aoai-apikey.lock.yml

Large diffs are not rendered by default.

14 changes: 6 additions & 8 deletions .github/workflows/smoke-copilot-aoai-entra.lock.yml

Large diffs are not rendered by default.

13 changes: 5 additions & 8 deletions .github/workflows/smoke-copilot-arm.lock.yml

Large diffs are not rendered by default.

13 changes: 5 additions & 8 deletions .github/workflows/smoke-copilot-sdk.lock.yml

Large diffs are not rendered by default.

14 changes: 6 additions & 8 deletions .github/workflows/smoke-copilot.lock.yml

Large diffs are not rendered by default.

13 changes: 5 additions & 8 deletions .github/workflows/smoke-create-cross-repo-pr.lock.yml

Large diffs are not rendered by default.

13 changes: 5 additions & 8 deletions .github/workflows/smoke-crush.lock.yml

Large diffs are not rendered by default.

14 changes: 6 additions & 8 deletions .github/workflows/smoke-gemini.lock.yml

Large diffs are not rendered by default.

13 changes: 5 additions & 8 deletions .github/workflows/smoke-multi-pr.lock.yml

Large diffs are not rendered by default.

13 changes: 5 additions & 8 deletions .github/workflows/smoke-opencode.lock.yml

Large diffs are not rendered by default.

14 changes: 6 additions & 8 deletions .github/workflows/smoke-otel-backends.lock.yml

Large diffs are not rendered by default.

13 changes: 5 additions & 8 deletions .github/workflows/smoke-project.lock.yml

Large diffs are not rendered by default.

13 changes: 5 additions & 8 deletions .github/workflows/smoke-service-ports.lock.yml

Large diffs are not rendered by default.

13 changes: 5 additions & 8 deletions .github/workflows/smoke-temporary-id.lock.yml

Large diffs are not rendered by default.

13 changes: 5 additions & 8 deletions .github/workflows/smoke-test-tools.lock.yml

Large diffs are not rendered by default.

13 changes: 5 additions & 8 deletions .github/workflows/smoke-update-cross-repo-pr.lock.yml

Large diffs are not rendered by default.

13 changes: 5 additions & 8 deletions .github/workflows/smoke-workflow-call-with-inputs.lock.yml

Large diffs are not rendered by default.

13 changes: 5 additions & 8 deletions .github/workflows/smoke-workflow-call.lock.yml

Large diffs are not rendered by default.

14 changes: 6 additions & 8 deletions .github/workflows/spec-enforcer.lock.yml

Large diffs are not rendered by default.

14 changes: 6 additions & 8 deletions .github/workflows/spec-extractor.lock.yml

Large diffs are not rendered by default.

14 changes: 6 additions & 8 deletions .github/workflows/stale-pr-cleanup.lock.yml

Large diffs are not rendered by default.

15 changes: 7 additions & 8 deletions .github/workflows/stale-repo-identifier.lock.yml

Large diffs are not rendered by default.

14 changes: 6 additions & 8 deletions .github/workflows/sub-issue-closer.lock.yml

Large diffs are not rendered by default.

14 changes: 6 additions & 8 deletions .github/workflows/technical-doc-writer.lock.yml

Large diffs are not rendered by default.

14 changes: 6 additions & 8 deletions .github/workflows/terminal-stylist.lock.yml

Large diffs are not rendered by default.

13 changes: 5 additions & 8 deletions .github/workflows/test-create-pr-error-handling.lock.yml

Large diffs are not rendered by default.

13 changes: 5 additions & 8 deletions .github/workflows/test-dispatcher.lock.yml

Large diffs are not rendered by default.

14 changes: 6 additions & 8 deletions .github/workflows/test-quality-sentinel.lock.yml

Large diffs are not rendered by default.

13 changes: 5 additions & 8 deletions .github/workflows/test-workflow.lock.yml

Large diffs are not rendered by default.

14 changes: 6 additions & 8 deletions .github/workflows/tidy.lock.yml

Large diffs are not rendered by default.

14 changes: 6 additions & 8 deletions .github/workflows/ubuntu-image-analyzer.lock.yml

Large diffs are not rendered by default.

14 changes: 6 additions & 8 deletions .github/workflows/uk-ai-operational-resilience.lock.yml

Large diffs are not rendered by default.

13 changes: 5 additions & 8 deletions .github/workflows/update-astro.lock.yml

Large diffs are not rendered by default.

13 changes: 5 additions & 8 deletions .github/workflows/video-analyzer.lock.yml

Large diffs are not rendered by default.

13 changes: 5 additions & 8 deletions .github/workflows/visual-regression-checker.lock.yml

Large diffs are not rendered by default.

13 changes: 5 additions & 8 deletions .github/workflows/weekly-editors-health-check.lock.yml

Large diffs are not rendered by default.

14 changes: 6 additions & 8 deletions .github/workflows/workflow-generator.lock.yml

Large diffs are not rendered by default.

14 changes: 6 additions & 8 deletions .github/workflows/workflow-health-manager.lock.yml

Large diffs are not rendered by default.

13 changes: 5 additions & 8 deletions .github/workflows/workflow-normalizer.lock.yml

Large diffs are not rendered by default.

13 changes: 5 additions & 8 deletions .github/workflows/workflow-skill-extractor.lock.yml

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# ADR-42354: Default sandbox.agent.sudo to False (Network Isolation)

**Date**: 2026-06-29
**Status**: Draft
**Deciders**: Unknown

---

### Context

The `sandbox.agent` configuration controls how the AWF (Agentic Workflow Firewall) process is launched when running AI agents in GitHub Actions workflows. Previously, omitting the `sudo` field in `sandbox.agent` frontmatter was equivalent to `sudo: true`, causing AWF to be invoked as `sudo -E awf` — granting the agent elevated host-level access by default. This "permissive by default" posture conflicted with the security principle of least privilege. Any workflow that did not explicitly configure `sudo: false` unknowingly ran in a more privileged mode. The goal of this change is to make network isolation (rootless mode) the safe default, requiring explicit opt-in for elevated access.

### Decision

We will change the global default for `sandbox.agent.sudo` from `true` (host-access/sudo mode) to `false` (network isolation/rootless mode). When `sudo` is omitted from the frontmatter, `NetworkIsolation=true` will be set and AWF will run without `sudo`. Explicitly setting `sudo: true` will still work but will emit a compile-time error in strict mode and a warning in non-strict mode, signaling that the field is deprecated and its use should be intentional.

### Alternatives Considered

#### Alternative 1: Keep sudo: true as the Default (Status Quo)

The existing behavior could be retained, requiring operators to explicitly set `sudo: false` to enable network isolation. This was rejected because security-by-default is strongly preferable: most workflows do not require host-level access, and relying on operators to opt into a safer mode leaves a large surface area exposed by inaction or oversight.

#### Alternative 2: Remove the sudo Option Entirely and Always Use Network Isolation

The `sudo` field could be removed from the schema so that all workflows unconditionally run in rootless/network-isolation mode. This was rejected because some legitimate workflows may currently depend on `sudo: true` for reasons not yet eliminated. A hard removal without a deprecation path would be a breaking change with no escape hatch; the warning/error feedback mechanism preserves discoverability while signaling the direction of travel.

### Consequences

#### Positive
- Workflows that omit `sudo` now default to the more secure rootless network-isolation mode, reducing the default attack surface for AI agents.
- Explicit `sudo: true` usage is surfaced at compile time (error in strict mode, warning otherwise), giving operators visibility into elevated-privilege configurations.
- Aligns the sandbox defaults with the security principle of least privilege.

#### Negative
- Existing workflows that omit `sudo` and relied on the old default (`sudo -E awf`) will silently switch to rootless mode, which may break workflows that require host-level access or sudo networking.
- The `SudoExplicitlyEnabled` sentinel field adds complexity to `AgentSandboxConfig`, requiring callers and test code to distinguish between "sudo not set" and "sudo set to false."
- All golden files and tests that previously asserted `sudo -E awf` as the default output must be updated, increasing the scope of a seemingly small default change.

#### Neutral
- The change does not alter the YAML serialization format; `sudo: true` and `sudo: false` remain valid frontmatter values.
- The deprecation path for `sudo: true` (strict error vs. non-strict warning) introduces two distinct enforcement modes whose behavior differences may need to be documented for operators.

---

*ADR created by [adr-writer agent]. Review and finalize before changing status from Draft to Accepted.*
32 changes: 24 additions & 8 deletions pkg/workflow/compiler_permissions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,10 +141,14 @@ This is a test workflow without network permissions.
}

// AWF is enabled by default for all engines (copilot, claude, codex) even without explicit network config
// This ensures sandbox.agent: awf is the default behavior
if !strings.Contains(string(lockContent), "sudo -E awf") {
// This ensures sandbox.agent: awf is the default behavior.
// With the new default of sudo: false (network isolation), AWF runs without sudo.
if !strings.Contains(string(lockContent), "awf --config") {
t.Error("Should contain AWF wrapper by default for Claude engine")
}
if strings.Contains(string(lockContent), "sudo -E awf") {
t.Error("Should NOT use sudo -E awf in network isolation mode (default)")
}
})

t.Run("network: defaults enables AWF by default for Claude", func(t *testing.T) {
Expand Down Expand Up @@ -177,10 +181,14 @@ This is a test workflow with explicit defaults network permissions.
t.Fatalf("Failed to read lock file: %v", err)
}

// AWF is enabled by default for Claude engine with network: defaults
if !strings.Contains(string(lockContent), "sudo -E awf") {
// AWF is enabled by default for Claude engine with network: defaults.
// With the new default of sudo: false (network isolation), AWF runs without sudo.
if !strings.Contains(string(lockContent), "awf --config") {
t.Error("Should contain AWF wrapper for Claude engine with network: defaults")
}
if strings.Contains(string(lockContent), "sudo -E awf") {
t.Error("Should NOT use sudo -E awf in network isolation mode (default)")
}
})

t.Run("network: {} enables AWF by default for Claude", func(t *testing.T) {
Expand Down Expand Up @@ -213,10 +221,14 @@ This is a test workflow with empty network permissions (deny all).
t.Fatalf("Failed to read lock file: %v", err)
}

// AWF is enabled by default for Claude engine with network: {}
if !strings.Contains(string(lockContent), "sudo -E awf") {
// AWF is enabled by default for Claude engine with network: {}.
// With the new default of sudo: false (network isolation), AWF runs without sudo.
if !strings.Contains(string(lockContent), "awf --config") {
t.Error("Should contain AWF wrapper for Claude engine with network: {}")
}
if strings.Contains(string(lockContent), "sudo -E awf") {
t.Error("Should NOT use sudo -E awf in network isolation mode (default)")
}
})

t.Run("network with allowed domains should use AWF", func(t *testing.T) {
Expand Down Expand Up @@ -251,10 +263,14 @@ This is a test workflow with explicit network permissions.
t.Fatalf("Failed to read lock file: %v", err)
}

// Should contain AWF wrapper with domains in config JSON
if !strings.Contains(string(lockContent), "sudo -E awf") {
// Should contain AWF wrapper with domains in config JSON.
// With the new default of sudo: false (network isolation), AWF runs without sudo.
if !strings.Contains(string(lockContent), "awf --config") {
t.Error("Should contain AWF wrapper with explicit network permissions")
}
if strings.Contains(string(lockContent), "sudo -E awf") {
t.Error("Should NOT use sudo -E awf in network isolation mode (default)")
}
if !strings.Contains(string(lockContent), "allowDomains") {
t.Error("Should contain allowDomains in AWF config JSON")
}
Expand Down
9 changes: 7 additions & 2 deletions pkg/workflow/frontmatter_extraction_security.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,11 +207,16 @@ func (c *Compiler) extractAgentSandboxConfig(agentVal any) *AgentSandboxConfig {
// Extract sudo (AWF topology egress mode).
// Semantics are inverted from the frontmatter field:
// sudo: false → no sudo = network isolation mode → NetworkIsolation=true
// sudo: true → sudo enabled = normal mode → NetworkIsolation=false
// (omitted) → sudo enabled = normal mode → NetworkIsolation=false (zero value)
// sudo: true → sudo enabled = normal mode → NetworkIsolation=false (deprecated; error in strict mode, warning otherwise)
// (omitted) → default = network isolation mode → NetworkIsolation=true (same as sudo: false)
agentConfig.NetworkIsolation = true // Default: sudo: false (network isolation enabled)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The object-format branch now correctly defaults NetworkIsolation = true here, but the string-format branch (line ~164, unchanged) still returns &AgentSandboxConfig{Type: agentType} with NetworkIsolation = false.

The default is fixed up later by applySandboxDefaults via the !SudoExplicitlyEnabled guard, so current behavior is correct. However, any future code reading agentConfig.NetworkIsolation from the pre-defaults sandboxConfig (e.g., inside runPostEngineValidations) would see false for string-format agents, which contradicts the stated default.

Consider making the default eager and consistent:

return &AgentSandboxConfig{
    Type:             agentType,
    NetworkIsolation: true, // Default: sudo: false (network isolation)
}

@copilot please address this.

if sudoVal, hasSudo := agentObj["sudo"]; hasSudo {
if sudoBool, ok := sudoVal.(bool); ok {
agentConfig.NetworkIsolation = !sudoBool
if sudoBool {
// sudo: true was explicitly set; record it so validation can warn/error.
agentConfig.SudoExplicitlyEnabled = true
}
}
}

Expand Down
15 changes: 14 additions & 1 deletion pkg/workflow/frontmatter_extraction_security_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,10 @@ func TestExtractAgentSandboxConfigSudo(t *testing.T) {
config := compiler.extractAgentSandboxConfig(agentObj)
require.NotNil(t, config, "Should extract agent sandbox config")
assert.True(t, config.NetworkIsolation, "sudo: false should enable network isolation (NetworkIsolation=true)")
assert.False(t, config.SudoExplicitlyEnabled, "sudo: false should not set SudoExplicitlyEnabled")
})

t.Run("extracts sandbox.agent.sudo: true as normal mode", func(t *testing.T) {
t.Run("extracts sandbox.agent.sudo: true as normal mode with SudoExplicitlyEnabled", func(t *testing.T) {
agentObj := map[string]any{
"id": "awf",
"sudo": true,
Expand All @@ -62,6 +63,18 @@ func TestExtractAgentSandboxConfigSudo(t *testing.T) {
config := compiler.extractAgentSandboxConfig(agentObj)
require.NotNil(t, config, "Should extract agent sandbox config")
assert.False(t, config.NetworkIsolation, "sudo: true should disable network isolation (NetworkIsolation=false)")
assert.True(t, config.SudoExplicitlyEnabled, "sudo: true should set SudoExplicitlyEnabled")
})

t.Run("sudo omitted defaults to network isolation mode", func(t *testing.T) {
agentObj := map[string]any{
"id": "awf",
}

config := compiler.extractAgentSandboxConfig(agentObj)
require.NotNil(t, config, "Should extract agent sandbox config")
assert.True(t, config.NetworkIsolation, "omitting sudo should default to network isolation (NetworkIsolation=true)")
assert.False(t, config.SudoExplicitlyEnabled, "omitting sudo should not set SudoExplicitlyEnabled")
})
}

Expand Down
43 changes: 26 additions & 17 deletions pkg/workflow/sandbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,21 +46,22 @@ type SandboxConfig struct {

// AgentSandboxConfig represents the agent sandbox configuration
type AgentSandboxConfig struct {
ID string `yaml:"id,omitempty"` // Agent ID: "awf" or "srt" (replaces Type in new object format)
Type SandboxType `yaml:"type,omitempty"` // Sandbox type: "awf" or "srt" (legacy, use ID instead)
Version string `yaml:"version,omitempty"` // AWF version override used to install and run the matching firewall version
Platform string `yaml:"platform,omitempty"` // AWF platform.type override (github.com, ghes, ghec, ghec-self-hosted)
NetworkIsolation bool `yaml:"sudo,omitempty"` // Internal: true = isolation mode (AWF --network-isolation). Frontmatter sudo: false maps to NetworkIsolation=true; sudo: true or omitted maps to NetworkIsolation=false.
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)
Command string `yaml:"command,omitempty"` // Custom command to replace AWF or SRT installation
Args []string `yaml:"args,omitempty"` // Additional arguments to append to the command
Env map[string]string `yaml:"env,omitempty"` // Environment variables to set on the step
Mounts []string `yaml:"mounts,omitempty"` // Container mounts to add for AWF (format: "source:dest:mode")
Memory string `yaml:"memory,omitempty"` // Memory limit for the AWF container (e.g., "4g", "8g")
ModelFallback *TemplatableBool `yaml:"model-fallback,omitempty"` // AWF API proxy model fallback enable/disable flag (optional)
Targets map[string]*AgentAPIProxyTargetConfig `yaml:"targets,omitempty"` // Per-provider API proxy target overrides keyed by provider name (e.g. "openai", "anthropic")
ID string `yaml:"id,omitempty"` // Agent ID: "awf" or "srt" (replaces Type in new object format)
Type SandboxType `yaml:"type,omitempty"` // Sandbox type: "awf" or "srt" (legacy, use ID instead)
Version string `yaml:"version,omitempty"` // AWF version override used to install and run the matching firewall version
Platform string `yaml:"platform,omitempty"` // AWF platform.type override (github.com, ghes, ghec, ghec-self-hosted)
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.
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)
Command string `yaml:"command,omitempty"` // Custom command to replace AWF or SRT installation
Args []string `yaml:"args,omitempty"` // Additional arguments to append to the command
Env map[string]string `yaml:"env,omitempty"` // Environment variables to set on the step
Mounts []string `yaml:"mounts,omitempty"` // Container mounts to add for AWF (format: "source:dest:mode")
Memory string `yaml:"memory,omitempty"` // Memory limit for the AWF container (e.g., "4g", "8g")
ModelFallback *TemplatableBool `yaml:"model-fallback,omitempty"` // AWF API proxy model fallback enable/disable flag (optional)
Targets map[string]*AgentAPIProxyTargetConfig `yaml:"targets,omitempty"` // Per-provider API proxy target overrides keyed by provider name (e.g. "openai", "anthropic")
}

// AgentAPIProxyTargetConfig configures a single LLM provider's API proxy target.
Expand Down Expand Up @@ -169,7 +170,8 @@ func applySandboxDefaults(sandboxConfig *SandboxConfig, engineConfig *EngineConf
sandboxLog.Print("No sandbox config found, creating default with agent: awf")
sandboxConfig = &SandboxConfig{
Agent: &AgentSandboxConfig{
Type: SandboxTypeAWF,
Type: SandboxTypeAWF,
NetworkIsolation: true, // Default: sudo: false (network isolation enabled)
},
}
ensureDefaultAgentWritePath(sandboxConfig)
Expand All @@ -188,7 +190,8 @@ func applySandboxDefaults(sandboxConfig *SandboxConfig, engineConfig *EngineConf
if sandboxConfig.Agent == nil {
sandboxLog.Print("Sandbox config exists without agent, setting default agent: awf")
sandboxConfig.Agent = &AgentSandboxConfig{
Type: SandboxTypeAWF,
Type: SandboxTypeAWF,
NetworkIsolation: true, // Default: sudo: false (network isolation enabled)
}
ensureDefaultAgentWritePath(sandboxConfig)
return sandboxConfig
Expand All @@ -205,6 +208,12 @@ func applySandboxDefaults(sandboxConfig *SandboxConfig, engineConfig *EngineConf
sandboxConfig.Agent.Type = SandboxTypeAWF
}

// Apply the default sudo: false (network isolation) when sudo was not explicitly
// set to true in frontmatter. This ensures network isolation is the default.
if !sandboxConfig.Agent.SudoExplicitlyEnabled {
sandboxConfig.Agent.NetworkIsolation = true

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The legacy sandbox.type code path above silently keeps sudo mode, inconsistent with the new default.

When sandboxConfig.Type != "" (line 183), the function returns early before reaching this block, so any workflow still using the old top-level sandbox.type: awf format never gets NetworkIsolation = true applied. The security policy change does not cover that format — there is no warning, no migration, and isAWFNetworkIsolationEnabled returns false for those configs because Agent remains nil.

💡 Suggested fix

Either apply the new default inside the legacy early-return path, or emit a deprecation warning so operators know their config is outside the new policy:

if sandboxConfig.Type != "" {
    sandboxLog.Printf("Sandbox config uses legacy Type field: %s, preserving it", sandboxConfig.Type)
    // Apply network isolation default to Agent if present.
    if sandboxConfig.Agent != nil && !sandboxConfig.Agent.SudoExplicitlyEnabled {
        sandboxConfig.Agent.NetworkIsolation = true
    }
    ensureDefaultAgentWritePath(sandboxConfig)
    return sandboxConfig
}

If the legacy path is intentionally excluded, document that explicitly in the function comment so reviewers and operators can audit their workflow inventory.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The !SudoExplicitlyEnabled → NetworkIsolation = true guard correctly applies the new default for configs that reach this point.

However, the early-return path at line 183–186 (when sandboxConfig.Type != "") bypasses this block:

if sandboxConfig.Type != "" {
    ensureDefaultAgentWritePath(sandboxConfig)
    return sandboxConfig  // ← bypasses the NetworkIsolation default below
}

For frontmatter-sourced configs this is safe because extractAgentSandboxConfig already sets NetworkIsolation = true as the default (line 212 of frontmatter_extraction_security.go). But programmatically-created configs with sandboxConfig.Type != "" and sandboxConfig.Agent != nil will silently skip the new default.

A defensive fix or a comment explaining the invariant would prevent future regressions:

if sandboxConfig.Type != "" {
    // NetworkIsolation is already set by extractAgentSandboxConfig; skip re-defaulting.
    ensureDefaultAgentWritePath(sandboxConfig)\n    return sandboxConfig\n}\n```\n\n@copilot please address this.

}

ensureDefaultAgentWritePath(sandboxConfig)
return sandboxConfig
}
Expand Down
9 changes: 6 additions & 3 deletions pkg/workflow/sandbox_custom_agent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,9 +265,12 @@ sandbox:
}
lockStr := string(lockContent)

// Verify standard AWF command is used
if !strings.Contains(lockStr, "sudo -E awf") {
t.Error("Expected standard AWF command 'sudo -E awf' with legacy type field")
// Verify standard AWF command is used (rootless mode - no sudo -E awf)
if strings.Contains(lockStr, "sudo -E awf") {
t.Error("Expected no sudo -E awf with legacy type field (network isolation is the default)")
}
if !strings.Contains(lockStr, "awf --config") {
t.Error("Expected rootless AWF invocation with legacy type field")
}

// Verify installation step is present
Expand Down
Loading
Loading