Skip to content

security: guard bot filtering against dependabot confused deputy attacks#29432

Merged
pelikhan merged 6 commits intomainfrom
copilot/guard-bot-filtering-against-dependabot-attacks
May 1, 2026
Merged

security: guard bot filtering against dependabot confused deputy attacks#29432
pelikhan merged 6 commits intomainfrom
copilot/guard-bot-filtering-against-dependabot-attacks

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented May 1, 2026

Summary

Guards bot filtering against the Dependabot Confused Deputy Injection technique documented in https://labs.boostsecurity.io/articles/weaponizing-dependabot-pwn-request-at-its-finest/

Attack Vector

An attacker can trigger @dependabot recreate (for pull_request events) or @dependabot show (for issue_comment events) to make dependabot[bot] appear as github.actor on an arbitrary PR. Because dependabot typically has write access to the repository, this bypasses the permission check in check_membership.cjs and allows the attacker's unauthorized PR to activate the agentic workflow.

Changes

  • check_permissions_utils.cjs: Add isConfusedDeputyAttack(actor, eventName, payload) helper that returns true when the current actor does not match the event-specific author:

    • pull_request with action synchronize: actor must match payload.pull_request.user.login (PR author). The check is scoped to the synchronize action only — the documented attack vector (@dependabot recreate). Other pull_request actions (labeled, unlabeled, assigned, review_requested, etc.) legitimately have the actor differ from the PR author and are excluded.
    • pull_request_review: actor must match payload.review.user.login (reviewer)
    • pull_request_review_comment: actor must match payload.comment.user.login (comment author)
    • issue_comment: actor must match payload.comment.user.login (comment author)
  • check_membership.cjs: Before any permission or bot-allowlist check, call isConfusedDeputyAttack. If detected, deny with result confused_deputy and an actionable error message.

  • check_skip_bots.cjs: Before applying skip-bots rules, call isConfusedDeputyAttack. If detected, do not skip the workflow — this prevents an attacker from suppressing the workflow run by making a skip-bot appear as the actor.

  • trigger_parser.go: The compiled condition for the "dependabot pull request" trigger shorthand now requires both github.actor == 'dependabot[bot]' and github.event.pull_request.user.login == 'dependabot[bot]', ensuring the PR was actually authored by Dependabot and not merely synchronized by it.

  • docs/adr/29450-guard-bot-filter-against-dependabot-confused-deputy-injection.md: ADR documenting the decision, attack vectors, rejected alternatives, and RFC 2119 normative specification.

Tests

  • check_permissions_utils.test.cjs: Tests for isConfusedDeputyAttack covering PR synchronize events (attack detected), PR labeled/unlabeled/review_requested events (no false positive), pull_request_review (actor = reviewer, not PR author), pull_request_review_comment (actor = comment author, not PR author), issue_comment events, workflow_call events (no false positives), and edge cases.
  • check_membership.test.cjs: Tests covering confused deputy denial (pull_request synchronize, issue_comment), no false positive for pull_request:labeled, genuine dependabot PR allowance, safe event bypass, non-PR events, and workflow_call with aw_context (no false positive).
  • check_skip_bots.test.cjs: Tests covering confused deputy skip prevention (synchronize), genuine bot skip, pull_request:labeled skip-bots applied normally, issue_comment, no-payload cases, and workflow_call compatibility.
  • trigger_parser_test.go: Updated expected condition for "dependabot pull request" trigger.


✨ PR Review Safe Output Test - Run 25203411948

💥 [THE END] — Illustrated by Smoke Claude · ● 340.4K ·

Add protection against the Dependabot Confused Deputy Injection technique
documented at https://labs.boostsecurity.io/articles/weaponizing-dependabot-pwn-request-at-its-finest/

An attacker can trigger @dependabot recreate (for pull_request events) or
@dependabot show (for issue_comment events) to make dependabot appear as
github.actor on an arbitrary PR, bypassing permission checks that rely
solely on github.actor.

Changes:
- Add isConfusedDeputyAttack() to check_permissions_utils.cjs: detects
  when actor != PR author (pull_request events) or comment author
  (issue_comment events)
- Guard check_membership.cjs: deny access with 'confused_deputy' result
  when attack is detected before any permission or bot-allowlist checks
- Guard check_skip_bots.cjs: skip the bot-skip rule when confused deputy
  is detected, preventing attackers from suppressing workflow runs
- Fix trigger_parser.go 'dependabot pull request' condition to also
  require github.event.pull_request.user.login == 'dependabot[bot]'
- Add comprehensive tests for all changed components

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/ea6c8a30-d88d-4c83-a604-7678ebce227b

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
@pelikhan
Copy link
Copy Markdown
Collaborator

pelikhan commented May 1, 2026

@copilot make sure this works with workflow_call and agent context

