From f4643b97364fab7196997e1041bef8883ba55e70 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 15 Mar 2026 19:07:32 +0000 Subject: [PATCH 1/3] Initial plan From 1729da33d8e88892f2a9a210b1a9de158d60f438 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 15 Mar 2026 19:14:04 +0000 Subject: [PATCH 2/3] fix: bot allowlist fallback bypassed when permission check returns API error Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/check_membership.cjs | 33 ++++++++-------- actions/setup/js/check_membership.test.cjs | 45 ++++++++++++++++++++++ 2 files changed, 62 insertions(+), 16 deletions(-) diff --git a/actions/setup/js/check_membership.cjs b/actions/setup/js/check_membership.cjs index 699d38bb419..e8a89bba84c 100644 --- a/actions/setup/js/check_membership.cjs +++ b/actions/setup/js/check_membership.cjs @@ -52,19 +52,14 @@ async function main() { // Check if the actor has the required repository permissions const result = await checkRepositoryPermission(actor, owner, repo, requiredPermissions); - if (result.error) { - core.setOutput("is_team_member", "false"); - core.setOutput("result", "api_error"); - core.setOutput("error_message", `Repository permission check failed: ${result.error}`); - return; - } - if (result.authorized) { core.setOutput("is_team_member", "true"); core.setOutput("result", "authorized"); core.setOutput("user_permission", result.permission); } else { - // User doesn't have required permissions, check if they're an allowed bot + // User doesn't have required permissions (or the permission check failed with an error). + // Always attempt the bot allowlist fallback before giving up, so that GitHub Apps whose + // actor is not a recognised GitHub user (e.g. "Copilot") are not silently denied. if (allowedBots && allowedBots.length > 0) { core.info(`Checking if actor '${actor}' is in allowed bots list: ${allowedBots.join(", ")}`); @@ -94,14 +89,20 @@ async function main() { } // Not authorized by role or bot - core.setOutput("is_team_member", "false"); - core.setOutput("result", "insufficient_permissions"); - core.setOutput("user_permission", result.permission); - core.setOutput( - "error_message", - `Access denied: User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}. ` + - `To allow this user to run the workflow, add their role to the frontmatter. Example: roles: [${requiredPermissions.join(", ")}, ${result.permission}]` - ); + if (result.error) { + core.setOutput("is_team_member", "false"); + core.setOutput("result", "api_error"); + core.setOutput("error_message", `Repository permission check failed: ${result.error}`); + } else { + core.setOutput("is_team_member", "false"); + core.setOutput("result", "insufficient_permissions"); + core.setOutput("user_permission", result.permission); + core.setOutput( + "error_message", + `Access denied: User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}. ` + + `To allow this user to run the workflow, add their role to the frontmatter. Example: roles: [${requiredPermissions.join(", ")}, ${result.permission}]` + ); + } } } diff --git a/actions/setup/js/check_membership.test.cjs b/actions/setup/js/check_membership.test.cjs index 81d61457d3b..9cd490489f5 100644 --- a/actions/setup/js/check_membership.test.cjs +++ b/actions/setup/js/check_membership.test.cjs @@ -381,6 +381,51 @@ describe("check_membership.cjs", () => { expect(mockCore.setOutput).toHaveBeenCalledWith("result", "insufficient_permissions"); }); + it("should authorize a bot in the allowlist when permission check returns an API error (e.g. GitHub App not a user)", async () => { + process.env.GH_AW_ALLOWED_BOTS = "Copilot"; + mockContext.actor = "Copilot"; + + const notAUserError = new Error("Copilot is not a user"); + mockGithub.rest.repos.getCollaboratorPermissionLevel + .mockRejectedValueOnce(notAUserError) // initial permission check → error + .mockResolvedValueOnce({ data: { permission: "none" } }); // bot status check (Copilot[bot] form) → active + + await runScript(); + + expect(mockCore.setOutput).toHaveBeenCalledWith("is_team_member", "true"); + expect(mockCore.setOutput).toHaveBeenCalledWith("result", "authorized_bot"); + expect(mockCore.setOutput).toHaveBeenCalledWith("user_permission", "bot"); + }); + + it("should return bot_not_active when permission check returns API error and bot is not installed", async () => { + process.env.GH_AW_ALLOWED_BOTS = "Copilot"; + mockContext.actor = "Copilot"; + + const notAUserError = new Error("Copilot is not a user"); + const notFoundError = { status: 404, message: "Not Found" }; + mockGithub.rest.repos.getCollaboratorPermissionLevel + .mockRejectedValueOnce(notAUserError) // initial permission check → error + .mockRejectedValue(notFoundError); // all bot status checks → 404 + + await runScript(); + + expect(mockCore.setOutput).toHaveBeenCalledWith("is_team_member", "false"); + expect(mockCore.setOutput).toHaveBeenCalledWith("result", "bot_not_active"); + }); + + it("should return api_error when permission check fails and actor is not in allowed bots list", async () => { + process.env.GH_AW_ALLOWED_BOTS = "some-other-bot"; + mockContext.actor = "Copilot"; + + const notAUserError = new Error("Copilot is not a user"); + mockGithub.rest.repos.getCollaboratorPermissionLevel.mockRejectedValue(notAUserError); + + await runScript(); + + expect(mockCore.setOutput).toHaveBeenCalledWith("is_team_member", "false"); + expect(mockCore.setOutput).toHaveBeenCalledWith("result", "api_error"); + }); + it("should authorize a bot with [bot] suffix in the allowlist via slug fallback", async () => { process.env.GH_AW_ALLOWED_BOTS = "copilot"; mockContext.actor = "copilot[bot]"; From 87fc507c897b5ad746af29c89f3e892b5c087bb5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 15 Mar 2026 19:50:56 +0000 Subject: [PATCH 3/3] =?UTF-8?q?fix:=20spelling=20'recognised'=E2=86=92'rec?= =?UTF-8?q?ognized'=20and=20guard=20result.permission=20in=20bot=5Fnot=5Fa?= =?UTF-8?q?ctive=20path?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/check_membership.cjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actions/setup/js/check_membership.cjs b/actions/setup/js/check_membership.cjs index e8a89bba84c..49103a93e6b 100644 --- a/actions/setup/js/check_membership.cjs +++ b/actions/setup/js/check_membership.cjs @@ -59,7 +59,7 @@ async function main() { } else { // User doesn't have required permissions (or the permission check failed with an error). // Always attempt the bot allowlist fallback before giving up, so that GitHub Apps whose - // actor is not a recognised GitHub user (e.g. "Copilot") are not silently denied. + // actor is not a recognized GitHub user (e.g. "Copilot") are not silently denied. if (allowedBots && allowedBots.length > 0) { core.info(`Checking if actor '${actor}' is in allowed bots list: ${allowedBots.join(", ")}`); @@ -79,7 +79,7 @@ async function main() { core.warning(`Bot '${actor}' is in the allowed list but not active/installed on ${owner}/${repo}`); core.setOutput("is_team_member", "false"); core.setOutput("result", "bot_not_active"); - core.setOutput("user_permission", result.permission); + core.setOutput("user_permission", result.permission ?? "bot"); core.setOutput("error_message", `Access denied: Bot '${actor}' is not active/installed on this repository`); return; } else {