Check collaborator permissions for workflow_run events (#1590)

The write-permission gate previously only ran for issue/PR entity
events. Apply it to workflow_run events as well, checking both the
workflow actor and the actor recorded on the upstream run when they
differ. allowed_non_write_users and the github_token override behave
the same as for entity events. Document the behavior for workflow_run
pipelines.
This commit is contained in:
Ashwin Bhat
2026-08-04 10:05:34 -07:00
committed by GitHub
parent b80a0f042f
commit acb0385805
7 changed files with 233 additions and 9 deletions
+7 -3
View File
@@ -9,7 +9,11 @@ import * as core from "@actions/core";
import { setupGitHubToken } from "../github/token";
import { checkWritePermissions } from "../github/validation/permissions";
import { createOctokit } from "../github/api/client";
import { parseGitHubContext, isEntityContext } from "../github/context";
import {
parseGitHubContext,
isEntityContext,
isWorkflowRunEvent,
} from "../github/context";
import { detectMode } from "../modes/detector";
import { prepareTagMode } from "../modes/tag";
import { prepareAgentMode } from "../modes/agent";
@@ -33,8 +37,8 @@ async function run() {
const githubToken = await setupGitHubToken();
const octokit = createOctokit(githubToken);
// Step 3: Check write permissions (only for entity contexts)
if (isEntityContext(context)) {
// Step 3: Check write permissions (entity contexts and workflow_run)
if (isEntityContext(context) || isWorkflowRunEvent(context)) {
// Check if github_token was provided as input (not from app)
const githubTokenProvided = !!process.env.OVERRIDE_GITHUB_TOKEN;
const hasWritePermissions = await checkWritePermissions(
+5 -2
View File
@@ -21,6 +21,7 @@ import {
isPullRequestEvent,
isPullRequestReviewEvent,
isPullRequestReviewCommentEvent,
isWorkflowRunEvent,
} from "../github/context";
import type { GitHubContext } from "../github/context";
import { detectMode } from "../modes/detector";
@@ -185,8 +186,10 @@ async function run() {
process.env.GITHUB_TOKEN = githubToken;
process.env.GH_TOKEN = githubToken;
// Check write permissions (only for entity contexts)
if (isEntityContext(context)) {
// Check write permissions for entity contexts, and for workflow_run
// events, whose upstream run may have been started by an actor without
// write access (e.g. the author of a fork pull request)
if (isEntityContext(context) || isWorkflowRunEvent(context)) {
const hasWritePermissions = await checkWritePermissions(
octokit.rest,
context,
+6
View File
@@ -282,6 +282,12 @@ export function isPullRequestReviewCommentEvent(
return context.eventName === "pull_request_review_comment";
}
export function isWorkflowRunEvent(
context: GitHubContext,
): context is AutomationContext & { payload: WorkflowRunEvent } {
return context.eventName === "workflow_run";
}
export function isIssuesAssignedEvent(
context: GitHubContext,
): context is ParsedGitHubContext & { payload: IssuesAssignedEvent } {
+45 -3
View File
@@ -1,5 +1,5 @@
import * as core from "@actions/core";
import type { ParsedGitHubContext } from "../context";
import { isWorkflowRunEvent, type GitHubContext } from "../context";
import type { Octokit } from "@octokit/rest";
/**
@@ -24,6 +24,28 @@ function isAllowedBot(actor: string, allowedBots: string): boolean {
return allowedList.includes(normalizedActor);
}
/**
* Collect the actors whose repository access should be checked. This is
* normally just the workflow actor (GITHUB_ACTOR). For workflow_run events
* the actor that started the upstream run is checked as well when it
* differs, since that is the account the run originates from.
*/
function getActorsToCheck(context: GitHubContext): string[] {
const actors = [context.actor];
if (isWorkflowRunEvent(context)) {
const runActor = context.payload.workflow_run?.actor?.login;
if (runActor && !actors.includes(runActor)) {
core.info(
`workflow_run was started by ${runActor}; checking permissions for that actor as well`,
);
actors.push(runActor);
}
}
return actors;
}
/**
* Check if the actor has write permissions to the repository
* @param octokit - The Octokit REST client
@@ -34,11 +56,31 @@ function isAllowedBot(actor: string, allowedBots: string): boolean {
*/
export async function checkWritePermissions(
octokit: Octokit,
context: ParsedGitHubContext,
context: GitHubContext,
allowedNonWriteUsers?: string,
githubTokenProvided?: boolean,
): Promise<boolean> {
const { repository, actor } = context;
for (const actor of getActorsToCheck(context)) {
const allowed = await checkActorWritePermissions(
octokit,
context,
actor,
allowedNonWriteUsers,
githubTokenProvided,
);
if (!allowed) return false;
}
return true;
}
async function checkActorWritePermissions(
octokit: Octokit,
context: GitHubContext,
actor: string,
allowedNonWriteUsers?: string,
githubTokenProvided?: boolean,
): Promise<boolean> {
const { repository } = context;
const allowedBots = context.inputs.allowedBots ?? "";
try {