|
| 1 | +# SPDX-License-Identifier: PMPL-1.0-or-later |
| 2 | +# |
| 3 | +# dependabot-automerge.yml — enable GitHub's native auto-merge on |
| 4 | +# Dependabot pull requests that match a declared severity / ecosystem |
| 5 | +# policy. Pairs with `.github/dependabot.yml`'s |
| 6 | +# `open-pull-requests-limit: 0` + security-only pattern (see the |
| 7 | +# cargo block there). |
| 8 | +# |
| 9 | +# What this does: |
| 10 | +# - Triggers on every Dependabot PR. |
| 11 | +# - Reads the PR's update-type metadata via the dependabot/fetch-metadata |
| 12 | +# action (no free-text parsing). |
| 13 | +# - Requires CI to be green before merge (GitHub's auto-merge enforces |
| 14 | +# required status checks). |
| 15 | +# - Gates merge behind a severity+ecosystem policy table. Default is |
| 16 | +# low+medium security updates only. |
| 17 | +# |
| 18 | +# Why auto-merge on GitHub (not via a bot like rhodibot) is the right |
| 19 | +# layer: GitHub enforces branch protection + required checks natively, |
| 20 | +# and the PR author is already `dependabot[bot]`. Rhodibot doesn't need |
| 21 | +# to know anything about ecosystems — GitHub handles the merge mechanics |
| 22 | +# once we approve. |
| 23 | +# |
| 24 | +# Threat model: |
| 25 | +# - A compromised upstream package with a bogus security advisory |
| 26 | +# could propose a malicious version bump. Mitigation: require at |
| 27 | +# least one non-automated reviewer for HIGH+CRITICAL severity |
| 28 | +# (done below — we explicitly refuse to auto-approve those). |
| 29 | +# - A compromised Dependabot itself is an Akerlof claim-grounder |
| 30 | +# problem. Not in scope here; track under |
| 31 | +# `project_claim_grounders_dual_use_akerlof.md`. |
| 32 | +# |
| 33 | +# Dogfooding: this workflow template is itself subject to the same |
| 34 | +# Dependabot config via the github-actions ecosystem block, so SHA |
| 35 | +# bumps for dependabot/fetch-metadata flow through the same path. |
| 36 | + |
| 37 | +name: Dependabot Auto-Merge |
| 38 | + |
| 39 | +on: |
| 40 | + pull_request: |
| 41 | + types: [opened, reopened, synchronize] |
| 42 | + |
| 43 | +permissions: |
| 44 | + contents: write # needed to enable auto-merge |
| 45 | + pull-requests: write # needed to approve |
| 46 | + # NB: keep narrow — do NOT add secrets: read or id-token: write here. |
| 47 | + |
| 48 | +jobs: |
| 49 | + automerge: |
| 50 | + # Only run for PRs actually authored by Dependabot. |
| 51 | + if: github.actor == 'dependabot[bot]' && github.event.pull_request.user.login == 'dependabot[bot]' |
| 52 | + runs-on: ubuntu-latest |
| 53 | + |
| 54 | + steps: |
| 55 | + - name: Fetch Dependabot metadata |
| 56 | + id: meta |
| 57 | + uses: dependabot/fetch-metadata@dbb049abf0d677abbd7f7eee0375145b417fdd34 # v2.2.0 |
| 58 | + with: |
| 59 | + github-token: ${{ secrets.GITHUB_TOKEN }} |
| 60 | + |
| 61 | + # --- Policy gate ------------------------------------------------------- |
| 62 | + # Outputs from fetch-metadata we care about: |
| 63 | + # update-type → version-update:semver-{patch,minor,major} |
| 64 | + # dependency-type → direct:{development,production} | indirect |
| 65 | + # alert-state → AUTO_DISMISSED | DISMISSED | FIXED | OPEN |
| 66 | + # ghsa-id → GHSA-... if this is a security PR |
| 67 | + # --- Policy ------------------------------------------------------------- |
| 68 | + # AUTO-APPROVE + AUTO-MERGE when: |
| 69 | + # 1. This is a SECURITY update (ghsa-id present), AND |
| 70 | + # 2. Update is patch or minor, AND |
| 71 | + # 3. Severity ≤ moderate (Dependabot doesn't expose severity |
| 72 | + # directly in fetch-metadata; infer from the absence of |
| 73 | + # HIGH/CRITICAL labels added by Dependabot). |
| 74 | + # Otherwise: do nothing. Human reviews HIGH+CRITICAL security |
| 75 | + # updates and all non-security bumps. |
| 76 | + - name: Decide policy outcome |
| 77 | + id: policy |
| 78 | + env: |
| 79 | + GHSA_ID: ${{ steps.meta.outputs.ghsa-id }} |
| 80 | + UPDATE_TYPE: ${{ steps.meta.outputs.update-type }} |
| 81 | + PR_LABELS: ${{ toJson(github.event.pull_request.labels.*.name) }} |
| 82 | + run: | |
| 83 | + set -euo pipefail |
| 84 | +
|
| 85 | + is_security=false |
| 86 | + is_patch_or_minor=false |
| 87 | + is_high_or_critical=false |
| 88 | +
|
| 89 | + [ -n "$GHSA_ID" ] && is_security=true |
| 90 | + case "$UPDATE_TYPE" in |
| 91 | + version-update:semver-patch|version-update:semver-minor) |
| 92 | + is_patch_or_minor=true ;; |
| 93 | + esac |
| 94 | +
|
| 95 | + # Dependabot adds severity labels like "severity: high", |
| 96 | + # "severity: critical". Look for those in the PR labels JSON. |
| 97 | + if echo "$PR_LABELS" | grep -qiE '"(severity: (high|critical))"'; then |
| 98 | + is_high_or_critical=true |
| 99 | + fi |
| 100 | +
|
| 101 | + if $is_security && $is_patch_or_minor && ! $is_high_or_critical; then |
| 102 | + echo "action=automerge" >> "$GITHUB_OUTPUT" |
| 103 | + else |
| 104 | + echo "action=skip" >> "$GITHUB_OUTPUT" |
| 105 | + fi |
| 106 | + echo "security=$is_security" >> "$GITHUB_OUTPUT" |
| 107 | + echo "update_type=$UPDATE_TYPE" >> "$GITHUB_OUTPUT" |
| 108 | + echo "ghsa=$GHSA_ID" >> "$GITHUB_OUTPUT" |
| 109 | +
|
| 110 | + - name: Approve PR (if policy allows) |
| 111 | + if: steps.policy.outputs.action == 'automerge' |
| 112 | + env: |
| 113 | + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
| 114 | + PR_URL: ${{ github.event.pull_request.html_url }} |
| 115 | + run: | |
| 116 | + gh pr review --approve "$PR_URL" \ |
| 117 | + --body "Auto-approving Dependabot security update (${{ steps.policy.outputs.ghsa }}, ${{ steps.policy.outputs.update_type }}). Policy: low/moderate security patches/minors only." |
| 118 | +
|
| 119 | + - name: Enable auto-merge (if policy allows) |
| 120 | + if: steps.policy.outputs.action == 'automerge' |
| 121 | + env: |
| 122 | + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
| 123 | + PR_URL: ${{ github.event.pull_request.html_url }} |
| 124 | + run: | |
| 125 | + gh pr merge --auto --squash "$PR_URL" |
| 126 | +
|
| 127 | + - name: Write decision to step summary |
| 128 | + env: |
| 129 | + ACTION: ${{ steps.policy.outputs.action }} |
| 130 | + IS_SECURITY: ${{ steps.policy.outputs.security }} |
| 131 | + UPDATE_TYPE: ${{ steps.policy.outputs.update_type }} |
| 132 | + GHSA: ${{ steps.policy.outputs.ghsa }} |
| 133 | + run: | |
| 134 | + { |
| 135 | + echo "## Dependabot Auto-Merge Decision" |
| 136 | + echo "" |
| 137 | + echo "| Field | Value |" |
| 138 | + echo "|-------|-------|" |
| 139 | + echo "| Policy action | \`$ACTION\` |" |
| 140 | + echo "| Security update | \`$IS_SECURITY\` |" |
| 141 | + echo "| Update type | \`$UPDATE_TYPE\` |" |
| 142 | + echo "| GHSA ID | \`${GHSA:-n/a}\` |" |
| 143 | + } >> "$GITHUB_STEP_SUMMARY" |
0 commit comments