Skip to content

on.bots: allowlist is never consulted when the confused-deputy check fires on pull_request(_target):synchronize #59952

Description

@abbottdev

Summary

check_membership.cjs calls isConfusedDeputyAttack() and denies the run before it ever checks on.bots:. This means a bot actor that is explicitly trusted via the workflow's bots: allowlist is still unconditionally denied on pull_request/pull_request_target synchronize events whenever it pushes to a PR it didn't open — which is the normal, expected shape of any "AI fixes something and pushes a commit to your PR" workflow (doc-fixers, auto-formatters, dependency-bump bots, etc.).

This appears to contradict the design intent recorded in ADR-29450, whose "Negative consequences" section says of this exact scenario:

Such bots should open their own PRs rather than rebasing others' PRs; if they do not, they would need to be exempted via the bot allowlist.

But the current code never reaches the allowlist check for this case.

Where

actions/setup/js/check_membership.cjs, main():

if (isConfusedDeputyAttack(actorToValidate, eventName, context.payload)) {
  // ...deny, return...
}

const botResult = await checkBotAllowlistAuthorization(actorToValidate, allowedBots, owner, repo);

The confused-deputy check runs unconditionally, before checkBotAllowlistAuthorization. There is no code path where an actor in on.bots: can survive isConfusedDeputyAttack returning true.

isConfusedDeputyAttack itself (check_permissions_utils.cjs):

if ((eventName === "pull_request" || eventName === "pull_request_target") && payload.action === "synchronize" && actor.endsWith("[bot]")
  const prAuthor = payload.pull_request?.user?.login;
  if (prAuthor !== undefined && prAuthor !== actor) {
    return true;
  }
}

Repro

  1. Workflow frontmatter declares:
    on:
      pull_request_target:
        types: [synchronize]
      bots: ["my-fixup-bot[bot]"]
  2. my-fixup-bot[bot] (a GitHub App installed and active on the repo) pushes a commit to a PR opened by a human.
  3. The resulting synchronize event's pre_activation job logs:
    Access denied: Potential confused deputy attack detected. Actor 'my-fixup-bot[bot]' does not
    match the event author. The workflow may have been triggered indirectly via a bot command.
    
  4. is_team_member=false, the run is gated out, and no error surfaces anywhere visible except this warning in the pre_activation job log — the overall run still reports success, so nothing looks broken from the PR or Actions UI.

Observed in the wild: a gh-aw doc-fixer workflow (compiler v0.88.2) pushes commits as a custom GitHub App bot identity, and this silently blocks a separate required PR-review gh-aw workflow's re-run on every such push.

Why this isn't the Dependabot scenario ADR-29450 targeted

The confused-deputy attack ADR-29450 defends against is an attacker manipulating an un-trusted bot's re-trigger into appearing as an authorized actor. That threat model is about an actor not on the allowlist gaining trust it shouldn't have. It does not describe — and the fix shouldn't penalize — a bot the repo owner has already explicitly named in on.bots:, which is an affirmative, per-repo trust grant made by somehe workflow file. Treating an allowlisted bot as indistinguishable from an unauthenticated confused-deputy vector removes a capability(trusted bots pushing fixup commits to PRs) that on.bots: exists to grant, with no way to recover it.

Proposed fix

Check the bots allowlist before (or as an explicit override to) the confused-deputy check, but only for the actor-identity mismatch — not as a blanket bypass. Concretely, in check_membership.cjs:

const botResult = await checkBotAllowlistAuthorization(actorToValidate, allowedBots, owner, repo);
if (botResult.handled) {
  return; // explicitly-trusted, active bot short-circuits the confused-deputy check
}

if (isConfusedDeputyAttack(actorToValidate, eventName, context.payload)) {
  // ...deny...
}

This preserves the ADR's protection for the actual attack (an actor that is not on the allowlist manipulating a trusted bot like dependabot[bot] into appearing as the actor) while letting a repo owner explicitly vouch for a specific bot identity via on.bots:, exactly as the ADR's own
consequences section assumed was already possible.

Alternative considered

Add a new opt-in frontmatter flag analogous to allow-bot-authored-trigger-comment (currently issue_comment-only) for the pull_request(_target):synchronize path — e.g. allow-bot-authored-push: true — rather than changing allowlist ordering. Less surprising if allowlist-reordering has other implications I'm not seeing, but duplicates a second config surface for what on.bots: already claims to grant.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions