mirror of
https://github.com/anthropics/claude-code-action.git
synced 2026-08-05 02:38:30 +08:00
Move the allowed_bots check in checkHumanActor and checkWritePermissions so it only fires after the actor has been resolved as a non-User account (GitHub App / bot, or unresolvable app actor). Actors that resolve to a regular User account go through the standard human/write checks regardless of allowed_bots. The Copilot-style path (GITHUB_ACTOR not ending in [bot] and not resolvable as a user) is unchanged: it still falls through to the existing 404 catch, which already consults allowed_bots once the API has reported the actor is not a user. Update tests to match and add coverage for the User-account path.
122 lines
4.4 KiB
TypeScript
122 lines
4.4 KiB
TypeScript
import * as core from "@actions/core";
|
|
import type { ParsedGitHubContext } from "../context";
|
|
import type { Octokit } from "@octokit/rest";
|
|
|
|
/**
|
|
* Check if a bot actor is in the allowed bots list.
|
|
*/
|
|
function isAllowedBot(actor: string, allowedBots: string): boolean {
|
|
const trimmed = allowedBots.trim();
|
|
if (trimmed === "*") return true;
|
|
if (!trimmed) return false;
|
|
|
|
const allowedList = trimmed
|
|
.split(",")
|
|
.map((bot) =>
|
|
bot
|
|
.trim()
|
|
.toLowerCase()
|
|
.replace(/\[bot\]$/, ""),
|
|
)
|
|
.filter((bot) => bot.length > 0);
|
|
|
|
const normalizedActor = actor.toLowerCase().replace(/\[bot\]$/, "");
|
|
return allowedList.includes(normalizedActor);
|
|
}
|
|
|
|
/**
|
|
* Check if the actor has write permissions to the repository
|
|
* @param octokit - The Octokit REST client
|
|
* @param context - The GitHub context
|
|
* @param allowedNonWriteUsers - Comma-separated list of users allowed without write permissions, or '*' for all
|
|
* @param githubTokenProvided - Whether github_token was provided as input (not from app)
|
|
* @returns true if the actor has write permissions, false otherwise
|
|
*/
|
|
export async function checkWritePermissions(
|
|
octokit: Octokit,
|
|
context: ParsedGitHubContext,
|
|
allowedNonWriteUsers?: string,
|
|
githubTokenProvided?: boolean,
|
|
): Promise<boolean> {
|
|
const { repository, actor } = context;
|
|
const allowedBots = context.inputs.allowedBots ?? "";
|
|
|
|
try {
|
|
core.info(`Checking permissions for actor: ${actor}`);
|
|
|
|
// Check if we should bypass permission checks for this user
|
|
if (allowedNonWriteUsers && githubTokenProvided) {
|
|
const allowedUsers = allowedNonWriteUsers.trim();
|
|
if (allowedUsers === "*") {
|
|
core.warning(
|
|
`⚠️ SECURITY WARNING: Bypassing write permission check for ${actor} due to allowed_non_write_users='*'. This should only be used for workflows with very limited permissions.`,
|
|
);
|
|
return true;
|
|
} else if (allowedUsers) {
|
|
const allowedUserList = allowedUsers
|
|
.split(",")
|
|
.map((u) => u.trim())
|
|
.filter((u) => u.length > 0);
|
|
if (allowedUserList.includes(actor)) {
|
|
core.warning(
|
|
`⚠️ SECURITY WARNING: Bypassing write permission check for ${actor} due to allowed_non_write_users configuration. This should only be used for workflows with very limited permissions.`,
|
|
);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check if the actor is a GitHub App (bot user with [bot] suffix).
|
|
// Usernames cannot contain "[" or "]", so the suffix is a reliable
|
|
// bot signal that doesn't require an API lookup.
|
|
if (actor.endsWith("[bot]")) {
|
|
core.info(`Actor is a GitHub App: ${actor}`);
|
|
return true;
|
|
}
|
|
|
|
// For all other actors, resolve the account via the collaborator
|
|
// permission endpoint. allowed_bots is only consulted in the catch
|
|
// block below, after the API has confirmed the actor is not a regular
|
|
// user account (e.g. GitHub Apps like Copilot whose GITHUB_ACTOR is
|
|
// "Copilot" rather than "Copilot[bot]").
|
|
const response = await octokit.repos.getCollaboratorPermissionLevel({
|
|
owner: repository.owner,
|
|
repo: repository.repo,
|
|
username: actor,
|
|
});
|
|
|
|
const permissionLevel = response.data.permission;
|
|
core.info(`Permission level retrieved: ${permissionLevel}`);
|
|
|
|
if (permissionLevel === "admin" || permissionLevel === "write") {
|
|
core.info(`Actor has write access: ${permissionLevel}`);
|
|
return true;
|
|
} else {
|
|
core.warning(`Actor has insufficient permissions: ${permissionLevel}`);
|
|
return false;
|
|
}
|
|
} catch (error) {
|
|
// Handle 404 errors for non-user actors (e.g. GitHub Apps like Copilot
|
|
// whose GITHUB_ACTOR doesn't end with [bot]).
|
|
// The collaborator permission API only works for user accounts.
|
|
if (error instanceof Error && error.message.includes("is not a user")) {
|
|
core.info(
|
|
`Actor ${actor} is not a GitHub user (likely a GitHub App). Checking allowed_bots...`,
|
|
);
|
|
if (isAllowedBot(actor, allowedBots)) {
|
|
core.info(
|
|
`Non-user actor ${actor} is in allowed_bots list, granting access`,
|
|
);
|
|
return true;
|
|
}
|
|
core.warning(
|
|
`Non-user actor ${actor} is not in allowed_bots list. Add it to allowed_bots or use '*' to allow all bots.`,
|
|
);
|
|
return false;
|
|
}
|
|
|
|
core.error(`Failed to check permissions: ${error}`);
|
|
throw new Error(`Failed to check permissions for ${actor}: ${error}`);
|
|
}
|
|
}
|