Skip to content

Commit b3925c3

Browse files
Copilotlpcoxgh-aw-bot
authored
Align enclave agent frontmatter with primary agent tool configuration (#58620)
* Initial plan * Add enclaves[].agent.tools.github config aligned with primary agent tools.github Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> * Address code review: clarify MCPG version comment, hoist integrity map Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> * Standardize integrity-level lookup as a set (struct{}) for consistency Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> * Fix enclave GitHub AWF compatibility Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> * Review remaining feedback Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
1 parent babbe63 commit b3925c3

11 files changed

Lines changed: 553 additions & 120 deletions

File tree

.changeset/enclave-agent-tools-github.md

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

.github/aw/enclaves.md

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,39 @@ enclaves:
4747
- A fresh masked capability is generated per workflow run and passed only to the MCP gateway and AWF, never to the primary agent environment.
4848
- `timeout:` per enclave entry is capped at 4,740 seconds (AWF reserves the final 60 seconds of its 4,800-second finite-disclosure bucket for cleanup). The gateway itself enforces a 4,860-second tool timeout (4,800s AWF bucket + 60s transport allowance) — treat this as an enforcement bound, not a wall-clock guarantee.
4949

50-
## Agent GitHub Issues profile
50+
## Agent GitHub tool configuration
5151

52-
Use only this closed opt-in:
52+
Prefer this configuration shape for new workflows:
5353

5454
```yaml
5555
sandbox:
5656
mcp:
5757
version: v0.4.15
58+
enclaves:
59+
- agent:
60+
model: gpt-5
61+
tools:
62+
github:
63+
allowed: [list_issues, issue_read]
64+
allowed-repos: [octo-org/private-service]
65+
min-integrity: none
66+
repos:
67+
- repo: octo-org/private-service
68+
sensitivity: confidential
69+
```
70+
71+
- `allowed` is required and currently supports only `list_issues` and `issue_read`.
72+
- `allowed-repos` is optional. When omitted, the enclave identity inherits all repositories declared in the enclave's `repos:` list. When set, each entry must also appear in that list.
73+
- `min-integrity` is optional and defaults to `approved`.
74+
- Unsupported tools and out-of-scope repositories fail closed at compile time.
75+
- GraphQL, search, writes, and all other GitHub tools remain denied.
76+
- Minimum versions are AWF `v0.28.9` and mcpg `v0.4.15`; trusted repositories additionally require AWF `v0.28.14`.
77+
78+
## Deprecated legacy profile
79+
80+
The legacy profile remains supported during migration:
81+
82+
```yaml
5883
enclaves:
5984
- agent:
6085
model: gpt-5
@@ -65,6 +90,7 @@ enclaves:
6590
sensitivity: confidential
6691
```
6792

93+
- `enclaves[].agent.github.cli: issues-read-v1` is deprecated. Migrate to `enclaves[].agent.tools.github`.
6894
- `issues-read-v1` permits only the `list_issues` and `issue_read` GitHub MCP
6995
tools. GraphQL, search, writes, and all other GitHub tools fail closed.
7096
- V1 allows at most one repository whose sensitivity is neither `public` nor `trusted` in the agent entry; `trusted` is public-equivalent for this limit.

docs/src/content/docs/experimental/enclaves.md

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,41 @@ The generated gateway upstream uses a fresh masked capability for each workflow
3131

3232
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.
3333

34-
## GitHub Issues access from agent enclaves
34+
## GitHub tool access from agent enclaves
3535

36-
Agent enclaves can opt into the closed `issues-read-v1` profile:
36+
Prefer `agent.tools.github` for new workflows:
3737

3838
```yaml
3939
sandbox:
4040
agent:
4141
id: awf
4242
mcp:
4343
version: v0.4.15
44+
enclaves:
45+
- agent:
46+
model: gpt-5
47+
tools:
48+
github:
49+
allowed: [list_issues, issue_read]
50+
allowed-repos: [octo-org/private-service]
51+
min-integrity: none
52+
repos:
53+
- repo: octo-org/private-service
54+
sensitivity: confidential
55+
timeout: 180
56+
```
57+
58+
- `allowed` is required and currently accepts only `list_issues` and `issue_read`.
59+
- `allowed-repos` is optional. If omitted, the enclave identity inherits the enclave's `repos` list. If set, every entry must also appear in that list.
60+
- `min-integrity` is optional and defaults to `approved`.
61+
- GraphQL, search, writes, and every other GitHub tool remain denied.
62+
- The minimum supported versions are AWF `v0.28.9` (or `v0.28.14` when using `trusted`) and mcpg `v0.4.15`.
63+
64+
## Deprecated `issues-read-v1` profile
65+
66+
Agent enclaves can still opt into the legacy profile during migration:
67+
68+
```yaml
4469
enclaves:
4570
- agent:
4671
model: gpt-5
@@ -52,8 +77,7 @@ enclaves:
5277
timeout: 180
5378
```
5479

55-
`issues-read-v1` is the only accepted `agent.github.cli` value. Script
56-
enclaves cannot configure `github`. The first profile version accepts at most one repository whose sensitivity is neither `public` nor `trusted`; additional assigned repositories may declare `sensitivity: public` or `trusted`.
80+
`issues-read-v1` is the only accepted `agent.github.cli` value, and it is deprecated in favor of `agent.tools.github`. Script enclaves cannot configure `github`. The first profile version accepts at most one repository whose sensitivity is neither `public` nor `trusted`; additional assigned repositories may declare `sensitivity: public` or `trusted`.
5781

5882
The profile permits only `list_issues` and `issue_read` through the GitHub MCP
5983
server. GraphQL, search, writes, and every other GitHub tool are denied.

pkg/constants/version_constants.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,13 @@ const MCPGIntegrityReactionsMinVersion Version = "v0.2.18"
207207
// concurrent per-agent isolation for the issues-read-v1 enclave capability.
208208
const MCPGEnclaveGitHubIssuesMinVersion Version = "v0.4.15"
209209

210+
// MCPGEnclaveAgentToolsMinVersion is the first MCPG version whose distinct
211+
// enclave identity supports agent.tools.github allowlists and guard policies.
212+
// Currently identical to MCPGEnclaveGitHubIssuesMinVersion because both
213+
// enclave GitHub shapes share the same distinct-identity implementation;
214+
// kept as a separate constant so the two gates can diverge independently.
215+
const MCPGEnclaveAgentToolsMinVersion Version = "v0.4.15"
216+
210217
// DefaultPlaywrightCLIVersion is the default version of the @playwright/cli package.
211218
// Used when tools.playwright is enabled.
212219
// Keep this version outside the default 3-day npm release-age cooldown window enforced by

pkg/parser/schema_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,31 @@ func TestValidateMainWorkflowFrontmatterEnclaves(t *testing.T) {
202202
t.Fatalf("expected keyed top-level enclaves to validate: %v", err)
203203
}
204204

205+
toolsShape := map[string]any{
206+
"on": "workflow_dispatch",
207+
"engine": "copilot",
208+
"enclaves": []any{
209+
map[string]any{
210+
"agent": map[string]any{
211+
"model": "gpt-5",
212+
"tools": map[string]any{
213+
"github": map[string]any{
214+
"allowed": []any{"list_issues", "issue_read"},
215+
"allowed-repos": []any{"octo-org/private-service"},
216+
"min-integrity": "none",
217+
},
218+
},
219+
},
220+
"repos": []any{
221+
map[string]any{"repo": "octo-org/private-service", "sensitivity": "confidential"},
222+
},
223+
},
224+
},
225+
}
226+
if err := ValidateMainWorkflowFrontmatterWithSchemaAndLocation(toolsShape, "workflow.md"); err != nil {
227+
t.Fatalf("expected enclave agent.tools.github shape to validate: %v", err)
228+
}
229+
205230
valid["enclaves"].([]any)[0].(map[string]any)["repos"].([]any)[0].(map[string]any)["sensitivity"] = "trusted"
206231
if err := ValidateMainWorkflowFrontmatterWithSchemaAndLocation(valid, "workflow.md"); err != nil {
207232
t.Fatalf("expected trusted enclave sensitivity to validate: %v", err)
@@ -250,6 +275,23 @@ func TestValidateMainWorkflowFrontmatterEnclaves(t *testing.T) {
250275
t.Fatal("expected generic enclave GitHub CLI mode to be rejected")
251276
}
252277

278+
invalidTool := toolsShape
279+
invalidTool["enclaves"].([]any)[0].(map[string]any)["agent"].(map[string]any)["tools"].(map[string]any)["github"] = map[string]any{
280+
"allowed": []any{"search_issues"},
281+
}
282+
if err := ValidateMainWorkflowFrontmatterWithSchemaAndLocation(invalidTool, "workflow.md"); err == nil {
283+
t.Fatal("expected unsupported enclave GitHub tool to be rejected")
284+
}
285+
286+
invalidRepoScope := toolsShape
287+
invalidRepoScope["enclaves"].([]any)[0].(map[string]any)["agent"].(map[string]any)["tools"].(map[string]any)["github"] = map[string]any{
288+
"allowed": []any{"list_issues"},
289+
"allowed-repos": "all",
290+
}
291+
if err := ValidateMainWorkflowFrontmatterWithSchemaAndLocation(invalidRepoScope, "workflow.md"); err == nil {
292+
t.Fatal("expected scalar enclave GitHub repository scope to be rejected")
293+
}
294+
253295
scriptGitHub := map[string]any{
254296
"on": "workflow_dispatch",
255297
"engine": "copilot",

pkg/parser/schemas/main_workflow_schema.json

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3644,11 +3644,50 @@
36443644
"max-model-tokens": { "type": "integer", "minimum": 1, "maximum": 32768, "default": 1024 },
36453645
"github": {
36463646
"type": "object",
3647+
"description": "Deprecated. Use 'agent.tools.github' instead.",
3648+
"deprecated": true,
3649+
"x-deprecation-message": "'enclaves[].agent.github.cli' is deprecated. Use 'enclaves[].agent.tools.github' instead.",
36473650
"additionalProperties": false,
36483651
"required": ["cli"],
36493652
"properties": {
36503653
"cli": { "type": "string", "enum": ["issues-read-v1"] }
36513654
}
3655+
},
3656+
"tools": {
3657+
"type": "object",
3658+
"additionalProperties": false,
3659+
"properties": {
3660+
"github": {
3661+
"type": "object",
3662+
"additionalProperties": false,
3663+
"properties": {
3664+
"allowed": {
3665+
"type": "array",
3666+
"description": "GitHub MCP tools exposed to the enclave agent. Only explicitly allowed tools are available.",
3667+
"items": {
3668+
"type": "string",
3669+
"enum": ["list_issues", "issue_read"]
3670+
},
3671+
"minItems": 1
3672+
},
3673+
"allowed-repos": {
3674+
"type": "array",
3675+
"description": "Guard policy: explicit owner/repository slugs declared in the enclosing enclave 'repos' list.",
3676+
"items": {
3677+
"type": "string",
3678+
"pattern": "^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$",
3679+
"description": "Repository slug in owner/repository format."
3680+
},
3681+
"minItems": 1
3682+
},
3683+
"min-integrity": {
3684+
"type": "string",
3685+
"description": "Guard policy: minimum required integrity level for enclave GitHub repository access.",
3686+
"enum": ["none", "unapproved", "approved", "merged"]
3687+
}
3688+
}
3689+
}
3690+
}
36523691
}
36533692
}
36543693
},

pkg/workflow/enclave_github_proxy_test.go

Lines changed: 64 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,55 @@ import (
1414
)
1515

1616
func TestEnclaveGitHubMCPAgentPolicy(t *testing.T) {
17-
data := enclaveGitHubIssuesWorkflowData()
18-
data.Enclaves[0].Repos = []*EnclaveRepository{
19-
{Repo: "octo-org/trusted-service", Sensitivity: "trusted"},
20-
{Repo: "octo-org/public-docs", Sensitivity: "public"},
17+
tests := []struct {
18+
name string
19+
data *WorkflowData
20+
wantTools []string
21+
wantRepos []string
22+
wantMinIntegrity string
23+
}{
24+
{
25+
name: "legacy profile defaults",
26+
data: func() *WorkflowData {
27+
data := enclaveGitHubIssuesWorkflowData()
28+
data.Enclaves[0].Repos = []*EnclaveRepository{
29+
{Repo: "octo-org/trusted-service", Sensitivity: "trusted"},
30+
{Repo: "octo-org/public-docs", Sensitivity: "public"},
31+
}
32+
return data
33+
}(),
34+
wantTools: []string{"list_issues", "issue_read"},
35+
wantRepos: []string{"octo-org/trusted-service", "octo-org/public-docs"},
36+
wantMinIntegrity: "approved",
37+
},
38+
{
39+
name: "agent tools config overrides defaults",
40+
data: func() *WorkflowData {
41+
data := enclaveGitHubToolsWorkflowData()
42+
data.Enclaves[0].Repos = []*EnclaveRepository{
43+
{Repo: "octo-org/private-service", Sensitivity: "confidential"},
44+
{Repo: "octo-org/public-docs", Sensitivity: "public"},
45+
}
46+
data.Enclaves[0].Agent.Tools.GitHub.AllowedRepos = GitHubReposScope{"octo-org/public-docs"}
47+
return data
48+
}(),
49+
wantTools: []string{"list_issues", "issue_read"},
50+
wantRepos: []string{"octo-org/public-docs"},
51+
wantMinIntegrity: "none",
52+
},
2153
}
2254

23-
policy := enclaveGitHubMCPAgentPolicy(data)
24-
assert.Equal(t, []string{"github"}, policy.Servers)
25-
assert.Equal(t, map[string][]string{"github": {"list_issues", "issue_read"}}, policy.Tools)
26-
assert.Equal(t, map[string]any{
27-
"repos": []string{"octo-org/trusted-service", "octo-org/public-docs"},
28-
"min-integrity": "approved",
29-
}, policy.AllowOnly)
55+
for _, tt := range tests {
56+
t.Run(tt.name, func(t *testing.T) {
57+
policy := enclaveGitHubMCPAgentPolicy(tt.data)
58+
assert.Equal(t, []string{"github"}, policy.Servers)
59+
assert.Equal(t, map[string][]string{"github": tt.wantTools}, policy.Tools)
60+
assert.Equal(t, map[string]any{
61+
"repos": tt.wantRepos,
62+
"min-integrity": tt.wantMinIntegrity,
63+
}, policy.AllowOnly)
64+
})
65+
}
3066
}
3167

3268
func TestEnclaveGitHubMCPGatewayConfiguration(t *testing.T) {
@@ -130,3 +166,20 @@ func TestEnclaveGitHubMCPVersionGates(t *testing.T) {
130166
require.Error(t, err)
131167
assert.Contains(t, err.Error(), string(constants.MCPGEnclaveGitHubIssuesMinVersion))
132168
}
169+
170+
func TestEnclaveGitHubToolsVersionGates(t *testing.T) {
171+
data := enclaveGitHubToolsWorkflowData()
172+
data.NetworkPermissions.Firewall.Version = string(constants.AWFEnclaveGitHubIssuesMinVersion)
173+
require.NoError(t, validateEnclavesConfig(data))
174+
175+
data.NetworkPermissions.Firewall.Version = "v0.28.8"
176+
err := validateEnclavesConfig(data)
177+
require.Error(t, err)
178+
assert.Contains(t, err.Error(), string(constants.AWFEnclaveGitHubIssuesMinVersion))
179+
180+
data = enclaveGitHubToolsWorkflowData()
181+
data.SandboxConfig.MCP.Version = "v0.4.14"
182+
err = validateEnclavesConfig(data)
183+
require.Error(t, err)
184+
assert.Contains(t, err.Error(), string(constants.MCPGEnclaveAgentToolsMinVersion))
185+
}

0 commit comments

Comments
 (0)