Add tests documenting that isConfusedDeputyAttack correctly returns false
for workflow_call events, preventing false positives in reusable workflow
scenarios where context.payload = { inputs: { aw_context: "..." } } and
has no pull_request.user or comment.user fields.

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/31d9390c-d678-4471-93aa-2edc4f23bfc7

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 1, 2026

@copilot make sure this works with workflow_call and agent context

For workflow_call events, context.eventName = "workflow_call" (never pull_request/issue_comment) and context.payload = { inputs: { aw_context: "..." } } — no pull_request.user or comment.user fields are present. isConfusedDeputyAttack correctly returns false in all workflow_call scenarios, so reusable workflows work without any false positives.

Added explicit tests documenting this behaviour in commit 94dd4d3:

  • isConfusedDeputyAttack with workflow_call + aw_context payload → false
  • isConfusedDeputyAttack with workflow_call + injected pull_request field in payload → false (eventName guard ensures safety)
  • check_membership workflow_call + aw_context → proceeds to normal permission check, no confused_deputy denial
  • check_skip_bots workflow_call + aw_context → normal skip-bots logic applies

For the "agent context" (the agent job that runs after activation): the confused deputy check only runs at activation time in pre_activation. Once the activation job succeeds, the agent job runs under the already-validated actor — no interference.

@pelikhan pelikhan marked this pull request as ready for review May 1, 2026 04:19
Copilot AI review requested due to automatic review settings May 1, 2026 04:19
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens workflow actor/bot filtering against the “Dependabot Confused Deputy” injection technique, preventing attackers from spoofing github.actor via Dependabot commands to bypass permission checks or suppress runs.

Changes:

  • Add isConfusedDeputyAttack(actor, eventName, payload) helper and integrate it into membership and skip-bots checks.
  • Tighten the "dependabot pull request" trigger shorthand to require both actor and PR author to be Dependabot.
  • Add/adjust unit tests for the new confused-deputy protection paths.
Show a summary per file
File Description
pkg/workflow/trigger_parser.go Strengthens Dependabot PR shorthand condition to also validate PR author.
pkg/workflow/trigger_parser_test.go Updates expected shorthand condition string for Dependabot PR trigger.
actions/setup/js/check_permissions_utils.cjs Introduces isConfusedDeputyAttack helper and exports it.
actions/setup/js/check_permissions_utils.test.cjs Adds unit tests for isConfusedDeputyAttack.
actions/setup/js/check_membership.cjs Denies early when a confused-deputy pattern is detected.
actions/setup/js/check_membership.test.cjs Adds tests covering confused-deputy denial/allowance scenarios.
actions/setup/js/check_skip_bots.cjs Prevents skip-bots suppression when a confused-deputy pattern is detected.
actions/setup/js/check_skip_bots.test.cjs Adds tests ensuring confused-deputy prevents skipping and avoids false positives for workflow_call.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 8/8 changed files
  • Comments generated: 2

