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
- Workflow frontmatter declares:
on:
pull_request_target:
types: [synchronize]
bots: ["my-fixup-bot[bot]"]
my-fixup-bot[bot] (a GitHub App installed and active on the repo) pushes a commit to a PR opened by a human.
- 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.
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.
Summary
check_membership.cjscallsisConfusedDeputyAttack()and denies the run before it ever checkson.bots:. This means a bot actor that is explicitly trusted via the workflow'sbots:allowlist is still unconditionally denied onpull_request/pull_request_targetsynchronizeevents 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:
But the current code never reaches the allowlist check for this case.
Where
actions/setup/js/check_membership.cjs,main():The confused-deputy check runs unconditionally, before
checkBotAllowlistAuthorization. There is no code path where an actor inon.bots:can surviveisConfusedDeputyAttackreturningtrue.isConfusedDeputyAttackitself (check_permissions_utils.cjs):Repro
my-fixup-bot[bot](a GitHub App installed and active on the repo) pushes a commit to a PR opened by a human.synchronizeevent'spre_activationjob logs:is_team_member=false, the run is gated out, and no error surfaces anywhere visible except this warning in thepre_activationjob log — the overall run still reportssuccess, 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) thaton.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: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 viaon.bots:, exactly as the ADR's ownconsequences section assumed was already possible.
Alternative considered
Add a new opt-in frontmatter flag analogous to
allow-bot-authored-trigger-comment(currentlyissue_comment-only) for thepull_request(_target):synchronizepath — 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 whaton.bots:already claims to grant.