You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Dependabot opens daily PRs for npm dev dependencies (plus grouped weekly PRs for AWS SDK/CDK). Every one of these requires a maintainer to approve, wait for checks, and merge by hand — and because branch protection enforces strict up-to-date checks (strict: true), each merge invalidates the other open Dependabot PRs, triggering a rebase/re-approve cycle across all of them. This is a recurring, low-value drain on maintainer time.
This issue proposes enabling GitHub native auto-merge for a narrowly-scoped subset of Dependabot PRs — direct dev dependencies, non-major updates only — via a hardened workflow that verifies the PR's provenance and commit contents before approving. It also closes two gaps in our required status checks that exist today and become more relevant once human review is removed from these merges.
Why is this needed?
Maintainer toil. Dev-dependency bumps (e.g. lint-staged, typedoc, the vitest/typescript groups) are mechanical: CI either passes or it doesn't, and the human "review" is a rubber stamp. Automating them frees maintainer time for actual reviews.
Current state (verified via API on 2026-07-21):
Branch protection on main: 1 required approving review, dismiss_stale_reviews: true, require_last_push_approval: true, enforce_admins: true, linear history, squash-only.
Required status checks are only: Acknowledgment Check, Related Issue Check, Semantic PR Title Check, CodeQL.
Gap 1: the ~35 lint/unit-test matrix contexts from pr-run-linting-check-and-unit-tests.yml are required=false (verified on chore(deps-dev): bump lint-staged from 17.0.8 to 17.1.0 #5483). Human merges catch red tests informally; auto-merge only waits on required checks, so a dev-dep bump that breaks the build would merge green.
Gap 2:dependency-review (the workflow that blocks known-vulnerable package versions — our main supply-chain gate for dependency PRs) is also required=false. Its own header comment says it only blocks "if the workflow run is marked as required".
allow_auto_merge is currently false at the repo level; GitHub Actions is permitted to approve PRs (can_approve_pull_request_reviews: true).
Fixing the two required-check gaps is worthwhile independently of auto-merge.
Which area does this relate to?
Automation, Governance
Solution
Threat model
The primary risk is not "Dependabot proposes a bad version" — that risk exists with manual merging too and is mitigated by dependency-review and CI. The risk auto-merge introduces is the automation approving code no human reviewed:
Non-Dependabot PR gets auto-merged. An attacker (or a bug) causes the workflow to approve an arbitrary PR.
Poisoned Dependabot PR. A Dependabot PR is open; an account with write access (possibly compromised) pushes an extra commit onto the dependabot/* branch. The PR author is still dependabot[bot], so an author-only check passes, and the automation merges unreviewed code.
Race: the malicious commit lands after the workflow has approved and enabled auto-merge, but before checks complete.
Compromised upstream package: a legitimately-published but malicious dev-dep release.
Design: layered controls
Workflow gating (threat 1) — the job runs only when all of the following hold: PR author is dependabot[bot], triggering actor is dependabot[bot], head branch is in this repo (not a fork), branch name starts with dependabot/, and repository is aws-powertools/powertools-lambda-typescript. Additionally dependabot/fetch-metadata itself hard-fails on non-Dependabot events. The dependabot[bot] app identity cannot be spoofed.
Commit content verification (threat 2) — before approving, the workflow lists every commit on the PR via the API and requires each one to be (a) authored by dependabot[bot] and (b) verification.verified == true, i.e. signed by GitHub's key. Locally forging user.name = "dependabot[bot]" cannot produce a GitHub-verified signature. One foreign or unsigned commit → hard fail, no approval, PR falls back to normal human review.
Branch protection closes the race (threat 3) — existing settings do the heavy lifting:
dismiss_stale_reviews: true: any push after approval instantly dismisses the bot's approval, blocking auto-merge again.
The push re-triggers the workflow, which now fails either the actor condition (a human pushed) or commit verification — so no re-approval is issued.
require_last_push_approval: true: the last pusher can never satisfy the review requirement themselves.
Net effect: a poisoned PR doesn't merge; it gets stuck waiting for a human. Auto-merge enablement is a request that branch protection re-validates continuously, not a one-time decision.
Scope limits (threat 4 + blast radius):
Only dependency-type == 'direct:development' and update-type != 'version-update:semver-major'. Production dependencies and majors always get human review. For grouped PRs, fetch-metadata reports direct:development only when every dependency in the group is a dev dependency, so mixed groups fall through to manual review.
Making dependency-review required blocks known-vulnerable/malicious versions regardless of who merges. Optionally, a Dependabot cooldown (e.g. 3–5 days) lets the ecosystem surface bad releases before we ingest them.
Workflow hardening:permissions: {} at workflow level with contents: write + pull-requests: write granted only to the job; no actions/checkout — the workflow never fetches or executes PR code; all actions pinned to commit SHAs per secure-workflows.yml policy.
Required-checks fix
The unit-test matrix fans out into ~35 contexts; requiring each by name is brittle (renaming a package or bumping a Node version would silently block merges). Instead, add one aggregating job to reusable-run-linting-check-and-unit-tests.yml:
unit-tests-complete:
runs-on: ubuntu-latestneeds: [code-quality, check-examples, check-layer-publisher, check-docs-snippets, check-docs]if: always()steps:
- name: All checks passedrun: | if [ "${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}" = "true" ]; then exit 1 fi
if: always() + explicit failure check prevents a skipped aggregate job from satisfying the required check. Both pr-run-linting-check-and-unit-tests.yml and dependency-review.yml run on all PRs with no paths: filter (verified), so making them required cannot strand a PR whose checks never report. While in there, add reopened to the PR workflow's trigger types so reopened PRs re-report the required context. The reusable workflow is also called by make-release.yml; the aggregate job is harmless there.
Auto-merge workflow (sketch)
name: Dependabot auto-mergeon: pull_requestpermissions: {}jobs:
automerge:
runs-on: ubuntu-latestpermissions:
contents: writepull-requests: writeif: >- github.repository == 'aws-powertools/powertools-lambda-typescript' && github.event.pull_request.user.login == 'dependabot[bot]' && github.actor == 'dependabot[bot]' && github.event.pull_request.head.repo.full_name == github.repository && startsWith(github.event.pull_request.head.ref, 'dependabot/')steps:
- name: Fetch Dependabot metadataid: metadatauses: dependabot/fetch-metadata@<pinned-sha> # v2.xwith:
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Verify every commit is authored by Dependabot and signed by GitHubenv:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}PR_NUMBER: ${{ github.event.pull_request.number }}run: | set -euo pipefail gh api "repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}/commits" --paginate \ --jq '.[] | [(.author.login // "unknown"), (.commit.verification.verified | tostring)] | @tsv' \ | while IFS=$'\t' read -r login verified; do if [ "$login" != "dependabot[bot]" ] || [ "$verified" != "true" ]; then echo "::error::Commit not authored+signed by dependabot[bot] (author: ${login}, verified: ${verified})" exit 1 fi done
- name: Approve and enable auto-merge (dev dependencies, non-major only)if: >- steps.metadata.outputs.dependency-type == 'direct:development' && steps.metadata.outputs.update-type != 'version-update:semver-major'env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}PR_URL: ${{ github.event.pull_request.html_url }}run: | gh pr review --approve "$PR_URL" gh pr merge --auto --squash "$PR_URL"
Because the workflow triggers on synchronize, it automatically re-approves after every legitimate Dependabot rebase — eliminating the dismiss/re-approve churn caused by strict: true + dismiss_stale_reviews.
Rollout plan
Order matters — required checks must exist before auto-merge is enabled:
Add unit-tests-complete aggregate job + reopened trigger type (PR).
Wait for one PR run so the new context is reported.
Add run-unit-tests / unit-tests-complete and dependency-review to required status checks (repo admin).
Enable allow_auto_merge in repo settings (repo admin).
Land dependabot-auto-merge.yml (PR), with fetch-metadata pinned to the current release SHA.
Observe the next few Dependabot dev-dep PRs merge unattended; confirm prod-dep and grouped SDK PRs still require manual review.
Steps 1–3 are valuable even if auto-merge is ultimately rejected.
Residual risk
A compromised upstream dev-dependency release that passes CI and dependency-review would auto-merge. This risk is materially unchanged from today: a human approving a routine dev-dep bump does not audit package tarballs. Mitigations: dependency-review as a required check (this proposal), optional Dependabot cooldown, and the fact that dev dependencies do not ship in published packages or the Lambda layer.
Summary
Dependabot opens daily PRs for npm dev dependencies (plus grouped weekly PRs for AWS SDK/CDK). Every one of these requires a maintainer to approve, wait for checks, and merge by hand — and because branch protection enforces strict up-to-date checks (
strict: true), each merge invalidates the other open Dependabot PRs, triggering a rebase/re-approve cycle across all of them. This is a recurring, low-value drain on maintainer time.This issue proposes enabling GitHub native auto-merge for a narrowly-scoped subset of Dependabot PRs — direct dev dependencies, non-major updates only — via a hardened workflow that verifies the PR's provenance and commit contents before approving. It also closes two gaps in our required status checks that exist today and become more relevant once human review is removed from these merges.
Why is this needed?
Maintainer toil. Dev-dependency bumps (e.g.
lint-staged,typedoc, thevitest/typescriptgroups) are mechanical: CI either passes or it doesn't, and the human "review" is a rubber stamp. Automating them frees maintainer time for actual reviews.Current state (verified via API on 2026-07-21):
main: 1 required approving review,dismiss_stale_reviews: true,require_last_push_approval: true,enforce_admins: true, linear history, squash-only.Acknowledgment Check,Related Issue Check,Semantic PR Title Check,CodeQL.pr-run-linting-check-and-unit-tests.ymlarerequired=false(verified on chore(deps-dev): bump lint-staged from 17.0.8 to 17.1.0 #5483). Human merges catch red tests informally; auto-merge only waits on required checks, so a dev-dep bump that breaks the build would merge green.dependency-review(the workflow that blocks known-vulnerable package versions — our main supply-chain gate for dependency PRs) is alsorequired=false. Its own header comment says it only blocks "if the workflow run is marked as required".allow_auto_mergeis currentlyfalseat the repo level; GitHub Actions is permitted to approve PRs (can_approve_pull_request_reviews: true).Fixing the two required-check gaps is worthwhile independently of auto-merge.
Which area does this relate to?
Automation, Governance
Solution
Threat model
The primary risk is not "Dependabot proposes a bad version" — that risk exists with manual merging too and is mitigated by dependency-review and CI. The risk auto-merge introduces is the automation approving code no human reviewed:
dependabot/*branch. The PR author is stilldependabot[bot], so an author-only check passes, and the automation merges unreviewed code.Design: layered controls
Workflow gating (threat 1) — the job runs only when all of the following hold: PR author is
dependabot[bot], triggering actor isdependabot[bot], head branch is in this repo (not a fork), branch name starts withdependabot/, and repository isaws-powertools/powertools-lambda-typescript. Additionallydependabot/fetch-metadataitself hard-fails on non-Dependabot events. Thedependabot[bot]app identity cannot be spoofed.Commit content verification (threat 2) — before approving, the workflow lists every commit on the PR via the API and requires each one to be (a) authored by
dependabot[bot]and (b)verification.verified == true, i.e. signed by GitHub's key. Locally forginguser.name = "dependabot[bot]"cannot produce a GitHub-verified signature. One foreign or unsigned commit → hard fail, no approval, PR falls back to normal human review.Branch protection closes the race (threat 3) — existing settings do the heavy lifting:
dismiss_stale_reviews: true: any push after approval instantly dismisses the bot's approval, blocking auto-merge again.require_last_push_approval: true: the last pusher can never satisfy the review requirement themselves.Net effect: a poisoned PR doesn't merge; it gets stuck waiting for a human. Auto-merge enablement is a request that branch protection re-validates continuously, not a one-time decision.
Scope limits (threat 4 + blast radius):
dependency-type == 'direct:development'andupdate-type != 'version-update:semver-major'. Production dependencies and majors always get human review. For grouped PRs, fetch-metadata reportsdirect:developmentonly when every dependency in the group is a dev dependency, so mixed groups fall through to manual review.dependency-reviewrequired blocks known-vulnerable/malicious versions regardless of who merges. Optionally, a Dependabotcooldown(e.g. 3–5 days) lets the ecosystem surface bad releases before we ingest them.Workflow hardening:
permissions: {}at workflow level withcontents: write+pull-requests: writegranted only to the job; noactions/checkout— the workflow never fetches or executes PR code; all actions pinned to commit SHAs persecure-workflows.ymlpolicy.Required-checks fix
The unit-test matrix fans out into ~35 contexts; requiring each by name is brittle (renaming a package or bumping a Node version would silently block merges). Instead, add one aggregating job to
reusable-run-linting-check-and-unit-tests.yml:if: always()+ explicit failure check prevents a skipped aggregate job from satisfying the required check. Bothpr-run-linting-check-and-unit-tests.ymlanddependency-review.ymlrun on all PRs with nopaths:filter (verified), so making them required cannot strand a PR whose checks never report. While in there, addreopenedto the PR workflow's trigger types so reopened PRs re-report the required context. The reusable workflow is also called bymake-release.yml; the aggregate job is harmless there.Auto-merge workflow (sketch)
Because the workflow triggers on
synchronize, it automatically re-approves after every legitimate Dependabot rebase — eliminating the dismiss/re-approve churn caused bystrict: true+dismiss_stale_reviews.Rollout plan
Order matters — required checks must exist before auto-merge is enabled:
unit-tests-completeaggregate job +reopenedtrigger type (PR).run-unit-tests / unit-tests-completeanddependency-reviewto required status checks (repo admin).allow_auto_mergein repo settings (repo admin).dependabot-auto-merge.yml(PR), withfetch-metadatapinned to the current release SHA.Steps 1–3 are valuable even if auto-merge is ultimately rejected.
Residual risk
A compromised upstream dev-dependency release that passes CI and dependency-review would auto-merge. This risk is materially unchanged from today: a human approving a routine dev-dep bump does not audit package tarballs. Mitigations: dependency-review as a required check (this proposal), optional Dependabot
cooldown, and the fact that dev dependencies do not ship in published packages or the Lambda layer.Acknowledgment
Future readers
Please react with 👍 and your use case to help us understand customer demand.