Skip to content

ci: auto-merge Dependabot security PRs via Automattic/dependabot-auto-merge-action #178

Description

@alchemydc

Summary

Adopt Automattic/dependabot-auto-merge-action so Dependabot security PRs merge themselves once they clear a set of gates, instead of waiting on a human to notice them.

Scope check before anyone starts: this action only acts on PRs that fix a known advisory — gate 1 requires a GHSA ID, falling back to the Dependabot Alerts API for indirect dependencies. It does not touch routine version bumps. The toil documented in docs/dependabot.md § Routine merge workflow — grouped minor/patch PRs, @dependabot rebase churn, lockfile PRs going BEHIND one at a time — is unaffected by this. If reducing that churn is also wanted, it needs a separate mechanism, and gh pr merge --auto --merge (already documented there) is the cheap version.

How it works

Four sequential gates, each configurable:

  1. Security advisory — PR must fix a known vulnerability (GHSA ID required).
  2. CVSS severity — score must meet cvss-threshold (default 7.0). A score of zero, missing, or out of range routes to human review rather than merging.
  3. Compatibility score — direct dependencies only, default minimum 80%. Indirect dependencies skip this gate because fetch-metadata cannot score them.
  4. Age — PR must have been open age-days (default 7) before the scheduled job merges it.

A security-fast-track label bypasses all four and posts an audit comment naming who applied it. PRs failing a gate get sirt-review-required; PRs passing but waiting on age get auto-merge-pending.

What it would actually do here, today

All 9 open Dependabot alerts (7 high, 2 medium) are indirect/transitive and all have a patched version available. Against the default cvss-threshold: 7.0:

  • Would clear the CVSS gate: js-yaml (7.5, two alerts), brace-expansion (7.5, two alerts) — 4 of 9.
  • Would route to human review as below threshold: nanoid (5.9), mysql2 (5.9) — 2 of 9.
  • Would route to human review as unscored: mysql2 (0.0), deepmerge-ts (0.0), postcss (0.0) — 3 of 9. GitHub returns 0.0 for unscored advisories and the action treats that as needs-a-human.

Since every one of these is indirect, gate 3 is skipped and gate 1 leans entirely on the Alerts API fallback, which is what makes the security-events: read permission load-bearing rather than optional.

Precondition worth resolving first: there are currently zero open Dependabot security PRs despite those 9 fixable alerts — the 4 open bot PRs (#171-#174) are all routine bumps. An auto-merger for security PRs is worth nothing if security PRs are never opened. Find out why first: these are transitive under pnpm, and Dependabot may be unable to bump them without the parent package moving. That investigation may turn out to be the higher-value half of this issue.

Prerequisites

All already satisfied, nothing to change:

  • Auto-merge enabled on the repo (allow_auto_merge: true).
  • At least one required status check on the default branch — web, via existing branch protection on main.
  • Dependabot security updates enabled.

Proposed workflow

New file .github/workflows/dependabot-auto-merge.yml. Pin to a SHA, not a floating tag — the action's own README says so, and it runs with write permissions:

name: Dependabot auto-merge

on:
  pull_request_target:
    types: [opened, synchronize, reopened, labeled]
  schedule:
    - cron: '0 9 * * *'

permissions:
  pull-requests: write
  contents: write
  security-events: read

jobs:
  dependabot-auto-merge:
    uses: Automattic/dependabot-auto-merge-action/.github/workflows/dependabot-auto-merge.yml@8143c95d871e96dc12cb448aa1dde9c2abae694e # v1.5
    permissions:
      pull-requests: write
      contents: write
      security-events: read
    with:
      event-name: ${{ github.event_name }}
      merge-method: merge

merge-method: merge rather than the default squash, to match how Dependabot PRs are merged here today.

Note this is the repo's first pull_request_target workflow — it runs with write access to the base repo against PR head refs. The action's stated mitigation is that it acts only on app/dependabot as author. It is also the first workflow needing contents: write; ci.yml is contents: read. Both are worth a deliberate look rather than a rubber stamp, and the github-actions Dependabot ecosystem is already configured, so the pinned SHA will get its own update PRs.

Decisions to make

  • cvss-threshold: default 7.0 leaves 5 of the current 9 alerts to a human. Lowering it to around 5.0 would sweep in the two 5.9s, but unscored 0.0 advisories always need a human regardless.
  • age-days: default is 7. Note dependabot.yml already sets cooldown.default-days: 3 and pnpm-workspace.yaml sets minimumReleaseAge — but security updates ignore cooldown, so the action's age gate is the only aging applied to exactly these PRs. Worth picking deliberately rather than inheriting 7.
  • Interaction with strict status checks: docs/dependabot.md notes a lockfile PR goes BEHIND whenever another PR merges, and strict checks block it until rebased. A scheduled merge job will hit the same wall; decide whether it should trigger @dependabot rebase or just leave it for the next run.

Acceptance

A Dependabot security PR fixing a high-severity advisory gets labeled auto-merge-pending, and merges without human action once CI is green and the age gate passes. One below the CVSS threshold gets sirt-review-required and is left alone. Routine (non-security) Dependabot PRs are untouched, and no non-Dependabot PR is ever labeled or merged by this workflow.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions