Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
abd3a3d
Add unified enclave MCP compiler support
lpcox Aug 6, 2026
ac4ef20
Merge branch 'main' into lpcox-compile-enclave-mcp
pelikhan Aug 6, 2026
a4cd10d
Merge branch 'main' into lpcox-compile-enclave-mcp
github-actions[bot] Aug 7, 2026
2d573f6
Merge branch 'main' into lpcox-compile-enclave-mcp
github-actions[bot] Aug 7, 2026
1b3bb88
Merge branch 'main' into lpcox-compile-enclave-mcp
github-actions[bot] Aug 7, 2026
d734781
Merge branch 'main' into lpcox-compile-enclave-mcp
github-actions[bot] Aug 7, 2026
5f5e2fd
Merge branch 'main' into lpcox-compile-enclave-mcp
github-actions[bot] Aug 7, 2026
b310c31
Merge branch 'main' into lpcox-compile-enclave-mcp
github-actions[bot] Aug 7, 2026
e736696
Merge branch 'main' into lpcox-compile-enclave-mcp
github-actions[bot] Aug 7, 2026
a89db00
Merge remote-tracking branch 'origin/main' into lpcox-compile-enclave…
Copilot Aug 7, 2026
32865fe
Merge branch 'main' into lpcox-compile-enclave-mcp
github-actions[bot] Aug 7, 2026
9550613
Merge main into lpcox-compile-enclave-mcp, resolve awf_helpers.go con…
Copilot Aug 7, 2026
b55c1f9
Merge main into lpcox-compile-enclave-mcp, resolve awf_env.go conflicts
Copilot Aug 7, 2026
d9d5c26
Merge branch 'main' into lpcox-compile-enclave-mcp
github-actions[bot] Aug 7, 2026
0dbd761
Merge branch 'main' into lpcox-compile-enclave-mcp
github-actions[bot] Aug 7, 2026
c9612d3
Merge branch 'main' into lpcox-compile-enclave-mcp
github-actions[bot] Aug 8, 2026
bf3b439
Merge branch 'main' into lpcox-compile-enclave-mcp
github-actions[bot] Aug 8, 2026
d6f2a26
Merge branch 'main' into lpcox-compile-enclave-mcp
github-actions[bot] Aug 8, 2026
92bf040
Merge branch 'main' into lpcox-compile-enclave-mcp
github-actions[bot] Aug 8, 2026
22f5b03
Merge remote-tracking branch 'origin/main' into lpcox-compile-enclave…
Copilot Aug 8, 2026
721a129
Merge branch 'main' into lpcox-compile-enclave-mcp
github-actions[bot] Aug 8, 2026
de9637a
Merge branch 'main' into lpcox-compile-enclave-mcp
github-actions[bot] Aug 8, 2026
ffc4d03
Merge branch 'main' into lpcox-compile-enclave-mcp
github-actions[bot] Aug 8, 2026
584a081
Merge branch 'main' into lpcox-compile-enclave-mcp
github-actions[bot] Aug 8, 2026
b0d47e9
Merge branch 'main' into lpcox-compile-enclave-mcp
github-actions[bot] Aug 9, 2026
6469c3b
Merge branch 'main' into lpcox-compile-enclave-mcp
github-actions[bot] Aug 9, 2026
e9f4ac4
Merge branch 'main' into lpcox-compile-enclave-mcp
github-actions[bot] Aug 9, 2026
c92332d
Merge branch 'main' into lpcox-compile-enclave-mcp
github-actions[bot] Aug 9, 2026
6500cec
Merge branch 'main' into lpcox-compile-enclave-mcp
github-actions[bot] Aug 10, 2026
a96e7fd
Merge branch 'main' into lpcox-compile-enclave-mcp
github-actions[bot] Aug 10, 2026
cfbdbf3
Merge branch 'main' into lpcox-compile-enclave-mcp
github-actions[bot] Aug 10, 2026
4ea396e
Merge branch 'main' into lpcox-compile-enclave-mcp
github-actions[bot] Aug 10, 2026
db5d5d4
Refine unified enclave configuration
lpcox Aug 10, 2026
1173fd8
Fix lint-errors: add Example: guidance to error messages in enclaves.…
Copilot Aug 11, 2026
03eda80
Merge remote-tracking branch 'origin/main' into lpcox-compile-enclave…
Copilot Aug 11, 2026
f554dca
Refine unified enclave configuration
lpcox Aug 11, 2026
d5700b7
Cover enclave disclosure timing bound
lpcox Aug 11, 2026
4550a33
Fix enclave gateway timeout contract
lpcox Aug 11, 2026
f785cc7
Merge branch 'main' into lpcox-compile-enclave-mcp
github-actions[bot] Aug 11, 2026
3fbda5b
Fix lint-errors: add Example: guidance to error messages in enclaves.…
Copilot Aug 11, 2026
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
12 changes: 12 additions & 0 deletions docs/public/schemas/mcp-gateway-config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,18 @@
},
"default": ["*"]
},
"connectTimeout": {
"type": "integer",
"description": "Per-transport timeout in seconds while connecting to an HTTP MCP upstream.",
"minimum": 1,
"default": 30
},
"toolTimeout": {
"type": "integer",
"description": "Per-server timeout in seconds for a tool invocation.",
"minimum": 1,
"default": 60
},
"env": {
"type": "object",
"description": "Environment variables to pass through for variable resolution. Values may contain variable expressions using '${VARIABLE_NAME}' syntax, which will be resolved from the process environment.",
Expand Down
33 changes: 33 additions & 0 deletions docs/src/content/docs/reference/enclaves.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
title: Private repository enclaves
description: Configure unified AWF script and agent enclaves through the trusted MCP gateway.
---

The top-level `enclaves` array enables finite-disclosure access to approved private repositories. The compiler registers `enclave_run_script` or `enclave_run_agent` from the keyed entries present on the `awf-enclave` MCP route. Omit the array to disable enclaves.

Enclaves require AWF network isolation. Configure `sandbox.agent.sudo: false` (or the `docker-sbx` runtime) so the compiler launches mcpg in bridge mode and AWF can attach it to the isolated topology.

```yaml
sandbox:
agent:
id: awf
sudo: false
enclaves:
- script:
repos:
- repo: octo-org/private-service
sensitivity: confidential
timeout: 45
- agent:
model: gpt-5
repos:
- repo: octo-org/private-service
sensitivity: confidential
timeout: 180
```

Each type can appear at most once. When the same repository appears in both entries, its sensitivity must match because its information budget is shared across executor types. AWF fixes the script enclave network and interpreter and the agent enclave network internally; workflows cannot override those security invariants.

The generated gateway upstream uses a fresh masked capability for each workflow run. That capability is passed only to mcpg and AWF and is excluded from the primary agent environment. The gateway allows 120 seconds for the AWF-owned HTTP upstream to become available. It enforces a 630-second tool timeout, covering AWF's maximum 600-second finite-disclosure timing bucket plus a 30-second transport allowance. Executor timeouts are capped at 540 seconds because AWF reserves 60 seconds in the final bucket for processing and cleanup. The gateway timeout is an enforcement bound, not an absolute AWF wall-clock guarantee under pathological host cleanup or scheduler stalls.

This compiler contract depends on the unified enclave implementation from `github/gh-aw-firewall#6992`. Until that change is available in an AWF release, pinning an older AWF version will not provide the enclave server.
1 change: 0 additions & 1 deletion pkg/cli/codemod_engine_env_secrets_pure_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,6 @@ func TestRemoveUnsafeEngineEnvKeys(t *testing.T) {
}

for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
gotLines, gotModified := removeUnsafeEngineEnvKeys(tt.lines, tt.unsafeKeys)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,6 @@ func TestMigrateMessagesEffectiveTokensSuffixToAICreditsSuffix(t *testing.T) {
}

for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
gotLines, gotModified := migrateMessagesEffectiveTokensSuffixToAICreditsSuffix(tt.lines)
Expand Down
59 changes: 59 additions & 0 deletions pkg/parser/schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,65 @@ import (
"testing"
)

func TestValidateMainWorkflowFrontmatterEnclaves(t *testing.T) {
valid := map[string]any{
"on": "workflow_dispatch",
"engine": "copilot",
"enclaves": []any{
map[string]any{
"script": nil,
"repos": []any{
map[string]any{"repo": "octo-org/private-service", "sensitivity": "confidential"},
},
"timeout": 45,
},
map[string]any{
"agent": map[string]any{"model": "gpt-5"},
"repos": []any{
map[string]any{"repo": "octo-org/private-service", "sensitivity": "confidential"},
},
"timeout": 540,
},
},
}
if err := ValidateMainWorkflowFrontmatterWithSchemaAndLocation(valid, "workflow.md"); err != nil {
t.Fatalf("expected keyed top-level enclaves to validate: %v", err)
}

legacy := map[string]any{
"on": "workflow_dispatch",
"engine": "copilot",
"sandbox": map[string]any{
"enclaves": []any{
map[string]any{
"type": "script",
"repositories": []any{},
},
},
},
}
if err := ValidateMainWorkflowFrontmatterWithSchemaAndLocation(legacy, "workflow.md"); err == nil {
t.Fatal("expected legacy sandbox.enclaves shape to be rejected")
}

tooLong := map[string]any{
"on": "workflow_dispatch",
"engine": "copilot",
"enclaves": []any{
map[string]any{
"agent": map[string]any{"model": "gpt-5"},
"repos": []any{
map[string]any{"repo": "octo-org/private-service", "sensitivity": "confidential"},
},
"timeout": 541,
},
},
}
if err := ValidateMainWorkflowFrontmatterWithSchemaAndLocation(tooLong, "workflow.md"); err == nil {
t.Fatal("expected enclave timeout above 540 seconds to be rejected")
}
}

func TestValidateWithSchema(t *testing.T) {
tests := []struct {
name string
Expand Down
86 changes: 85 additions & 1 deletion pkg/parser/schemas/main_workflow_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -3415,6 +3415,70 @@
}
]
},
"enclaves": {
"type": "array",
"description": "AWF-owned private-repository executors exposed only through the compiler-launched MCP gateway. Omit this field to disable enclaves.",
"minItems": 1,
"maxItems": 2,
"items": {
"oneOf": [
{
"type": "object",
"additionalProperties": false,
"required": ["script", "repos"],
"properties": {
"script": {
"type": ["object", "null"],
"additionalProperties": false,
"properties": {
"max-script-bytes": { "type": "integer", "minimum": 1, "maximum": 65536, "default": 65536 }
}
},
"repos": { "$ref": "#/$defs/enclave-repos" },
"runtime": { "type": "string", "enum": ["docker", "gvisor", "sbx"], "default": "docker" },
"image": { "type": "string", "minLength": 1, "maxLength": 500 },
"timeout": { "type": "integer", "minimum": 1, "maximum": 540, "default": 30 },
"memory-limit": { "type": "string", "pattern": "^[1-9][0-9]*[bkmgBKMG]$", "default": "512m" },
"cpu-limit": { "type": "string", "pattern": "^(?:[0-9]{1,2})(?:\\.[0-9]{1,3})?$", "default": "1" },
"pids-limit": { "type": "integer", "minimum": 1, "maximum": 4096, "default": 128 },
"tmpfs-limit": { "type": "string", "pattern": "^[1-9][0-9]*[bkmgBKMG]$", "default": "64m" },
"max-output-bytes": { "type": "integer", "minimum": 1, "maximum": 8192, "default": 8192 },
"max-invocations": { "type": "integer", "minimum": 1, "maximum": 10000, "default": 32 }
}
},
{
"type": "object",
"additionalProperties": false,
"required": ["agent", "repos"],
"properties": {
"agent": {
"type": "object",
"additionalProperties": false,
"required": ["model"],
"properties": {
"engine": { "type": "string", "enum": ["copilot", "claude", "codex", "gemini"], "default": "copilot" },
"profile": { "type": "string", "enum": ["openai", "anthropic"], "default": "openai" },
"model": { "type": "string", "minLength": 1, "maxLength": 200, "pattern": "^[A-Za-z0-9][A-Za-z0-9._:@/-]{0,199}$" },
"max-task-bytes": { "type": "integer", "minimum": 1, "maximum": 65536, "default": 4096 },
"max-model-requests": { "type": "integer", "minimum": 1, "maximum": 64, "default": 8 },
"max-model-tokens": { "type": "integer", "minimum": 1, "maximum": 32768, "default": 1024 }
}
},
"repos": { "$ref": "#/$defs/enclave-repos" },
"runtime": { "type": "string", "enum": ["docker", "gvisor", "sbx"], "default": "docker" },
"image": { "type": "string", "minLength": 1, "maxLength": 500 },
"timeout": { "type": "integer", "minimum": 1, "maximum": 540, "default": 120 },
"memory-limit": { "type": "string", "pattern": "^[1-9][0-9]*[bkmgBKMG]$", "default": "512m" },
"cpu-limit": { "type": "string", "pattern": "^(?:[0-9]{1,2})(?:\\.[0-9]{1,3})?$", "default": "1" },
"pids-limit": { "type": "integer", "minimum": 1, "maximum": 4096, "default": 128 },
"tmpfs-limit": { "type": "string", "pattern": "^[1-9][0-9]*[bkmgBKMG]$", "default": "64m" },
"max-output-bytes": { "type": "integer", "minimum": 1, "maximum": 8192, "default": 8192 },
"max-invocations": { "type": "integer", "minimum": 1, "maximum": 1000, "default": 8 }
}
}
]
}
},
"sandbox": {
"description": "Sandbox configuration for AI engines. Controls agent sandbox (AWF) and MCP gateway. The MCP gateway is always enabled and cannot be disabled.",
"oneOf": [
Expand All @@ -3425,7 +3489,7 @@
},
{
"type": "object",
"description": "Object format for full sandbox configuration with agent and mcp options",
"description": "Object format for full sandbox configuration with agent and MCP gateway options",
"properties": {
"type": {
"type": "string",
Expand Down Expand Up @@ -12329,6 +12393,26 @@
}
],
"$defs": {
"enclave-repos": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"additionalProperties": false,
"required": ["repo", "sensitivity"],
"properties": {
"repo": {
"type": "string",
"maxLength": 140,
"pattern": "^[A-Za-z0-9][A-Za-z0-9-]{0,38}/[A-Za-z0-9._-]{1,100}$"
},
"sensitivity": {
"type": "string",
"enum": ["public", "internal", "confidential", "sealed"]
}
}
}
},
"github_actions_runs_on": {
"description": "Runner type for workflow execution (GitHub Actions standard field). Supports multiple forms: simple string for single runner label (e.g., 'ubuntu-latest'), array for runner selection with fallbacks, or object for GitHub-hosted runner groups with specific labels. For agentic workflows, runner selection matters when AI workloads require specific compute resources or when using self-hosted runners with specialized capabilities. Typically configured at the job level instead. See https://docs.github.com/en/actions/using-jobs/choosing-the-runner-for-a-job",
"oneOf": [
Expand Down
6 changes: 6 additions & 0 deletions pkg/workflow/awf_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,9 @@ type AWFConfigFile struct {
// cross-repository private data access. Omitted when not configured.
BoundedQueries *AWFBoundedQueriesConfig `json:"boundedQueries,omitempty"`

// Enclaves configures the unified AWF-owned script and agent enclave subsystem.
Enclaves []map[string]any `json:"enclaves,omitempty"`

// Container contains container execution configuration.
Container *AWFContainerConfig `json:"container,omitempty"`

Expand Down Expand Up @@ -471,6 +474,9 @@ func BuildAWFConfigJSON(config AWFCommandConfig) (string, error) {
awfConfig := AWFConfigFile{
Schema: buildAWFConfigSchemaURL(firewallConfig),
}
if config.WorkflowData != nil {
awfConfig.Enclaves = buildAWFEnclavesConfig(config.WorkflowData.Enclaves)
}

// ── Runner section ──────────────────────────────────────────────────────
if topology := getRunnerTopology(config.WorkflowData); topology != "" {
Expand Down
7 changes: 7 additions & 0 deletions pkg/workflow/awf_env.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,13 @@ func ComputeAWFExcludeEnvVarNames(workflowData *WorkflowData, coreSecretVarNames
// The runner-owned gateway forwards them only for HTTP MCP github-oidc authentication.
addUnique("ACTIONS_ID_TOKEN_REQUEST_URL")
addUnique("ACTIONS_ID_TOKEN_REQUEST_TOKEN")
if enclavesEnabled(workflowData) {
addUnique(enclaveMCPCapabilityEnv)
addUnique(enclaveMCPGatewayContainerEnv)
addUnique(enclaveMCPGatewayEndpointEnv)
addUnique(enclaveMCPGatewayIdentityEnv)
addUnique(enclaveMCPReadinessTimeoutEnv)
}

// Explicitly excluded env vars from the frontmatter excluded-env field.
// These are always excluded regardless of their value content.
Expand Down
2 changes: 2 additions & 0 deletions pkg/workflow/codex_mcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ func (e *CodexEngine) RenderMCPConfig(yaml *strings.Builder, tools map[string]an
if hasMCPScripts {
renderer.RenderMCPScriptsMCP(&mcpConfigContent, workflowData.MCPScripts, workflowData)
}
case enclaveMCPServerName:
writeEnclaveMCPTOML(&mcpConfigContent, workflowData)
default:
// Handle custom MCP tools using shared helper (with adapter for isLast parameter)
HandleCustomMCPToolInSwitch(&mcpConfigContent, toolName, expandedTools, false, func(yaml *strings.Builder, toolName string, toolConfig map[string]any, isLast bool) error {
Expand Down
7 changes: 4 additions & 3 deletions pkg/workflow/compiler_validators.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ func (c *Compiler) validateCoreToolConfiguration(workflowData *WorkflowData, mar
{logMessage: "Validating OTLP workload identity configuration", validateFn: func() error { return validateOTLPWorkloadIdentity(workflowData) }},
{logMessage: "Validating default AI credits pricing values", validateFn: func() error { return validateDefaultAiCreditsPricing(workflowData) }},
{logMessage: "Validating tools.github.bounded-queries configuration", validateFn: func() error { return validateBoundedQueriesConfig(workflowData) }},
{logMessage: "Validating enclaves configuration", validateFn: func() error { return validateEnclavesConfig(workflowData) }},
}
// This validation is intentionally outside the table below because strict mode
// turns the same validation result into either an error or a warning.
Expand Down Expand Up @@ -494,13 +495,13 @@ func validateOTLPWorkloadIdentity(workflowData *WorkflowData) error {
return nil
}
if !strings.EqualFold(strings.TrimSpace(workloadIdentity.Provider), "google") {
return errors.New("observability.otlp.workload-identity.provider must be google")
return errors.New("observability.otlp.workload-identity.provider must be google. Example:\n\nobservability:\n otlp:\n workload-identity:\n provider: google\n audience: my-audience")
}
if strings.TrimSpace(workloadIdentity.Audience) == "" {
return errors.New("observability.otlp.workload-identity.audience is required")
return errors.New("observability.otlp.workload-identity.audience is required. Example:\n\nobservability:\n otlp:\n workload-identity:\n provider: google\n audience: my-audience")
}
if getOTLPGitHubAppTokenConfig(workflowData.RawFrontmatter) != nil {
return errors.New("observability.otlp.workload-identity cannot be combined with GitHub App credentials")
return errors.New("observability.otlp.workload-identity cannot be combined with GitHub App credentials; use one authentication method only. Example:\n\nobservability:\n otlp:\n workload-identity:\n provider: google\n audience: my-audience")
}
return nil
}
Loading