From bd57bfc31b5206a6ca52c5cf103e5d8841cc368d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 18 Nov 2025 19:35:45 +0000 Subject: [PATCH 1/2] Initial plan From 158744d5c0d81ad1bc65464904d1b01eb8b75199 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 18 Nov 2025 19:56:50 +0000 Subject: [PATCH 2/2] feat: move sandbox configuration to shared workflow import - Add `sandbox` field support to included_file_schema.json - Create shared/sandbox-runtime.md for sandbox-runtime configuration - Update smoke-srt.md to import sandbox config instead of using builtin field - Add sandbox extraction and merging in import processing - Update parser to extract MergedSandbox from imports - Update compiler to merge sandbox from imported files - Fix firewall test to pass nil sandboxConfig parameter Addresses feedback: sandbox should not be builtin, rather imported as shared workflow Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/shared/sandbox-runtime.md | 3 + .github/workflows/smoke-srt.lock.yml | 6 + .github/workflows/smoke-srt.md | 3 +- pkg/parser/frontmatter.go | 14 ++ pkg/parser/schemas/included_file_schema.json | 128 ++++++++++++++++++ pkg/workflow/compiler.go | 11 ++ .../firewall_default_enablement_test.go | 6 +- 7 files changed, 167 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/shared/sandbox-runtime.md diff --git a/.github/workflows/shared/sandbox-runtime.md b/.github/workflows/shared/sandbox-runtime.md new file mode 100644 index 00000000000..bad7d9e0198 --- /dev/null +++ b/.github/workflows/shared/sandbox-runtime.md @@ -0,0 +1,3 @@ +--- +sandbox: sandbox-runtime +--- diff --git a/.github/workflows/smoke-srt.lock.yml b/.github/workflows/smoke-srt.lock.yml index 01af82d2878..b49caf383aa 100644 --- a/.github/workflows/smoke-srt.lock.yml +++ b/.github/workflows/smoke-srt.lock.yml @@ -5,6 +5,10 @@ # # Smoke test workflow for Sandbox Runtime (SRT) - validates SRT functionality with Copilot # +# Resolved workflow manifest: +# Imports: +# - shared/sandbox-runtime.md +# # Job Dependency Graph: # ```mermaid # graph LR @@ -284,6 +288,8 @@ jobs: mkdir -p "$PROMPT_DIR" # shellcheck disable=SC2006,SC2287 cat > "$GH_AW_PROMPT" << 'PROMPT_EOF' + + You are testing the Sandbox Runtime (SRT) integration. Perform the following tasks: 1. Run `echo "Hello from SRT!"` using bash diff --git a/.github/workflows/smoke-srt.md b/.github/workflows/smoke-srt.md index f5e4976a937..d46385c6b74 100644 --- a/.github/workflows/smoke-srt.md +++ b/.github/workflows/smoke-srt.md @@ -14,7 +14,8 @@ network: allowed: - defaults - github -sandbox: sandbox-runtime +imports: + - shared/sandbox-runtime.md tools: bash: github: diff --git a/pkg/parser/frontmatter.go b/pkg/parser/frontmatter.go index 8640a0a9972..6ed371b1985 100644 --- a/pkg/parser/frontmatter.go +++ b/pkg/parser/frontmatter.go @@ -91,6 +91,7 @@ type ImportsResult struct { MergedNetwork string // Merged network configuration from all imports MergedPermissions string // Merged permissions configuration from all imports MergedSecretMasking string // Merged secret-masking steps from all imports + MergedSandbox string // Merged sandbox configuration from all imports ImportedFiles []string // List of imported file paths (for manifest) AgentFile string // Path to custom agent file (if imported) } @@ -161,6 +162,7 @@ func ProcessImportsFromFrontmatterWithManifest(frontmatter map[string]any, baseD var networkBuilder strings.Builder var permissionsBuilder strings.Builder var secretMaskingBuilder strings.Builder + var sandboxBuilder strings.Builder var engines []string var safeOutputs []string var agentFile string // Track custom agent file @@ -384,6 +386,12 @@ func ProcessImportsFromFrontmatterWithManifest(frontmatter map[string]any, baseD if err == nil && secretMaskingContent != "" && secretMaskingContent != "{}" { secretMaskingBuilder.WriteString(secretMaskingContent + "\n") } + + // Extract sandbox from imported file + sandboxContent, err := extractSandboxFromContent(string(content)) + if err == nil && sandboxContent != "" { + sandboxBuilder.WriteString(sandboxContent + "\n") + } } log.Printf("Completed BFS traversal. Processed %d imports in total", len(processedOrder)) @@ -400,6 +408,7 @@ func ProcessImportsFromFrontmatterWithManifest(frontmatter map[string]any, baseD MergedNetwork: networkBuilder.String(), MergedPermissions: permissionsBuilder.String(), MergedSecretMasking: secretMaskingBuilder.String(), + MergedSandbox: sandboxBuilder.String(), ImportedFiles: processedOrder, AgentFile: agentFile, }, nil @@ -729,6 +738,11 @@ func extractSecretMaskingFromContent(content string) (string, error) { return extractFrontmatterField(content, "secret-masking", "{}") } +// extractSandboxFromContent extracts sandbox section from frontmatter as JSON string +func extractSandboxFromContent(content string) (string, error) { + return extractFrontmatterField(content, "sandbox", "") +} + // extractFrontmatterField extracts a specific field from frontmatter as JSON string func extractFrontmatterField(content, fieldName, emptyValue string) (string, error) { result, err := ExtractFrontmatterFromContent(content) diff --git a/pkg/parser/schemas/included_file_schema.json b/pkg/parser/schemas/included_file_schema.json index 100fef29c9b..6297cf67c19 100644 --- a/pkg/parser/schemas/included_file_schema.json +++ b/pkg/parser/schemas/included_file_schema.json @@ -443,6 +443,134 @@ "additionalProperties": false } ] + }, + "sandbox": { + "description": "Sandbox runtime configuration for AI engines. Controls the execution sandbox (AWF or Sandbox Runtime). Only supported for Copilot engine.", + "examples": [ + "default", + "sandbox-runtime", + { + "type": "sandbox-runtime", + "config": { + "network": { + "allowedDomains": [ + "github.com", + "*.npmjs.org" + ], + "allowUnixSockets": [ + "/var/run/docker.sock" + ] + } + } + } + ], + "oneOf": [ + { + "type": "string", + "enum": [ + "default", + "sandbox-runtime" + ], + "description": "Sandbox type: 'default' uses AWF (Agent Workflow Firewall), 'sandbox-runtime' uses Anthropic Sandbox Runtime with auto-generated config" + }, + { + "type": "object", + "description": "Custom sandbox runtime configuration", + "properties": { + "type": { + "type": "string", + "enum": [ + "default", + "sandbox-runtime" + ], + "description": "Sandbox type to use" + }, + "config": { + "type": "object", + "description": "Custom Sandbox Runtime configuration (only applies when type is 'sandbox-runtime')", + "properties": { + "network": { + "type": "object", + "properties": { + "allowedDomains": { + "type": "array", + "description": "List of allowed domains (supports wildcards like '*.github.com')", + "items": { + "type": "string" + } + }, + "deniedDomains": { + "type": "array", + "description": "List of explicitly denied domains", + "items": { + "type": "string" + } + }, + "allowUnixSockets": { + "type": "array", + "description": "List of allowed Unix socket paths (e.g., ['/var/run/docker.sock'])", + "items": { + "type": "string" + } + }, + "allowLocalBinding": { + "type": "boolean", + "description": "Allow binding to local ports (default: false)" + } + }, + "additionalProperties": false + }, + "filesystem": { + "type": "object", + "properties": { + "denyRead": { + "type": "array", + "description": "List of paths to deny read access", + "items": { + "type": "string" + } + }, + "allowWrite": { + "type": "array", + "description": "List of paths to allow write access", + "items": { + "type": "string" + } + }, + "denyWrite": { + "type": "array", + "description": "List of paths to deny write access", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "ignoreViolations": { + "type": "object", + "description": "Map of command patterns to paths that should ignore violations", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "enableWeakerNestedSandbox": { + "type": "boolean", + "description": "Enable weaker nested sandbox mode (recommended: true for Docker access)" + } + }, + "additionalProperties": false + } + }, + "required": [ + "type" + ], + "additionalProperties": false + } + ] } }, "additionalProperties": false, diff --git a/pkg/workflow/compiler.go b/pkg/workflow/compiler.go index b48d6ecd144..84cffd52198 100644 --- a/pkg/workflow/compiler.go +++ b/pkg/workflow/compiler.go @@ -741,6 +741,17 @@ func (c *Compiler) ParseWorkflowFile(markdownPath string) (*WorkflowData, error) } } + // Merge sandbox configuration from imports with top-level sandbox configuration + if importsResult.MergedSandbox != "" && sandboxConfig == nil { + // Parse the merged sandbox JSON + var sandboxData any + if err := json.Unmarshal([]byte(importsResult.MergedSandbox), &sandboxData); err != nil { + return nil, fmt.Errorf("failed to parse merged sandbox configuration: %w", err) + } + // Extract sandbox config from the parsed data + sandboxConfig = c.extractSandboxConfig(map[string]any{"sandbox": sandboxData}) + } + // Validate permissions from imports against top-level permissions // Extract top-level permissions first topLevelPermissions := c.extractPermissions(result.Frontmatter) diff --git a/pkg/workflow/firewall_default_enablement_test.go b/pkg/workflow/firewall_default_enablement_test.go index 728f728ab38..fae98ee2bac 100644 --- a/pkg/workflow/firewall_default_enablement_test.go +++ b/pkg/workflow/firewall_default_enablement_test.go @@ -99,7 +99,7 @@ func TestCopilotFirewallDefaultIntegration(t *testing.T) { } // Enable firewall by default - enableFirewallByDefaultForCopilot(engineConfig.ID, networkPerms) + enableFirewallByDefaultForCopilot(engineConfig.ID, networkPerms, nil) // Verify firewall is enabled if networkPerms.Firewall == nil { @@ -175,7 +175,7 @@ func TestCopilotFirewallDefaultIntegration(t *testing.T) { } // Enable firewall by default (should not override explicit config) - enableFirewallByDefaultForCopilot(engineConfig.ID, networkPerms) + enableFirewallByDefaultForCopilot(engineConfig.ID, networkPerms, nil) // Verify firewall is still disabled if networkPerms.Firewall.Enabled { @@ -231,7 +231,7 @@ func TestCopilotFirewallDefaultIntegration(t *testing.T) { } // Enable firewall by default (should not affect non-copilot engines) - enableFirewallByDefaultForCopilot(engineConfig.ID, networkPerms) + enableFirewallByDefaultForCopilot(engineConfig.ID, networkPerms, nil) // Verify firewall is NOT enabled for claude if networkPerms.Firewall != nil {