From 38b683b3ff8059a5d56e6f92e58f2fc60f3a9402 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Feb 2026 13:58:50 +0000 Subject: [PATCH 1/4] Initial plan From a86aaef946939e4daa3fdeb9e2ccec4ae8263492 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Feb 2026 14:50:49 +0000 Subject: [PATCH 2/4] fix: improve GitHub App multi-repo token handling for MCP server - Use YAML block scalar (newline-separated) for multiple repositories in actions/create-github-app-token, improving clarity and explicit formatting - Add GH_AW_GITHUB_APP_CONFIGURED env var to lockdown detection step when a GitHub App is configured (values: 'single' or 'multi') - Update determine_automatic_lockdown.cjs to recognize single-repo GitHub App tokens as custom tokens for lockdown on public repos; multi-repo app tokens are excluded from lockdown to preserve cross-repo access - Update test to expect the new block scalar format for multiple repos Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .../setup/js/determine_automatic_lockdown.cjs | 18 ++++++++++++++++-- pkg/workflow/mcp_github_config.go | 19 +++++++++++++++++++ pkg/workflow/safe_outputs_app.go | 18 +++++++++++++----- pkg/workflow/safe_outputs_app_test.go | 6 ++++-- 4 files changed, 52 insertions(+), 9 deletions(-) diff --git a/actions/setup/js/determine_automatic_lockdown.cjs b/actions/setup/js/determine_automatic_lockdown.cjs index 33bdadb729..a42800faa0 100644 --- a/actions/setup/js/determine_automatic_lockdown.cjs +++ b/actions/setup/js/determine_automatic_lockdown.cjs @@ -6,7 +6,8 @@ * and custom token availability. * * Lockdown mode is automatically enabled for public repositories when ANY custom GitHub token - * is configured (GH_AW_GITHUB_TOKEN, GH_AW_GITHUB_MCP_SERVER_TOKEN, or custom github-token). + * is configured (GH_AW_GITHUB_TOKEN, GH_AW_GITHUB_MCP_SERVER_TOKEN, custom github-token, or + * a single-repo GitHub App token). * This prevents unauthorized access to private repositories that the token may have access to. * * For public repositories WITHOUT custom tokens, lockdown mode is disabled (false) as @@ -15,6 +16,12 @@ * For private repositories, lockdown mode is not necessary (false) as there is no risk * of exposing private repository access. * + * GitHub App tokens (GH_AW_GITHUB_APP_CONFIGURED): + * - "single": A GitHub App token scoped to a single repository. Treated as a custom token + * for lockdown purposes on public repositories. + * - "multi": A GitHub App token scoped to multiple repositories. Cross-repo access is + * intentional, so lockdown is NOT enabled (it would break the multi-repo use case). + * * @param {any} github - GitHub API client * @param {any} context - GitHub context * @param {any} core - GitHub Actions core library @@ -43,11 +50,18 @@ async function determineAutomaticLockdown(github, context, core) { const hasGhAwToken = !!process.env.GH_AW_GITHUB_TOKEN; const hasGhAwMcpToken = !!process.env.GH_AW_GITHUB_MCP_SERVER_TOKEN; const hasCustomToken = !!process.env.CUSTOM_GITHUB_TOKEN; - const hasAnyCustomToken = hasGhAwToken || hasGhAwMcpToken || hasCustomToken; + + // GitHub App token: only treat single-repo apps as a "custom token" for lockdown purposes. + // Multi-repo apps intentionally access multiple repos, so lockdown would break their use case. + const appConfigured = process.env.GH_AW_GITHUB_APP_CONFIGURED || ""; + const hasAppToken = appConfigured === "single"; + + const hasAnyCustomToken = hasGhAwToken || hasGhAwMcpToken || hasCustomToken || hasAppToken; core.info(`GH_AW_GITHUB_TOKEN configured: ${hasGhAwToken}`); core.info(`GH_AW_GITHUB_MCP_SERVER_TOKEN configured: ${hasGhAwMcpToken}`); core.info(`Custom github-token configured: ${hasCustomToken}`); + core.info(`GitHub App configured: ${appConfigured || "no"}`); core.info(`Any custom token configured: ${hasAnyCustomToken}`); // Set lockdown based on visibility AND custom token availability diff --git a/pkg/workflow/mcp_github_config.go b/pkg/workflow/mcp_github_config.go index 5167cfcdff..222cf8eb05 100644 --- a/pkg/workflow/mcp_github_config.go +++ b/pkg/workflow/mcp_github_config.go @@ -293,6 +293,22 @@ func (c *Compiler) generateGitHubMCPLockdownDetectionStep(yaml *strings.Builder, // Extract custom github-token if present customToken := getGitHubToken(githubTool) + // Determine if a GitHub App is configured and how many repositories it covers. + // This context helps the lockdown detection make correct security decisions: + // - Single-repo app on a public repo: treat as custom token → enable lockdown + // - Multi-repo app on a public repo: cross-repo access is intentional → no lockdown + var appConfigured string + if data.ParsedTools != nil && data.ParsedTools.GitHub != nil && data.ParsedTools.GitHub.App != nil { + app := data.ParsedTools.GitHub.App + // Wildcard (*) means org-wide access, treated the same as multi-repo + if len(app.Repositories) > 1 || (len(app.Repositories) == 1 && app.Repositories[0] == "*") { + appConfigured = "multi" + } else { + // Single repo or no repos specified (defaults to current repo) + appConfigured = "single" + } + } + // Generate the step using the determine_automatic_lockdown.cjs action yaml.WriteString(" - name: Determine automatic lockdown mode for GitHub MCP Server\n") yaml.WriteString(" id: determine-automatic-lockdown\n") @@ -303,6 +319,9 @@ func (c *Compiler) generateGitHubMCPLockdownDetectionStep(yaml *strings.Builder, if customToken != "" { fmt.Fprintf(yaml, " CUSTOM_GITHUB_TOKEN: %s\n", customToken) } + if appConfigured != "" { + fmt.Fprintf(yaml, " GH_AW_GITHUB_APP_CONFIGURED: %s\n", appConfigured) + } yaml.WriteString(" with:\n") yaml.WriteString(" script: |\n") yaml.WriteString(" const determineAutomaticLockdown = require('/opt/gh-aw/actions/determine_automatic_lockdown.cjs');\n") diff --git a/pkg/workflow/safe_outputs_app.go b/pkg/workflow/safe_outputs_app.go index 3bebf4a6ab..13981e2aea 100644 --- a/pkg/workflow/safe_outputs_app.go +++ b/pkg/workflow/safe_outputs_app.go @@ -4,7 +4,6 @@ import ( "encoding/json" "fmt" "sort" - "strings" "github.com/github/gh-aw/pkg/logger" ) @@ -140,14 +139,23 @@ func (c *Compiler) buildGitHubAppTokenMintStep(app *GitHubAppConfig, permissions // Add repositories - behavior depends on configuration: // - If repositories is ["*"], omit the field to allow org-wide access - // - If repositories is specified with values, use those specific repos + // - If repositories is a single value, use inline format + // - If repositories has multiple values, use block scalar format (newline-separated) + // to ensure clarity and proper parsing by actions/create-github-app-token // - If repositories is empty/not specified, default to current repository if len(app.Repositories) == 1 && app.Repositories[0] == "*" { // Org-wide access: omit repositories field entirely safeOutputsAppLog.Print("Using org-wide GitHub App token (repositories: *)") - } else if len(app.Repositories) > 0 { - reposStr := strings.Join(app.Repositories, ",") - steps = append(steps, fmt.Sprintf(" repositories: %s\n", reposStr)) + } else if len(app.Repositories) == 1 { + // Single repository: use inline format for clarity + steps = append(steps, fmt.Sprintf(" repositories: %s\n", app.Repositories[0])) + } else if len(app.Repositories) > 1 { + // Multiple repositories: use block scalar format (newline-separated) + // This format is more readable and avoids potential issues with comma-separated parsing + steps = append(steps, " repositories: |-\n") + for _, repo := range app.Repositories { + steps = append(steps, fmt.Sprintf(" %s\n", repo)) + } } else { // Extract repo name from github.repository (which is "owner/repo") // Using GitHub Actions expression: split(github.repository, '/')[1] diff --git a/pkg/workflow/safe_outputs_app_test.go b/pkg/workflow/safe_outputs_app_test.go index cb481a442b..ef79165c9d 100644 --- a/pkg/workflow/safe_outputs_app_test.go +++ b/pkg/workflow/safe_outputs_app_test.go @@ -177,8 +177,10 @@ Test workflow with app token minting and repository restrictions. // Convert steps to string for easier assertion stepsStr := strings.Join(job.Steps, "") - // Verify repositories are included in the minting step - assert.Contains(t, stepsStr, "repositories: repo1,repo2", "Should include repositories") + // Verify repositories are included in the minting step using block scalar format + assert.Contains(t, stepsStr, "repositories: |-", "Should use block scalar format for multiple repositories") + assert.Contains(t, stepsStr, "repo1", "Should include first repository") + assert.Contains(t, stepsStr, "repo2", "Should include second repository") } // TestSafeOutputsAppWithoutSafeOutputs tests that app without safe outputs doesn't break From 31beec6cf06e0bf140468ff4477b4e3f9ff4c140 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Feb 2026 15:19:01 +0000 Subject: [PATCH 3/4] fix: skip determine-automatic-lockdown when tools.github.app is configured GitHub App tokens are already scoped to specific repositories via the app installation, so the automatic lockdown detection step is unnecessary and should not be generated. Changes: - generateGitHubMCPLockdownDetectionStep: return early when GitHub App is configured - mcp_renderer.go: set shouldUseStepOutput=false when app is configured - mcp_environment.go: skip GITHUB_MCP_LOCKDOWN env var when app is configured - determine_automatic_lockdown.cjs: remove GH_AW_GITHUB_APP_CONFIGURED complexity (step no longer runs when app is configured) - Add test: TestGitHubMCPAppTokenNoLockdownDetectionStep Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .../setup/js/determine_automatic_lockdown.cjs | 19 ++----- pkg/workflow/github_mcp_app_token_test.go | 52 +++++++++++++++++++ pkg/workflow/mcp_environment.go | 6 ++- pkg/workflow/mcp_github_config.go | 34 +++++------- pkg/workflow/mcp_renderer.go | 9 ++-- 5 files changed, 79 insertions(+), 41 deletions(-) diff --git a/actions/setup/js/determine_automatic_lockdown.cjs b/actions/setup/js/determine_automatic_lockdown.cjs index a42800faa0..1a823331bd 100644 --- a/actions/setup/js/determine_automatic_lockdown.cjs +++ b/actions/setup/js/determine_automatic_lockdown.cjs @@ -6,8 +6,7 @@ * and custom token availability. * * Lockdown mode is automatically enabled for public repositories when ANY custom GitHub token - * is configured (GH_AW_GITHUB_TOKEN, GH_AW_GITHUB_MCP_SERVER_TOKEN, custom github-token, or - * a single-repo GitHub App token). + * is configured (GH_AW_GITHUB_TOKEN, GH_AW_GITHUB_MCP_SERVER_TOKEN, or custom github-token). * This prevents unauthorized access to private repositories that the token may have access to. * * For public repositories WITHOUT custom tokens, lockdown mode is disabled (false) as @@ -16,11 +15,8 @@ * For private repositories, lockdown mode is not necessary (false) as there is no risk * of exposing private repository access. * - * GitHub App tokens (GH_AW_GITHUB_APP_CONFIGURED): - * - "single": A GitHub App token scoped to a single repository. Treated as a custom token - * for lockdown purposes on public repositories. - * - "multi": A GitHub App token scoped to multiple repositories. Cross-repo access is - * intentional, so lockdown is NOT enabled (it would break the multi-repo use case). + * Note: This step is NOT generated when tools.github.app is configured. GitHub App tokens + * are already scoped to specific repositories, so automatic lockdown detection is unnecessary. * * @param {any} github - GitHub API client * @param {any} context - GitHub context @@ -50,18 +46,11 @@ async function determineAutomaticLockdown(github, context, core) { const hasGhAwToken = !!process.env.GH_AW_GITHUB_TOKEN; const hasGhAwMcpToken = !!process.env.GH_AW_GITHUB_MCP_SERVER_TOKEN; const hasCustomToken = !!process.env.CUSTOM_GITHUB_TOKEN; - - // GitHub App token: only treat single-repo apps as a "custom token" for lockdown purposes. - // Multi-repo apps intentionally access multiple repos, so lockdown would break their use case. - const appConfigured = process.env.GH_AW_GITHUB_APP_CONFIGURED || ""; - const hasAppToken = appConfigured === "single"; - - const hasAnyCustomToken = hasGhAwToken || hasGhAwMcpToken || hasCustomToken || hasAppToken; + const hasAnyCustomToken = hasGhAwToken || hasGhAwMcpToken || hasCustomToken; core.info(`GH_AW_GITHUB_TOKEN configured: ${hasGhAwToken}`); core.info(`GH_AW_GITHUB_MCP_SERVER_TOKEN configured: ${hasGhAwMcpToken}`); core.info(`Custom github-token configured: ${hasCustomToken}`); - core.info(`GitHub App configured: ${appConfigured || "no"}`); core.info(`Any custom token configured: ${hasAnyCustomToken}`); // Set lockdown based on visibility AND custom token availability diff --git a/pkg/workflow/github_mcp_app_token_test.go b/pkg/workflow/github_mcp_app_token_test.go index c368aa71bd..191dbe8fd8 100644 --- a/pkg/workflow/github_mcp_app_token_test.go +++ b/pkg/workflow/github_mcp_app_token_test.go @@ -253,3 +253,55 @@ Test org-wide GitHub MCP app token. assert.Contains(t, lockContent, "owner:", "Should include owner field") assert.Contains(t, lockContent, "app-id:", "Should include app-id field") } + +// TestGitHubMCPAppTokenNoLockdownDetectionStep tests that determine-automatic-lockdown +// step is NOT generated when a GitHub App is configured. +// GitHub App tokens are already scoped to specific repositories, so automatic lockdown +// detection is unnecessary. +func TestGitHubMCPAppTokenNoLockdownDetectionStep(t *testing.T) { + compiler := NewCompilerWithVersion("1.0.0") + + markdown := `--- +on: issues +permissions: + contents: read + issues: read +strict: false +tools: + github: + mode: local + app: + app-id: ${{ vars.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + repositories: + - "repo1" + - "repo2" +--- + +# Test Workflow + +Test that determine-automatic-lockdown is not generated when app is configured. +` + + tmpDir := t.TempDir() + testFile := filepath.Join(tmpDir, "test.md") + err := os.WriteFile(testFile, []byte(markdown), 0644) + require.NoError(t, err, "Failed to write test file") + + err = compiler.CompileWorkflow(testFile) + require.NoError(t, err, "Failed to compile workflow") + + lockFile := strings.TrimSuffix(testFile, ".md") + ".lock.yml" + content, err := os.ReadFile(lockFile) + require.NoError(t, err, "Failed to read lock file") + lockContent := string(content) + + // The automatic lockdown detection step must NOT be present when app is configured + assert.NotContains(t, lockContent, "Determine automatic lockdown mode", "determine-automatic-lockdown step should not be generated when app is configured") + assert.NotContains(t, lockContent, "id: determine-automatic-lockdown", "determine-automatic-lockdown step ID should not be present") + assert.NotContains(t, lockContent, "steps.determine-automatic-lockdown.outputs.lockdown", "lockdown step output reference should not be present") + + // App token should still be minted and used + assert.Contains(t, lockContent, "id: github-mcp-app-token", "GitHub App token step should still be generated") + assert.Contains(t, lockContent, "GITHUB_MCP_SERVER_TOKEN: ${{ steps.github-mcp-app-token.outputs.token }}", "App token should be used for MCP server") +} diff --git a/pkg/workflow/mcp_environment.go b/pkg/workflow/mcp_environment.go index 6244e2e420..e1ce8cade0 100644 --- a/pkg/workflow/mcp_environment.go +++ b/pkg/workflow/mcp_environment.go @@ -82,10 +82,12 @@ func collectMCPEnvironmentVariables(tools map[string]any, mcpTools []string, wor envVars["GITHUB_MCP_SERVER_TOKEN"] = effectiveToken } - // Add lockdown value if it's determined from step output + // Add lockdown value if it's determined from step output. + // Skip when a GitHub App is configured — in that case, the determine-automatic-lockdown + // step is not generated, so there is no step output to reference. // Security: Pass step output through environment variable to prevent template injection // Convert "true"/"false" to "1"/"0" at the source to avoid shell conversion in templates - if !hasGitHubLockdownExplicitlySet(githubTool) { + if !hasGitHubLockdownExplicitlySet(githubTool) && !hasGitHubApp { envVars["GITHUB_MCP_LOCKDOWN"] = "${{ steps.determine-automatic-lockdown.outputs.lockdown == 'true' && '1' || '0' }}" } } diff --git a/pkg/workflow/mcp_github_config.go b/pkg/workflow/mcp_github_config.go index 222cf8eb05..3339579728 100644 --- a/pkg/workflow/mcp_github_config.go +++ b/pkg/workflow/mcp_github_config.go @@ -259,8 +259,10 @@ func getGitHubDockerImageVersion(githubTool any) string { // generateGitHubMCPLockdownDetectionStep generates a step to determine automatic lockdown mode // for GitHub MCP server based on repository visibility and token availability. // This step is added when: -// - GitHub tool is enabled AND -// - lockdown field is not explicitly specified in the workflow configuration +// - GitHub tool is enabled AND +// - lockdown field is not explicitly specified in the workflow configuration AND +// - tools.github.app is NOT configured (GitHub App tokens are already repo-scoped, so +// automatic lockdown detection is unnecessary and skipped) // // Lockdown mode is automatically enabled for public repositories when any custom GitHub token // is configured (GH_AW_GITHUB_TOKEN, GH_AW_GITHUB_MCP_SERVER_TOKEN, or custom github-token). @@ -277,6 +279,15 @@ func (c *Compiler) generateGitHubMCPLockdownDetectionStep(yaml *strings.Builder, return } + // Skip automatic lockdown detection when a GitHub App is configured. + // GitHub App tokens are already scoped to specific repositories, so automatic + // lockdown detection is not needed — the token's access is inherently bounded + // by the app installation and the listed repositories. + if data.ParsedTools != nil && data.ParsedTools.GitHub != nil && data.ParsedTools.GitHub.App != nil { + githubConfigLog.Print("GitHub App configured, skipping automatic lockdown determination (app tokens are already repo-scoped)") + return + } + githubConfigLog.Print("Generating automatic lockdown determination step for GitHub MCP server") // Resolve the latest version of actions/github-script @@ -293,22 +304,6 @@ func (c *Compiler) generateGitHubMCPLockdownDetectionStep(yaml *strings.Builder, // Extract custom github-token if present customToken := getGitHubToken(githubTool) - // Determine if a GitHub App is configured and how many repositories it covers. - // This context helps the lockdown detection make correct security decisions: - // - Single-repo app on a public repo: treat as custom token → enable lockdown - // - Multi-repo app on a public repo: cross-repo access is intentional → no lockdown - var appConfigured string - if data.ParsedTools != nil && data.ParsedTools.GitHub != nil && data.ParsedTools.GitHub.App != nil { - app := data.ParsedTools.GitHub.App - // Wildcard (*) means org-wide access, treated the same as multi-repo - if len(app.Repositories) > 1 || (len(app.Repositories) == 1 && app.Repositories[0] == "*") { - appConfigured = "multi" - } else { - // Single repo or no repos specified (defaults to current repo) - appConfigured = "single" - } - } - // Generate the step using the determine_automatic_lockdown.cjs action yaml.WriteString(" - name: Determine automatic lockdown mode for GitHub MCP Server\n") yaml.WriteString(" id: determine-automatic-lockdown\n") @@ -319,9 +314,6 @@ func (c *Compiler) generateGitHubMCPLockdownDetectionStep(yaml *strings.Builder, if customToken != "" { fmt.Fprintf(yaml, " CUSTOM_GITHUB_TOKEN: %s\n", customToken) } - if appConfigured != "" { - fmt.Fprintf(yaml, " GH_AW_GITHUB_APP_CONFIGURED: %s\n", appConfigured) - } yaml.WriteString(" with:\n") yaml.WriteString(" script: |\n") yaml.WriteString(" const determineAutomaticLockdown = require('/opt/gh-aw/actions/determine_automatic_lockdown.cjs');\n") diff --git a/pkg/workflow/mcp_renderer.go b/pkg/workflow/mcp_renderer.go index fa16c16f55..88325ba8ca 100644 --- a/pkg/workflow/mcp_renderer.go +++ b/pkg/workflow/mcp_renderer.go @@ -126,9 +126,12 @@ func (r *MCPConfigRendererUnified) RenderGitHubMCP(yaml *strings.Builder, github // Get lockdown value - use detected value if lockdown wasn't explicitly set lockdown := getGitHubLockdown(githubTool) - // Check if automatic lockdown determination step will be generated - // The step is always generated when lockdown is not explicitly set - shouldUseStepOutput := !hasGitHubLockdownExplicitlySet(githubTool) + // Check if automatic lockdown determination step will be generated. + // The step is skipped when lockdown is explicitly set, or when a GitHub App is configured + // (app tokens are already repo-scoped, so automatic lockdown detection is not needed). + hasGitHubApp := workflowData != nil && workflowData.ParsedTools != nil && + workflowData.ParsedTools.GitHub != nil && workflowData.ParsedTools.GitHub.App != nil + shouldUseStepOutput := !hasGitHubLockdownExplicitlySet(githubTool) && !hasGitHubApp if shouldUseStepOutput { // Use the detected lockdown value from the step output From 1fd49ddbf3752fa635507c8c0487f87d93862472 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Feb 2026 15:43:11 +0000 Subject: [PATCH 4/4] refactor: use merged tools (raw map) for tools.github.app checks via hasGitHubApp helper Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/workflow/mcp_environment.go | 9 +++------ pkg/workflow/mcp_github_config.go | 11 ++++++++++- pkg/workflow/mcp_renderer.go | 4 +--- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/pkg/workflow/mcp_environment.go b/pkg/workflow/mcp_environment.go index e1ce8cade0..16d110bb6c 100644 --- a/pkg/workflow/mcp_environment.go +++ b/pkg/workflow/mcp_environment.go @@ -66,13 +66,10 @@ func collectMCPEnvironmentVariables(tools map[string]any, mcpTools []string, wor githubTool := tools["github"] // Check if GitHub App is configured for token minting - hasGitHubApp := false - if workflowData.ParsedTools != nil && workflowData.ParsedTools.GitHub != nil && workflowData.ParsedTools.GitHub.App != nil { - hasGitHubApp = true - } + appConfigured := hasGitHubApp(githubTool) // If GitHub App is configured, use the app token (overrides other tokens) - if hasGitHubApp { + if appConfigured { mcpEnvironmentLog.Print("Using GitHub App token for GitHub MCP server (overrides custom and default tokens)") envVars["GITHUB_MCP_SERVER_TOKEN"] = "${{ steps.github-mcp-app-token.outputs.token }}" } else { @@ -87,7 +84,7 @@ func collectMCPEnvironmentVariables(tools map[string]any, mcpTools []string, wor // step is not generated, so there is no step output to reference. // Security: Pass step output through environment variable to prevent template injection // Convert "true"/"false" to "1"/"0" at the source to avoid shell conversion in templates - if !hasGitHubLockdownExplicitlySet(githubTool) && !hasGitHubApp { + if !hasGitHubLockdownExplicitlySet(githubTool) && !appConfigured { envVars["GITHUB_MCP_LOCKDOWN"] = "${{ steps.determine-automatic-lockdown.outputs.lockdown == 'true' && '1' || '0' }}" } } diff --git a/pkg/workflow/mcp_github_config.go b/pkg/workflow/mcp_github_config.go index 3339579728..1e954ccaa4 100644 --- a/pkg/workflow/mcp_github_config.go +++ b/pkg/workflow/mcp_github_config.go @@ -81,6 +81,15 @@ func hasGitHubTool(parsedTools *Tools) bool { return parsedTools.GitHub != nil } +// hasGitHubApp checks if a GitHub App is configured in the (merged) GitHub tool configuration +func hasGitHubApp(githubTool any) bool { + if toolConfig, ok := githubTool.(map[string]any); ok { + _, exists := toolConfig["app"] + return exists + } + return false +} + // getGitHubType extracts the mode from GitHub tool configuration (local or remote) func getGitHubType(githubTool any) string { if toolConfig, ok := githubTool.(map[string]any); ok { @@ -283,7 +292,7 @@ func (c *Compiler) generateGitHubMCPLockdownDetectionStep(yaml *strings.Builder, // GitHub App tokens are already scoped to specific repositories, so automatic // lockdown detection is not needed — the token's access is inherently bounded // by the app installation and the listed repositories. - if data.ParsedTools != nil && data.ParsedTools.GitHub != nil && data.ParsedTools.GitHub.App != nil { + if hasGitHubApp(githubTool) { githubConfigLog.Print("GitHub App configured, skipping automatic lockdown determination (app tokens are already repo-scoped)") return } diff --git a/pkg/workflow/mcp_renderer.go b/pkg/workflow/mcp_renderer.go index 88325ba8ca..79395b84b5 100644 --- a/pkg/workflow/mcp_renderer.go +++ b/pkg/workflow/mcp_renderer.go @@ -129,9 +129,7 @@ func (r *MCPConfigRendererUnified) RenderGitHubMCP(yaml *strings.Builder, github // Check if automatic lockdown determination step will be generated. // The step is skipped when lockdown is explicitly set, or when a GitHub App is configured // (app tokens are already repo-scoped, so automatic lockdown detection is not needed). - hasGitHubApp := workflowData != nil && workflowData.ParsedTools != nil && - workflowData.ParsedTools.GitHub != nil && workflowData.ParsedTools.GitHub.App != nil - shouldUseStepOutput := !hasGitHubLockdownExplicitlySet(githubTool) && !hasGitHubApp + shouldUseStepOutput := !hasGitHubLockdownExplicitlySet(githubTool) && !hasGitHubApp(githubTool) if shouldUseStepOutput { // Use the detected lockdown value from the step output