Comment on lines +69 to +76
// For pull_request, pull_request_review, and pull_request_review_comment events,
// the PR author must match the actor. @dependabot recreate triggers a synchronize
// event with actor=dependabot[bot] but pull_request.user = original human author.
const prEvents = ["pull_request", "pull_request_review", "pull_request_review_comment"];
if (prEvents.includes(eventName)) {
const prAuthor = payload.pull_request?.user?.login;
if (prAuthor !== undefined && prAuthor !== actor) {
return true;
Copy link

Copilot AI May 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isConfusedDeputyAttack treats pull_request_review and pull_request_review_comment as if the PR author must match github.actor, but for these events the actor is typically the reviewer/commenter and will not match payload.pull_request.user.login in normal operation. This causes false positives that will deny check_membership and bypass check_skip_bots for essentially all review/review-comment triggers. Use the event-specific author fields instead (e.g., payload.review.user.login for pull_request_review and payload.comment.user.login for pull_request_review_comment), and only fall back to payload.pull_request.user.login for pull_request events.

Copilot uses AI. Check for mistakes.
Comment on lines +563 to +569
it("should return true for pull_request_review when actor differs from PR author", () => {
const payload = { pull_request: { user: { login: "attacker" } } };
expect(isConfusedDeputyAttack("dependabot[bot]", "pull_request_review", payload)).toBe(true);
});

it("should return true for pull_request_review_comment when actor differs from PR author", () => {
const payload = { pull_request: { user: { login: "attacker" } } };
Copy link

Copilot AI May 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These new tests assert that isConfusedDeputyAttack() should return true for pull_request_review / pull_request_review_comment when github.actor differs from the PR author. For these event types, the relevant author to compare against is the review author (payload.review.user.login) or the review comment author (payload.comment.user.login), not payload.pull_request.user.login. As written, the tests codify the same false-positive behavior that will break review/review-comment triggered workflows.

Suggested change
it("should return true for pull_request_review when actor differs from PR author", () => {
const payload = { pull_request: { user: { login: "attacker" } } };
expect(isConfusedDeputyAttack("dependabot[bot]", "pull_request_review", payload)).toBe(true);
});
it("should return true for pull_request_review_comment when actor differs from PR author", () => {
const payload = { pull_request: { user: { login: "attacker" } } };
it("should return false for pull_request_review when actor matches review author even if PR author differs", () => {
const payload = {
pull_request: { user: { login: "attacker" } },
review: { user: { login: "dependabot[bot]" } },
};
expect(isConfusedDeputyAttack("dependabot[bot]", "pull_request_review", payload)).toBe(false);
});
it("should return true for pull_request_review when actor differs from review author even if PR author matches", () => {
const payload = {
pull_request: { user: { login: "dependabot[bot]" } },
review: { user: { login: "attacker" } },
};
expect(isConfusedDeputyAttack("dependabot[bot]", "pull_request_review", payload)).toBe(true);
});
it("should return false for pull_request_review_comment when actor matches comment author even if PR author differs", () => {
const payload = {
pull_request: { user: { login: "attacker" } },
comment: { user: { login: "dependabot[bot]" } },
};
expect(isConfusedDeputyAttack("dependabot[bot]", "pull_request_review_comment", payload)).toBe(false);
});
it("should return true for pull_request_review_comment when actor differs from comment author even if PR author matches", () => {
const payload = {
pull_request: { user: { login: "dependabot[bot]" } },
comment: { user: { login: "attacker" } },
};

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great suggestion! Using event-specific author fields (payload.review.user.login for pull_request_review) instead of always comparing against the PR author is the correct approach to avoid false positives on normal review workflows.

📰 BREAKING: Report filed by Smoke Copilot · ● 1.3M

@pelikhan
Copy link
Copy Markdown
Collaborator

pelikhan commented May 1, 2026

@copilot review all comments

For pull_request_review, compare actor against payload.review.user.login
(the reviewer) rather than payload.pull_request.user.login (the PR author).
For pull_request_review_comment, compare actor against
payload.comment.user.login (the comment author), not the PR author.

Using pull_request.user.login for all three event types caused false
positives on every review/review-comment workflow run, since reviewers
are almost never the PR author.

Update tests to cover the correct event-specific author comparisons,
including explicit false-positive prevention cases.

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/f5e1c9c7-f623-40ba-8b83-d460dec714be

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 1, 2026

@copilot review all comments

Addressed the code reviewer's finding in commit 7df1e43:

  • pull_request_review: now compares actor against payload.review.user.login (the reviewer), not payload.pull_request.user.login (the PR author) — which would have caused false positives on every review workflow since reviewers are almost never the PR author
  • pull_request_review_comment: now compares actor against payload.comment.user.login (the comment author), not the PR author — same reasoning
  • pull_request: unchanged, still compares against payload.pull_request.user.login

Updated tests explicitly cover: (1) actor = reviewer, PR author differs → false (no false positive), (2) actor ≠ reviewer → true (real attack), and the same pattern for review comments.

@pelikhan
Copy link
Copy Markdown
Collaborator

pelikhan commented May 1, 2026

@copilot do a deep review, update ADR

Copilot AI and others added 2 commits May 1, 2026 04:42
…uty injection

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 1, 2026

@copilot do a deep review, update ADR

Deep review complete — no issues found beyond what was already fixed in commit 7df1e43.

ADR added in commit 108cfce: docs/adr/29450-guard-bot-filter-against-dependabot-confused-deputy-injection.md

The ADR covers:

  • Context: why github.actor is an insufficient identity signal and how the attack works
  • All 4 changes (helper, membership check, skip-bots bypass, trigger_parser condition)
  • 4 rejected alternatives (allowlist-only, GITHUB_TRIGGERING_ACTOR, deny-all-bots, compile-time-only)
  • RFC 2119 normative spec for each of the four change areas
  • Correct note that protection is automatic for existing compiled workflows (JS helpers are runtime-loaded, not embedded in lock files)

@pelikhan
Copy link
Copy Markdown
Collaborator

pelikhan commented May 1, 2026

@copilot review this scenario where the workflow is triggered by a label. Be more precise for this check.

https://github.com/github/gh-aw/actions/runs/25202808475/job/73897140423#step:4:1

Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 1, 2026

@copilot review this scenario where the workflow is triggered by a label. Be more precise for this check.

https://github.com/github/gh-aw/actions/runs/25202808475/job/73897140423#step:4:1

Fixed in commit 4479d66.

The log showed actor 'pelikhan' does not match the event authorpelikhan had labeled a PR authored by copilot[bot], triggering a pull_request:labeled event where the actor is legitimately the labeler, not the PR author.

The attack specifically exploits the synchronize action (@dependabot recreate force-pushes to the branch). The fix narrows the pull_request check to only fire when payload.action === "synchronize". Actions like labeled, unlabeled, assigned, review_requested, ready_for_review, etc. are now excluded — in those cases the actor is intentionally the person who performed the action, not the PR author.

Tests updated to include action: "synchronize" in all attack scenarios, plus new false cases for labeled, unlabeled, and review_requested. ADR-29450 updated accordingly.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • invalid.example.invalid
    • Triggering command: /usr/lib/git-core/git-remote-https /usr/lib/git-core/git-remote-https origin https://invalid.example.invalid/nonexistent-repo.git e/git conf�� --local --get ode_modules/.bin/git =receive Test User ck (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

@pelikhan pelikhan added smoke and removed smoke labels May 1, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 1, 2026

✨ The prophecy is fulfilled... Smoke Codex has completed its mystical journey. The stars align. 🌟

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 1, 2026

⚠️ Smoke Crush failed. Crush encountered unexpected challenges...

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 1, 2026

⚠️ Smoke Gemini failed. Gemini encountered unexpected challenges...

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 1, 2026

📰 BREAKING: Smoke Copilot is now investigating this pull request. Sources say the story is developing...

@github-actions github-actions Bot removed the smoke label May 1, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 1, 2026

🎬 THE ENDSmoke Claude MISSION: ACCOMPLISHED! The hero saves the day! ✨

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 1, 2026

🚀 Smoke OpenCode MISSION COMPLETE! OpenCode delivered. 🔥

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 1, 2026

Agent Container Tool Check

Tool Status Version
bash 5.2.21
sh available
git 2.53.0
jq 1.7
yq 4.52.5
curl 8.5.0
gh 2.89.0
node 22.22.2
python3 3.10.16 (PyPy 7.3.19)
go 1.24.13
java 10.0.201
dotnet missing

Result: 11/12 tools available ⚠️

Overall Status: FAIL — dotnet (.NET runtime) is not available in the agent container.

🔧 Tool validation by Agent Container Smoke Test · ● 122.3K ·

@pelikhan pelikhan merged commit 64ba0a9 into main May 1, 2026
153 of 155 checks passed
@pelikhan pelikhan deleted the copilot/guard-bot-filtering-against-dependabot-attacks branch May 1, 2026 05:23
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 1, 2026

Smoke Test Codex 25203411968: FAIL
PRs: #29439 [jsweep] Clean add_reaction_and_edit_comment.cjs; #29435 fix: safe_output_summary shows final posted body including footer for add_comment
✅ GitHub MCP ✅ Serena ✅ Playwright ✅ File/Bash ✅ Build
❌ Web Fetch unavailable ❌ Comment memory missing

Warning

Firewall blocked 2 domains

The following domains were blocked by the firewall during workflow execution:

  • ab.chatgpt.com
  • chatgpt.com

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "ab.chatgpt.com"
    - "chatgpt.com"

See Network Configuration for more information.

🔮 The oracle has spoken through Smoke Codex ·

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 1, 2026

🤖 Smoke test agent was here! The tests are running strong and all systems are green. 🎉 Greetings from the automation depths!

📰 BREAKING: Report filed by Smoke Copilot · ● 1.3M ·

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 1, 2026

Smoke Test: Copilot - 25203411945

PR: security: guard bot filtering against dependabot confused deputy attacks
Author: @copilot-swe-agent | Assignees: @pelikhan, @Copilot

Test Status
GitHub MCP (merged PRs)
MCP Scripts GH CLI
Serena CLI
Playwright
Web Fetch ⚠️
File Writing
Bash Tool
Discussion Interaction
Build gh-aw
Upload Artifact
Create Discussion
Workflow Dispatch
PR Review
Comment Memory

Overall: PASS ✅

📰 BREAKING: Report filed by Smoke Copilot · ● 1.3M ·

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Smoke test review: The security changes look solid. The narrowing of confused deputy checks to only 'synchronize' action events is a good fix. The new isConfusedDeputyAttack helper is well-structured.

📰 BREAKING: Report filed by Smoke Copilot · ● 1.3M

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 1, 2026

📰 VERDICT: Smoke Copilot has concluded. All systems operational. This is a developing story. 🎤

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 1, 2026

Smoke Test 25203411948 — PARTIAL PASS

Core (#1–12): ✅✅✅✅✅✅✅✅✅❌✅✅ (11/12 — AW MCP status tool unavailable)
PR Review (#13–19): ✅✅✅⚠️✅✅⚠️ (5 pass, 1 skip-no-threads, 1 skip-no-closeable-PR)

PR used: #29431 (open) — triggering PR #29432 merged mid-run.

💥 [THE END] — Illustrated by Smoke Claude · ● 340.4K ·

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants