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
23 changes: 17 additions & 6 deletions pkg/workflow/compiler_difc_proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,14 @@ func (c *Compiler) generateStartCliProxyStep(yaml *strings.Builder, data *Workfl
}
}

// defaultCliProxyPolicyJSON is the fallback guard policy for the CLI proxy when no
// guard policy is explicitly configured in the workflow frontmatter.
// The DIFC proxy requires a --policy flag to forward requests; without it, all API
// calls return HTTP 503 with body "proxy enforcement not configured".
// This default allows all repos with no integrity filtering — the most permissive
// policy that still satisfies the proxy's requirement.
const defaultCliProxyPolicyJSON = `{"allow-only":{"repos":"all","min-integrity":"none"}}`
Comment on lines +360 to +366
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

defaultCliProxyPolicyJSON is defined with backslash-escaped quotes inside a raw string literal, producing {\"allow-only\":...} at runtime. That string is not valid JSON and will be passed verbatim to start_cli_proxy.sh--policy, likely causing the proxy to reject the policy. Define the constant as actual JSON (no backslashes) or build it via json.Marshal to match the format returned by getDIFCProxyPolicyJSON and the PR description example.

Copilot uses AI. Check for mistakes.

// buildStartCliProxyStepYAML returns the YAML for the "Start CLI proxy" step,
// or an empty string if the proxy cannot be configured.
func (c *Compiler) buildStartCliProxyStepYAML(data *WorkflowData) string {
Expand All @@ -368,10 +376,15 @@ func (c *Compiler) buildStartCliProxyStepYAML(data *WorkflowData) string {
customGitHubToken := getGitHubToken(githubTool)
effectiveToken := getEffectiveGitHubToken(customGitHubToken)

// Build the guard policy JSON (static fields only)
// Build the guard policy JSON (static fields only).
// The CLI proxy requires a policy to forward requests — without one, all API
// calls return HTTP 503 ("proxy enforcement not configured"). Use the default
// permissive policy when no guard policy is configured in the frontmatter.
policyJSON := getDIFCProxyPolicyJSON(githubTool)
// An empty policy is acceptable — the proxy still provides gh CLI routing
// without guard filtering when no min-integrity is configured.
if policyJSON == "" {
policyJSON = defaultCliProxyPolicyJSON
difcProxyLog.Print("No guard policy configured, using default CLI proxy policy")
}

// Resolve the container image from the MCP gateway configuration
ensureDefaultMCPGatewayConfig(data)
Expand All @@ -389,9 +402,7 @@ func (c *Compiler) buildStartCliProxyStepYAML(data *WorkflowData) string {
sb.WriteString(" env:\n")
fmt.Fprintf(&sb, " GH_TOKEN: %s\n", effectiveToken)
sb.WriteString(" GITHUB_SERVER_URL: ${{ github.server_url }}\n")
if policyJSON != "" {
fmt.Fprintf(&sb, " CLI_PROXY_POLICY: '%s'\n", policyJSON)
}
fmt.Fprintf(&sb, " CLI_PROXY_POLICY: '%s'\n", policyJSON)
fmt.Fprintf(&sb, " CLI_PROXY_IMAGE: '%s'\n", containerImage)
sb.WriteString(" run: |\n")
sb.WriteString(" bash \"${RUNNER_TEMP}/gh-aw/actions/start_cli_proxy.sh\"\n")
Expand Down
65 changes: 65 additions & 0 deletions pkg/workflow/compiler_difc_proxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -693,3 +693,68 @@ func TestGenerateSetGHRepoAfterDIFCProxyStep(t *testing.T) {
assert.NotContains(t, result, "GH_HOST", "should not touch GH_HOST")
})
}

// TestBuildStartCliProxyStepYAML verifies that the CLI proxy step always emits
// CLI_PROXY_POLICY, using the default permissive policy when no guard policy is
// configured in the frontmatter.
func TestBuildStartCliProxyStepYAML(t *testing.T) {
c := &Compiler{}

t.Run("emits default policy when no guard policy is configured", func(t *testing.T) {
data := &WorkflowData{
Tools: map[string]any{
"github": map[string]any{"toolsets": []string{"default"}},
},
}

result := c.buildStartCliProxyStepYAML(data)
require.NotEmpty(t, result, "should emit CLI proxy step even without guard policy")
assert.Contains(t, result, "CLI_PROXY_POLICY", "should always emit CLI_PROXY_POLICY")
assert.Contains(t, result, `"allow-only"`, "default policy should contain allow-only")
assert.Contains(t, result, `"repos":"all"`, "default policy should allow all repos")
assert.Contains(t, result, `"min-integrity":"none"`, "default policy should have min-integrity none")
})
Comment on lines +710 to +716
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

These assertions expect the default policy to contain backslash-escaped quotes (e.g. \"allow-only\"). If defaultCliProxyPolicyJSON is corrected to valid JSON (no extra backslashes), this test should be updated to assert for normal JSON fragments (e.g. "allow-only", "repos":"all", "min-integrity":"none"). As written, the test will keep passing even if the emitted policy is not valid JSON for the proxy.

This issue also appears in the following locations of the same file:

  • line 723
  • line 739

Copilot uses AI. Check for mistakes.

t.Run("emits default policy when github tool is nil", func(t *testing.T) {
data := &WorkflowData{
Tools: map[string]any{},
}

result := c.buildStartCliProxyStepYAML(data)
require.NotEmpty(t, result, "should emit CLI proxy step even without github tool")
assert.Contains(t, result, "CLI_PROXY_POLICY", "should always emit CLI_PROXY_POLICY")
assert.Contains(t, result, `"min-integrity":"none"`, "should use default min-integrity")
})

t.Run("uses configured guard policy when present", func(t *testing.T) {
data := &WorkflowData{
Tools: map[string]any{
"github": map[string]any{
"min-integrity": "approved",
"allowed-repos": "owner/*",
},
},
}

result := c.buildStartCliProxyStepYAML(data)
require.NotEmpty(t, result, "should emit CLI proxy step")
assert.Contains(t, result, "CLI_PROXY_POLICY", "should emit CLI_PROXY_POLICY")
assert.Contains(t, result, `"min-integrity":"approved"`, "should use configured min-integrity")
assert.Contains(t, result, `"repos":"owner/*"`, "should use configured repos")
})

t.Run("emits correct step structure", func(t *testing.T) {
data := &WorkflowData{
Tools: map[string]any{
"github": map[string]any{"toolsets": []string{"default"}},
},
}

result := c.buildStartCliProxyStepYAML(data)
assert.Contains(t, result, "name: Start CLI proxy", "should have correct step name")
assert.Contains(t, result, "GH_TOKEN:", "should include GH_TOKEN")
assert.Contains(t, result, "GITHUB_SERVER_URL:", "should include GITHUB_SERVER_URL")
assert.Contains(t, result, "CLI_PROXY_IMAGE:", "should include CLI_PROXY_IMAGE")
assert.Contains(t, result, "start_cli_proxy.sh", "should reference the start script")
})
}
Loading