microsoft/TypeAgent

Public

mirrored from https://github.com/microsoft/TypeAgentAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
e8d2358d047ec0080cf22e45e5e6aa4a6ba261b3

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

.github/workflows/fix-dependabot-alerts.yml

318lines · modecode

1# Copyright (c) Microsoft Corporation.
2# Licensed under the MIT License.
3
4# Automatically remediate Dependabot security alerts by running the
5# fix-dependabot-alerts script, verifying the build for each fix,
6# and opening a pull request with the passing changes.
7#
8# REQUIRED: A repository secret named DEPENDABOT_PAT containing a
9# Personal Access Token (classic) with the `security_events` scope,
10# or a fine-grained token with "Dependabot alerts" read permission.
11# The default GITHUB_TOKEN cannot access the Dependabot alerts API.
12
13name: fix-dependabot-alerts
14
15on:
16 schedule:
17 # Run weekly on Monday at 9:00 UTC
18 - cron: "0 9 * * 1"
19 workflow_dispatch:
20 inputs:
21 dry-run:
22 description: "Dry run — analyse only, don't apply fixes"
23 type: boolean
24 default: false
25 auto-fix-args:
26 description: "Extra flags for the script (e.g. --apply-overrides=pkg1,pkg2)"
27 type: string
28 default: "--auto-fix"
29
30concurrency:
31 group: ${{ github.workflow }}
32 cancel-in-progress: true
33
34permissions:
35 contents: write
36 pull-requests: write
37 security-events: write
38
39env:
40 ELECTRON_CACHE: ${{ github.workspace }}/.cache/electron
41 ELECTRON_BUILDER_CACHE: ${{ github.workspace }}/.cache/electron-builder
42
43jobs:
44 fix-alerts:
45 runs-on: ubuntu-latest
46
47 steps:
48 - uses: actions/checkout@v4
49 with:
50 fetch-depth: 0
51
52 - uses: pnpm/action-setup@v4
53 name: Install pnpm
54 with:
55 package_json_file: ts/package.json
56
57 - uses: actions/setup-node@v4
58 with:
59 node-version: 22
60 cache: "pnpm"
61 cache-dependency-path: ts/pnpm-lock.yaml
62
63 - name: Install dependencies
64 working-directory: ts
65 run: pnpm install --frozen-lockfile --strict-peer-dependencies
66
67 # ── Analyse and fix alerts (bisect) ─────────────────────────────
68 #
69 # 1. Dry-run to discover fixable packages
70 # 2. Apply each package's fix individually
71 # 3. Build-check after each; rollback failures
72 # 4. Only passing fixes survive into the PR
73 - name: Analyse and fix alerts
74 id: fix
75 working-directory: ts
76 env:
77 # NOTE: GITHUB_TOKEN cannot access Dependabot alerts API (403).
78 # A PAT with security_events scope must be stored as DEPENDABOT_PAT.
79 GH_TOKEN: ${{ secrets.DEPENDABOT_PAT || secrets.GITHUB_TOKEN }}
80 run: |
81 # ── Step 1: Discover fixable packages ───────────────────────
82 echo "::group::Analysing alerts"
83 node tools/scripts/fix-dependabot-alerts.mjs --dry-run --json --skip-install > /tmp/dep-analysis.json 2>/tmp/dep-analysis.log || true
84
85 if ! jq -e '.summary' /tmp/dep-analysis.json > /dev/null 2>&1; then
86 echo "::error::Script produced no valid JSON output"
87 echo "--- stderr log ---"
88 cat /tmp/dep-analysis.log || true
89 echo "--- end stderr log ---"
90 echo "resolved=0" >> "$GITHUB_OUTPUT"
91 echo "blocked=0" >> "$GITHUB_OUTPUT"
92 echo "failed=0" >> "$GITHUB_OUTPUT"
93 echo "changes=false" >> "$GITHUB_OUTPUT"
94 echo "::endgroup::"
95 exit 0
96 fi
97
98 TOTAL_BLOCKED=$(jq '.summary.blocked' /tmp/dep-analysis.json)
99 TOTAL_NO_PATCH=$(jq '.summary.noPatch' /tmp/dep-analysis.json)
100
101 # Get the list of packages the script would resolve
102 FIXABLE=$(jq -r '.resolved[].package' /tmp/dep-analysis.json | sort -u)
103 FIXABLE_COUNT=$(echo "$FIXABLE" | grep -c . || true)
104 echo "Fixable packages ($FIXABLE_COUNT): $FIXABLE"
105 echo "::endgroup::"
106
107 if [ "$FIXABLE_COUNT" -eq 0 ]; then
108 echo "No fixable alerts"
109 echo "resolved=0" >> "$GITHUB_OUTPUT"
110 echo "blocked=$TOTAL_BLOCKED" >> "$GITHUB_OUTPUT"
111 echo "failed=0" >> "$GITHUB_OUTPUT"
112 echo "changes=false" >> "$GITHUB_OUTPUT"
113 exit 0
114 fi
115
116 if [ "${{ inputs.dry-run }}" == "true" ]; then
117 echo "resolved=$FIXABLE_COUNT" >> "$GITHUB_OUTPUT"
118 echo "blocked=$TOTAL_BLOCKED" >> "$GITHUB_OUTPUT"
119 echo "failed=0" >> "$GITHUB_OUTPUT"
120 echo "changes=false" >> "$GITHUB_OUTPUT"
121 exit 0
122 fi
123
124 # ── Step 2: Apply fixes one package at a time ───────────────
125 APPLIED=""
126 ROLLED_BACK=""
127 FLAGS="${{ inputs.auto-fix-args || '--auto-fix' }}"
128
129 for PKG in $FIXABLE; do
130 echo "::group::Fixing $PKG"
131
132 # Save a rollback point
133 cp package.json /tmp/pkg-backup.json
134 cp pnpm-lock.yaml /tmp/lock-backup.yaml 2>/dev/null || true
135
136 # Apply fix for this specific package — always use --auto-fix=$PKG
137 # regardless of other flags, so each package is targeted individually
138 read -r -a extra_args <<< "$FLAGS"
139 filtered_args=()
140 for arg in "${extra_args[@]}"; do
141 case "$arg" in
142 --auto-fix|--auto-fix=*)
143 ;;
144 *)
145 filtered_args+=("$arg")
146 ;;
147 esac
148 done
149 filtered_args+=("--auto-fix=$PKG")
150 filtered_args+=("--skip-install")
151 echo "Running: fix-dependabot-alerts.mjs ${filtered_args[*]}"
152 set +e
153 node tools/scripts/fix-dependabot-alerts.mjs "${filtered_args[@]}" 2>&1
154 fix_exit=$?
155 set -e
156
157 if ! git diff --quiet; then
158 # Changes were made — verify build
159 echo "Changes detected, verifying build..."
160 node tools/scripts/repo-policy-check.mjs --fix 2>/dev/null || true
161
162 set +e
163 pnpm run build 2>&1
164 build_exit=$?
165 set -e
166
167 if [ "$build_exit" -ne 0 ]; then
168 echo "::warning::Build failed after fixing $PKG — rolling back"
169 # Rollback: restore package.json and lockfile, reinstall
170 cp /tmp/pkg-backup.json package.json
171 cp /tmp/lock-backup.yaml pnpm-lock.yaml 2>/dev/null || true
172 pnpm install 2>&1 || true
173 git checkout -- . 2>/dev/null || true
174 ROLLED_BACK="$ROLLED_BACK $PKG"
175 else
176 echo "✅ $PKG fixed and build passed"
177 APPLIED="$APPLIED $PKG"
178 fi
179 else
180 echo "No changes for $PKG (may already be fixed)"
181 fi
182
183 echo "::endgroup::"
184 done
185
186 # ── Step 3: Report results ──────────────────────────────────
187 APPLIED_COUNT=$(echo "$APPLIED" | wc -w | tr -d ' ')
188 ROLLED_BACK_COUNT=$(echo "$ROLLED_BACK" | wc -w | tr -d ' ')
189
190 echo "resolved=$APPLIED_COUNT" >> "$GITHUB_OUTPUT"
191 echo "blocked=$TOTAL_BLOCKED" >> "$GITHUB_OUTPUT"
192 echo "failed=$ROLLED_BACK_COUNT" >> "$GITHUB_OUTPUT"
193 echo "applied_packages=$APPLIED" >> "$GITHUB_OUTPUT"
194 echo "rolled_back_packages=$ROLLED_BACK" >> "$GITHUB_OUTPUT"
195
196 cd ..
197 if git diff --quiet; then
198 echo "changes=false" >> "$GITHUB_OUTPUT"
199 else
200 echo "changes=true" >> "$GITHUB_OUTPUT"
201 echo "Files changed:"
202 git diff --stat
203 fi
204
205 # ── Final build + shell packaging verification ──────────────────
206 - name: Final build verification
207 if: ${{ steps.fix.outputs.changes == 'true' }}
208 id: build
209 working-directory: ts
210 run: |
211 node tools/scripts/repo-policy-check.mjs --fix || true
212 pnpm run build
213 echo "build_ok=true" >> "$GITHUB_OUTPUT"
214 env:
215 DEBUG_DEMB: true
216
217 - name: Package - shell
218 if: ${{ steps.build.outputs.build_ok == 'true' }}
219 id: shell
220 working-directory: ts
221 run: |
222 pnpm run shell:package
223 echo "shell_ok=true" >> "$GITHUB_OUTPUT"
224
225 # ── Create PR ───────────────────────────────────────────────────
226 - name: Create pull request
227 if: ${{ steps.fix.outputs.changes == 'true' && steps.build.outputs.build_ok == 'true' }}
228 env:
229 GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
230 run: |
231 BRANCH="automated/fix-dependabot-alerts-$(date +%Y%m%d)"
232
233 git config user.name "github-actions[bot]"
234 git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
235
236 git checkout -b "$BRANCH"
237 git add -A
238 git commit -m "fix: remediate Dependabot security alerts
239
240 Automated by fix-dependabot-alerts workflow.
241
242 Applied:${{ steps.fix.outputs.applied_packages }}
243 Rolled back:${{ steps.fix.outputs.rolled_back_packages || ' (none)' }}
244 Blocked: ${{ steps.fix.outputs.blocked }} package(s)
245 Shell packaging: ${{ steps.shell.outputs.shell_ok == 'true' && 'passed' || 'skipped' }}
246
247 Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>"
248
249 git push origin "$BRANCH"
250
251 # Build PR body from step outputs
252 APPLIED="${{ steps.fix.outputs.applied_packages }}"
253 ROLLED="${{ steps.fix.outputs.rolled_back_packages }}"
254 RESOLVED="${{ steps.fix.outputs.resolved }}"
255 BLOCKED="${{ steps.fix.outputs.blocked }}"
256 FAILED="${{ steps.fix.outputs.failed }}"
257
258 BODY=$(cat <<'PREOF'
259 ## Automated Dependabot Alert Remediation
260
261 This PR was automatically generated by the `fix-dependabot-alerts` workflow.
262 Each fix was applied individually and build-verified before inclusion.
263
264 ### Summary
265 PREOF
266 )
267
268 BODY="$BODY
269 - **Applied ($RESOLVED):**$APPLIED
270 - **Blocked:** $BLOCKED package(s) (require manual intervention)
271 - **Rolled back ($FAILED):**${ROLLED:- (none)}
272 - **Build:** ✅ Passed
273 - **Shell packaging:** ${{ steps.shell.outputs.shell_ok == 'true' && '✅ Passed' || '⚠️ Skipped' }}
274
275 ### How this works
276 1. Analyses all open Dependabot alerts
277 2. Applies each fix **individually** with build verification
278 3. Rolls back any fix that breaks the build
279 4. Only passing fixes are included in this PR
280
281 ### Review checklist
282 - [ ] Check that no breaking changes were introduced
283 - [ ] Verify rolled-back packages are investigated separately
284 - [ ] Run tests locally if concerned about specific packages"
285
286 gh pr create \
287 --base main \
288 --head "$BRANCH" \
289 --title "fix: remediate Dependabot security alerts ($(date +%Y-%m-%d))" \
290 --body "$BODY" \
291 --label "dependencies,security"
292
293 # ── Summary ─────────────────────────────────────────────────────
294 - name: Job summary
295 if: always()
296 run: |
297 echo "## Dependabot Alert Remediation" >> "$GITHUB_STEP_SUMMARY"
298 echo "" >> "$GITHUB_STEP_SUMMARY"
299 echo "| Metric | Count |" >> "$GITHUB_STEP_SUMMARY"
300 echo "|--------|-------|" >> "$GITHUB_STEP_SUMMARY"
301 echo "| Applied | ${{ steps.fix.outputs.resolved || '0' }} |" >> "$GITHUB_STEP_SUMMARY"
302 echo "| Blocked | ${{ steps.fix.outputs.blocked || '0' }} |" >> "$GITHUB_STEP_SUMMARY"
303 echo "| Rolled back | ${{ steps.fix.outputs.failed || '0' }} |" >> "$GITHUB_STEP_SUMMARY"
304 echo "" >> "$GITHUB_STEP_SUMMARY"
305 if [ -n "${{ steps.fix.outputs.applied_packages }}" ]; then
306 echo "**Applied:**${{ steps.fix.outputs.applied_packages }}" >> "$GITHUB_STEP_SUMMARY"
307 fi
308 if [ -n "${{ steps.fix.outputs.rolled_back_packages }}" ]; then
309 echo "**Rolled back:**${{ steps.fix.outputs.rolled_back_packages }}" >> "$GITHUB_STEP_SUMMARY"
310 fi
311 echo "" >> "$GITHUB_STEP_SUMMARY"
312 if [ "${{ steps.fix.outputs.changes }}" == "true" ]; then
313 echo "✅ Changes applied and PR created" >> "$GITHUB_STEP_SUMMARY"
314 elif [ "${{ inputs.dry-run }}" == "true" ]; then
315 echo "ℹ️ Dry run — no changes applied" >> "$GITHUB_STEP_SUMMARY"
316 else
317 echo "ℹ️ No fixable alerts found" >> "$GITHUB_STEP_SUMMARY"
318 fi
319