GitHub: improper authorization
High8.8Published Aug 16, 2026
### Summary `isConfusedDeputyAttack` exists to catch the case where the actor who triggered a workflow is not the author of the content the agent will read. It handles four event names. `pull_request_target` is not one of them, and the string does not appear anywhere in the file. On `pull_request_target: types: [labeled]` the actor is the maintainer who applied the label. The membership gate validates that maintainer, the confused-deputy check returns false because it has no branch for the event, and the agent then runs in the base-repo context reading a fork PR written by someone else entirely. The gate answers "may this person start the agent". Nobody asks "who wrote the content the agent is about to read". I want to put the argument plainly, because the easy reply is that the adopter chose an untrusted trigger. I am not arguing that actor != author is always suspicious. For `pull_request` your own comment gives a good reason to tolerate it, and I agree with it. The argument is narrower: this mechanism exists specifically to detect the confused-deputy condition, and it omits the event where the actor/content principal mismatch intersects with the highest-trust execution cont...
Affected versions
| Package | Affected | Fixed in |
|---|---|---|
| github/gh-aw Product | < v0.87.0 | v0.87.0 |
Details and references
### Summary `isConfusedDeputyAttack` exists to catch the case where the actor who triggered a workflow is not the author of the content the agent will read. It handles four event names. `pull_request_target` is not one of them, and the string does not appear anywhere in the file. On `pull_request_target: types: [labeled]` the actor is the maintainer who applied the label. The membership gate validates that maintainer, the confused-deputy check returns false because it has no branch for the event, and the agent then runs in the base-repo context reading a fork PR written by someone else entirely. The gate answers "may this person start the agent". Nobody asks "who wrote the content the agent is about to read". I want to put the argument plainly, because the easy reply is that the adopter chose an untrusted trigger. I am not arguing that actor != author is always suspicious. For `pull_request` your own comment gives a good reason to tolerate it, and I agree with it. The argument is narrower: this mechanism exists specifically to detect the confused-deputy condition, and it omits the event where the actor/content principal mismatch intersects with the highest-trust execution context. The check covers four sibling events and skips that one. An adopter using a documented trigger with a documented `forks:` key gets no coverage from a function written for exactly their situation. The distinction that matters for a fix is fork versus internal, not labeled versus not. A PR where `pull_request.head.repo.full_name != pull_request.base.repo.full_name` is authored by someone outside the repository's trust boundary; a same-repo PR is not, and its author already holds repository permissions. Keying on that boundary lets you close this without disturbing the ordinary case of a maintainer labelling an internal PR, which I expect is the false-positive you would worry about first. ### Details `actions/setup/js/check_permissions_utils.cjs`, `isConfusedDeputyAttack` at `:107`. The branches are: - `:120` `pull_request` — only when `action === "synchronize"` and the actor ends with `[bot]` - `:130` `pull_request_review` — reviewer must match the actor - `:140` `pull_request_review_comment` — comment author must match the actor - `:161` `issue_comment` — comment author must match the actor Then `return false`. `grep -c pull_request_target` on that file returns **0** at v0.85.4 and **0** on main at `b2ef1f3`. The comment at `:114-116` explains the design for `pull_request`: > Other pull_request actions (labeled, unlabeled, assigned, review_requested, > etc.) legitimately have actor != pr_author — the actor is whoever performed the > action, not the PR author — so checking those would cause false positives. That reasoning is sound for `pull_request`, which does not get repository secrets on a fork PR. `pull_request_target` does. The same "actor != author is normal" assumption is carried into an event where the content is untrusted and the token is not, and the file never distinguishes them because it never sees the event name at all. `check_membership.cjs:195` calls the function and, when it returns false, falls through to `checkRepositoryPermission` on `context.actor` — the labeler. ### PoC 7 assertions driving the unmodified shipped `check_permissions_utils.cjs`. No network, no third-party system. Attached, runs in one command. Environment: node 18.19.1, WSL2 Ubuntu 24.04. The payload is a `labeled` event where the PR author is `attacker` and the actor is `maintainer`: - **T1** At v0.85.4, `isConfusedDeputyAttack("maintainer", "pull_request_target", …)` returns **false**. The attacker-authored PR is not flagged. - **T2** Positive controls: the same harness gets **true** for `issue_comment` and `pull_request_review` with the same author/actor mismatch. The branches that exist do fire, so a false negative in T1 is a real gap and not a broken t
More GitHub advisories
All GitHub| Date | Advisory | Severity | Fixed in |
|---|---|---|---|
| Aug 25 | gh-aw: github.event.* command injection via heredoc-blind template guardrails (MCP config, all engines) | High8.5 | No fix yet |
| Aug 16 | GitHub: command injection | High8.5 | v0.87.0 |
| Aug 8 | gh-aw: safe-output validator forwards undeclared agent fields to the appliers (scope escape / mass assignment) | Critical9.1 | v0.86.1 |
| Aug 7 | gh-aw: unauthenticated prompt-injection to code execution in the shipped ai-moderator workflow | Medium6.5 | v0.86.1 |
| Aug 7 | command injection in compiled workflow via unsanitized `sandbox.mcp.env` exports | Critical9.6 | v0.86.0 |
| Aug 7 | gh-aw: URL allowlist bypass via userinfo @ in the content sanitizer (exfiltration channel) | Medium6.8 | v0.86.0 |