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
15 changes: 14 additions & 1 deletion .github/workflows/daily-issues-report.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 12 additions & 4 deletions .github/workflows/smoke-codex.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

42 changes: 42 additions & 0 deletions pkg/cli/compile_guard_policy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,45 @@ This workflow specifies repos without min-integrity.
})
}
}

// TestGuardPolicyMinIntegrityOnlyCompiledOutput verifies that when only min-integrity is
// specified (without repos), the compiled lock file includes repos="all" in the guard policy.
// This is a regression test for the MCP Gateway requirement that allow-only must include repos.
func TestGuardPolicyMinIntegrityOnlyCompiledOutput(t *testing.T) {
workflowContent := `---
on:
workflow_dispatch:
permissions:
contents: read
engine: copilot
tools:
github:
min-integrity: approved
---
# Guard Policy Test
This workflow uses min-integrity without specifying repos.
`

tmpDir := t.TempDir()
workflowPath := filepath.Join(tmpDir, "test-guard-policy.md")
err := os.WriteFile(workflowPath, []byte(workflowContent), 0644)
require.NoError(t, err, "Failed to write workflow file")

compiler := workflow.NewCompiler()
err = CompileWorkflowWithValidation(compiler, workflowPath, false, false, false, false, false, false)
require.NoError(t, err, "Expected compilation to succeed")

// Read the compiled lock file and verify it contains the correct guard-policies JSON block.
// The MCP Gateway requires repos to be present in the allow-only policy.
lockFilePath := filepath.Join(tmpDir, "test-guard-policy.lock.yml")
lockFileBytes, err := os.ReadFile(lockFilePath)
require.NoError(t, err, "Failed to read compiled lock file")

lockFileContent := string(lockFileBytes)
// Check that the guard-policies allow-only block contains both repos=all and min-integrity=approved
// in the correct JSON structure expected by the MCP Gateway.
assert.Contains(t, lockFileContent, `"guard-policies": {`+"\n"+` "allow-only": {`+"\n"+` "min-integrity": "approved",`+"\n"+` "repos": "all"`,
"Compiled lock file must include repos=all and min-integrity=approved in the guard-policies allow-only block")
Comment on lines +185 to +189
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

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

The test asserts a multi-line substring with exact indentation/newlines from the generated lock file. This is brittle and will fail on any formatting change (e.g., JSON pretty-print indent, YAML indentation, or renderer tweaks) even if the underlying guard policy is correct. Prefer parsing the relevant generated JSON/TOML block (or using a whitespace-insensitive regex) and asserting on the structured values for allow-only.min-integrity and allow-only.repos.

Copilot uses AI. Check for mistakes.
}
8 changes: 8 additions & 0 deletions pkg/workflow/mcp_github_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,10 @@ func getGitHubAllowedTools(githubTool any) []string {

// getGitHubGuardPolicies extracts guard policies from GitHub tool configuration.
// It reads the flat repos/min-integrity fields and wraps them for MCP gateway rendering.
// When min-integrity is set but repos is not, repos defaults to "all" because the MCP
// Gateway requires repos to be present in the allow-only policy.
// Note: repos-only (without min-integrity) is rejected earlier by validateGitHubGuardPolicy,
// so this function will never be called with repos but without min-integrity in practice.
// Returns nil if no guard policies are configured.
func getGitHubGuardPolicies(githubTool any) map[string]any {
if toolConfig, ok := githubTool.(map[string]any); ok {
Expand All @@ -244,6 +248,10 @@ func getGitHubGuardPolicies(githubTool any) map[string]any {
policy := map[string]any{}
if hasRepos {
policy["repos"] = repos
} else {
// Default repos to "all" when min-integrity is specified without repos.
// The MCP Gateway requires repos in the allow-only policy.
policy["repos"] = "all"
}
if hasIntegrity {
policy["min-integrity"] = integrity
Expand Down
9 changes: 7 additions & 2 deletions pkg/workflow/safeoutputs_guard_policy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,8 +197,13 @@ func TestDeriveSafeOutputsGuardPolicyFromGitHub(t *testing.T) {
githubTool: map[string]any{
"min-integrity": "approved",
},
expectNil: true,
description: "No repos means no guard-policy for safeoutputs",
expectedPolicies: map[string]any{
"write-sink": map[string]any{
"accept": []string{"*"},
},
},
expectNil: false,
description: "No repos defaults to all, which means accept=[*] for safeoutputs",
},
{
name: "no guard policy at all",
Expand Down
Loading