-
Notifications
You must be signed in to change notification settings - Fork 56
279 lines (247 loc) · 12 KB
/
dependabot-autofix.yml
File metadata and controls
279 lines (247 loc) · 12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
#
# Dependabot Auto-fix
# ------------------------------------------------------------------------
# Runs the `fix-dependabot-alerts` skill via GitHub Copilot CLI on a weekly
# schedule (and on-demand). Copilot enumerates open Dependabot alerts,
# applies fixes following `.claude/skills/fix-dependabot-alerts/SKILL.md`,
# runs the build + tests, and opens a single consolidated PR.
#
# Required secrets
# ------------------------------------------------------------------------
# POWER_PAGES_PUBLIC_GITHUB_APP_PRIVATE_KEY
# Private key of the existing GitHub App (app-id 2740120) that is
# already used by loc-update.yml / translations-export.yml. The App
# installation on this repo must have:
# - Contents: Read/Write
# - Pull requests: Read/Write
# Commits and the PR are authored as github-actions[bot] via this App.
#
# COPILOT_CLI_PAT
# Fine-grained PAT from a user with a Copilot license. Needed because
# GitHub App tokens cannot authenticate Copilot CLI (Copilot licenses
# are per-user). Required permissions:
# - Account: Copilot Requests: Read
# - Repository: Dependabot alerts: Read (so `gh api .../dependabot/alerts` works)
# The org-level "Copilot CLI" policy must be enabled for this user.
# ------------------------------------------------------------------------
name: Dependabot Auto-fix
on:
schedule:
# Weekly, Mondays 06:00 UTC
- cron: '0 6 * * 1'
workflow_dispatch:
permissions:
contents: read
concurrency:
group: dependabot-autofix
cancel-in-progress: false
jobs:
autofix:
name: Copilot CLI Dependabot Auto-fix
runs-on: ubuntu-latest
timeout-minutes: 45
permissions:
contents: write
pull-requests: write
steps:
- name: Generate GitHub App Token
id: app-token
uses: actions/create-github-app-token@v1
with:
app-id: 2740120
private-key: ${{ secrets.POWER_PAGES_PUBLIC_GITHUB_APP_PRIVATE_KEY }}
- name: Checkout Repository
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ steps.app-token.outputs.token }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Install GitHub Copilot CLI
run: npm install -g @github/copilot
- name: Configure git identity
run: |
git config --local user.name "github-actions[bot]"
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
- name: Create working branch
id: branch
run: |
BRANCH="copilot/dependabot-autofix-${{ github.run_id }}"
git checkout -b "$BRANCH"
echo "name=$BRANCH" >> "$GITHUB_OUTPUT"
- name: Run Copilot CLI (fix-dependabot-alerts)
id: copilot
timeout-minutes: 30
env:
# Copilot CLI auth + Dependabot alerts read before invoking Copilot.
COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_CLI_PAT }}
# App token used by `gh pr create` and `git push` so the PR
# and commits are authored by github-actions[bot].
GH_TOKEN: ${{ steps.app-token.outputs.token }}
REPO: ${{ github.repository }}
BRANCH: ${{ steps.branch.outputs.name }}
run: |
set -euo pipefail
if [ -z "${COPILOT_GITHUB_TOKEN:-}" ]; then
echo "COPILOT_CLI_PAT secret is not configured or is unavailable to this workflow."
exit 1
fi
GH_TOKEN="$COPILOT_GITHUB_TOKEN" gh api \
"repos/${REPO}/dependabot/alerts?state=open&per_page=100" \
--paginate \
--jq '.[] | {number, ghsa: .security_advisory.ghsa_id, pkg: .dependency.package.name, ecosystem: .dependency.package.ecosystem, severity: .security_vulnerability.severity, scope: .dependency.scope, manifest: .dependency.manifest_path, summary: .security_advisory.summary, vulnerable: .security_vulnerability.vulnerable_version_range, patched: .security_vulnerability.first_patched_version.identifier}' \
> alerts.json
if [ ! -s alerts.json ]; then
echo "NO_OPEN_ALERTS" | tee copilot-output.txt
exit 0
fi
# Inline the skill so behavior is identical even if Copilot CLI
# on the runner does not auto-load .claude/skills/.
SKILL_BODY="$(cat .claude/skills/fix-dependabot-alerts/SKILL.md)"
PROMPT=$(cat <<PROMPT_EOF
You are running inside a GitHub Actions job. Follow the skill
below to fix all currently open Dependabot alerts for the
repository \`${REPO}\` in a SINGLE consolidated pull request.
Environment already set up for you:
- Repository is checked out at the current working directory.
- A working branch named \`${BRANCH}\` is already created and checked out.
- \`npm ci\` has already installed dependencies.
- \`git\` is configured as github-actions[bot].
- \`alerts.json\` was generated from \`gh api repos/${REPO}/dependabot/alerts?state=open\`
before Copilot was invoked. Use it as the source of truth.
- \`GH_TOKEN\` is a GitHub App token with Contents and Pull requests
write access. Use it (it is already in env) for \`git push\` and
\`gh pr create\`.
Mandatory rules:
1. ALERT ENUMERATION — source of truth.
Read \`alerts.json\`, which was generated before Copilot was invoked.
The PR description and the count of "alerts fixed" MUST
be built one-to-one from \`alerts.json\`. Do NOT count
transitive-path occurrences of the same alert number as
separate alerts. Do NOT inflate counts based on how many
\`node_modules/**\` entries appear in \`package-lock.json\`.
2. ZERO ALERTS — exit cleanly.
If step 1 returns an empty list, do not commit, do not
push, do not open a PR. Print "NO_OPEN_ALERTS" and exit.
3. MINIMUM VIABLE FIX — prefer the smallest blast radius.
For each alert, pick the least-invasive strategy in this
strict preference order and record your choice in the PR
body:
a. Transitive dependency → add/extend an entry in the
\`overrides\` block of \`package.json\` pinning the
vulnerable package to the first patched version.
This is the preferred fix.
b. Direct dependency → bump that specific dependency in
\`package.json\` to the first patched version.
c. Only if (a) and (b) are infeasible (e.g. override
breaks peer-dep resolution, or a parent package
pins the vulnerable version), escalate to bumping a
parent package. In that case, justify the escalation
in the PR body and keep the parent bump as narrow
as possible (patch > minor > major).
Never run \`npm audit fix\` or \`npm audit fix --force\` as
a shortcut — they produce sprawling diffs with unrelated
upgrades. Use \`npm install\` / \`npm update\` targeted at
the specific package(s) you are fixing.
4. DIFF DISCIPLINE.
After the lockfile is regenerated, run:
git diff --stat origin/main
If any of the following are true, STOP, revert with
\`git checkout -- package.json package-lock.json\`, and
try a narrower strategy from rule 3:
- package.json changed a dependency that is NOT on the
alert list (except for adding entries to \`overrides\`).
- Any dependency bumped a MAJOR version that is not
strictly required to reach the first patched version.
5. VERIFY.
Run \`npm run build\` AND \`npm test\`. If either fails,
do NOT push and do NOT open a PR. Print
"BUILD_OR_TEST_FAILED" and exit non-zero.
6. OPEN THE PR (verification passed).
- \`git push -u origin ${BRANCH}\` (uses \$GH_TOKEN).
- Build the PR title and body strictly from \`alerts.json\`:
* Title: \`chore(deps): fix N open Dependabot alert(s)\`
where N = number of distinct entries in \`alerts.json\`.
* Body format:
## Summary
Fixes N open Dependabot alert(s) as enumerated by
\`gh api repos/${REPO}/dependabot/alerts?state=open\`.
## Alerts addressed
For each entry in \`alerts.json\`, one bullet:
- \`<pkg>\` (#<number>, <severity>, <ghsa>) —
vulnerable \`<vulnerable>\` → patched \`<patched>\`.
Strategy: override | direct-bump | parent-bump.
Rationale if parent-bump: <one line>.
## Collateral changes
List any other dep bumps that \`npm install\` cascaded
(e.g. peer updates) and explain why they were
necessary. If there are none, say "None".
## Verification
- \`npm run build\`: PASS
- \`npm test\`: PASS
- Open the PR with \`gh pr create --base main --head ${BRANCH}\`
using that title and body.
- Print the PR URL.
7. COMMIT / AUTHORSHIP HYGIENE.
Never manually edit integrity hashes in package-lock.json
(see skill). Use \`npm install\`, \`npm update\`, or a clean
reinstall to let npm compute them. Do not add any
\`Co-authored-by:\` trailer.
8. PERMISSIONS.
Never use \`--allow-all\` semantics; stick to the tools you
already have permission to use.
----- SKILL START -----
${SKILL_BODY}
----- SKILL END -----
PROMPT_EOF
)
copilot \
-p "$PROMPT" \
--model claude-sonnet-4.6 \
-s \
--no-ask-user \
--allow-tool 'shell(npm:*), shell(npx:*), shell(gh:*), shell(git:*), shell(node:*), write, read' \
--secret-env-vars 'GH_TOKEN' \
--share ./copilot-session.md \
| tee copilot-output.txt
- name: Summarize run
if: always()
run: |
{
echo "## Dependabot Auto-fix"
echo ""
echo "- Branch: \`${{ steps.branch.outputs.name }}\`"
if [ -f copilot-output.txt ]; then
if grep -q "NO_OPEN_ALERTS" copilot-output.txt; then
echo "- Result: no open Dependabot alerts; no PR opened."
elif grep -q "BUILD_OR_TEST_FAILED" copilot-output.txt; then
echo "- Result: build or tests failed after fixes; no PR opened."
else
PR_URL=$(grep -Eo 'https://github.com/[^ ]+/pull/[0-9]+' copilot-output.txt | head -n1 || true)
if [ -n "$PR_URL" ]; then
echo "- PR: $PR_URL"
else
echo "- Result: see Copilot session transcript artifact."
fi
fi
else
echo "- Result: Copilot step did not produce output."
fi
} >> "$GITHUB_STEP_SUMMARY"
- name: Upload Copilot session transcript
if: always()
uses: actions/upload-artifact@v4
with:
name: copilot-session-${{ github.run_id }}
path: |
copilot-session.md
copilot-output.txt
retention-days: 14
if-no-files-found: ignore