Skip to content

Commit d6ecbc9

Browse files
committed
ci: split preview-publish job, SHA-pin third-party actions, narrow dependabot auto-merge to non-production deps
1 parent ec5b8a1 commit d6ecbc9

7 files changed

Lines changed: 107 additions & 28 deletions

File tree

.github/workflows/backport.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ jobs:
3434
fetch-depth: 0
3535

3636
- name: Create backport PRs
37-
uses: korthout/backport-action@v4
37+
uses: korthout/backport-action@2e830a1d0b8269505846ddd407a70876913ad1f8 # v4
3838
with:
3939
# AUTOMATION_TOKEN (a PAT/App token) lets the required `build` check
4040
# run on the created PR; pushes made with the default GITHUB_TOKEN do

.github/workflows/ci.yml

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ jobs:
1515
runs-on: ubuntu-latest
1616
permissions:
1717
contents: read
18-
packages: write
1918
steps:
2019
- uses: actions/checkout@v7
2120
with:
2221
fetch-depth: 0
22+
persist-credentials: false
2323

2424
- uses: actions/setup-dotnet@v5
2525

@@ -56,14 +56,16 @@ jobs:
5656
name: coverage-report
5757
path: ./CoverageReport
5858

59+
# Uses gh api directly instead of a third-party gist-deploy action -- one
60+
# less individual-maintainer action holding a token in its supply chain.
5961
- name: Publish coverage badge to Gist
6062
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
61-
uses: exuanbo/actions-deploy-gist@v1
62-
with:
63-
token: ${{ secrets.GIST_TOKEN }}
64-
gist_id: 2852f1400e27493b185559c76b38e9b7
65-
file_path: ./CoverageReport/badge_combined.svg
66-
file_type: binary
63+
env:
64+
GH_TOKEN: ${{ secrets.GIST_TOKEN }}
65+
run: |
66+
jq -n --rawfile content ./CoverageReport/badge_combined.svg \
67+
'{files: {"badge_combined.svg": {content: $content}}}' \
68+
| gh api --method PATCH /gists/2852f1400e27493b185559c76b38e9b7 --input -
6769
6870
# Preview (prerelease) packages. main is not a public release ref
6971
# (version.json), so every CI build carries a prerelease suffix. PRs pack
@@ -83,8 +85,41 @@ jobs:
8385
name: nuget-packages
8486
path: ./artifacts/*
8587

86-
- name: Publish preview to GitHub Packages
88+
# Hands packages to the publish-preview job below, which carries
89+
# packages: write instead of this job -- keeps that credential out of the
90+
# job that runs untrusted PR code (dotnet build/test run arbitrary MSBuild
91+
# targets from the PR).
92+
- name: Upload preview packages
8793
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
94+
uses: actions/upload-artifact@v7
95+
with:
96+
name: preview-packages
97+
path: ./artifacts/*
98+
99+
# Split from `build` so the packages: write token is never present while
100+
# building/testing PR code. Only runs for pushes to main, never for PRs
101+
# (including same-repo ones), so a compromised dependency can't use this
102+
# token from its own PR build.
103+
publish-preview:
104+
needs: build
105+
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
106+
runs-on: ubuntu-latest
107+
permissions:
108+
contents: read
109+
packages: write
110+
steps:
111+
- uses: actions/checkout@v7
112+
with:
113+
persist-credentials: false
114+
115+
- uses: actions/setup-dotnet@v5
116+
117+
- uses: actions/download-artifact@v7
118+
with:
119+
name: preview-packages
120+
path: ./artifacts
121+
122+
- name: Publish preview to GitHub Packages
88123
run: |
89124
dotnet nuget push "./artifacts/*.nupkg" \
90125
--api-key ${{ secrets.GITHUB_TOKEN }} \

.github/workflows/claude-code-review.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ jobs:
3333

3434
- name: Run Claude Code Review
3535
id: claude-review
36-
uses: anthropics/claude-code-action@v1
36+
uses: anthropics/claude-code-action@01872ccc02bf66740207fb338a783ce028216758 # v1
3737
with:
3838
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
3939
plugin_marketplaces: 'https://github.com/anthropics/claude-code.git'

.github/workflows/claude.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ jobs:
3232

3333
- name: Run Claude Code
3434
id: claude
35-
uses: anthropics/claude-code-action@v1
35+
uses: anthropics/claude-code-action@01872ccc02bf66740207fb338a783ce028216758 # v1
3636
with:
3737
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
3838

.github/workflows/dependabot-approve-and-automerge.yml

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,14 @@
11
name: Dependabot Auto-Approve and Auto-Merge
2+
3+
# Auto-merges Dependabot patch/minor updates, narrowed by blast radius:
4+
# github-actions bumps (CI-only, and SHA-pinned per T10) and NuGet packages
5+
# NOT referenced by any shipped src/ project still auto-merge once CI (build)
6+
# passes. NuGet's own dependency-type classification has no test-project
7+
# detection (every NuGet requirement reports as "direct:production" -- see
8+
# Dependabot::Dependency.register_production_check in dependabot-core), so the
9+
# src/ check below is done directly against src/**/*.csproj instead of trusted
10+
# from Dependabot metadata. Majors, dotnet-sdk bumps, docker bumps, and any
11+
# NuGet package referenced by src/ always require a human review and merge.
212
on: pull_request
313

414
permissions:
@@ -12,20 +22,54 @@ jobs:
1222
steps:
1323
- name: Dependabot metadata
1424
id: metadata
15-
uses: dependabot/fetch-metadata@v3.1.0
25+
uses: dependabot/fetch-metadata@25dd0e34f4fe68f24cc83900b1fe3fe149efef98 # v3.1.0
1626
with:
1727
github-token: "${{ secrets.GITHUB_TOKEN }}"
28+
29+
- uses: actions/checkout@v7
30+
with:
31+
persist-credentials: false
32+
1833
# Only auto-approve/merge patch and minor updates. A major bump can carry
1934
# breaking changes that still compile and pass tests, then ship to consumers
20-
# unnoticed -- leave majors for a human to review and merge.
21-
- name: Approve a PR
35+
# unnoticed -- leave majors for a human to review and merge. Within
36+
# patch/minor, narrow further by dependency risk (see file header).
37+
- name: Decide auto-merge eligibility
38+
id: eligibility
2239
if: steps.metadata.outputs.update-type == 'version-update:semver-patch' || steps.metadata.outputs.update-type == 'version-update:semver-minor'
40+
env:
41+
ECOSYSTEM: ${{ steps.metadata.outputs.package-ecosystem }}
42+
UPDATED_DEPENDENCIES_JSON: ${{ steps.metadata.outputs.updated-dependencies-json }}
43+
run: |
44+
case "$ECOSYSTEM" in
45+
github_actions)
46+
echo "eligible=true" >> "$GITHUB_OUTPUT"
47+
;;
48+
nuget)
49+
production_found=false
50+
while IFS= read -r name; do
51+
[ -z "$name" ] && continue
52+
if grep -rqiF "Include=\"$name\"" src/ --include="*.csproj"; then
53+
production_found=true
54+
echo "::notice::src/ references $name -- not auto-merging"
55+
fi
56+
done < <(echo "$UPDATED_DEPENDENCIES_JSON" | jq -r '.[].dependencyName' | sort -u)
57+
echo "eligible=$([ "$production_found" = true ] && echo false || echo true)" >> "$GITHUB_OUTPUT"
58+
;;
59+
*)
60+
echo "eligible=false" >> "$GITHUB_OUTPUT"
61+
echo "::notice::Not auto-merging -- $ECOSYSTEM ecosystem requires human review"
62+
;;
63+
esac
64+
65+
- name: Approve a PR
66+
if: steps.eligibility.outputs.eligible == 'true'
2367
run: gh pr review --approve "$PR_URL"
2468
env:
2569
PR_URL: ${{github.event.pull_request.html_url}}
2670
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
2771
- name: Enable auto-merge for Dependabot PRs
28-
if: steps.metadata.outputs.update-type == 'version-update:semver-patch' || steps.metadata.outputs.update-type == 'version-update:semver-minor'
72+
if: steps.eligibility.outputs.eligible == 'true'
2973
run: gh pr merge --auto --merge "$PR_URL"
3074
env:
3175
PR_URL: ${{github.event.pull_request.html_url}}

.github/workflows/release.yml

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -65,25 +65,25 @@ jobs:
6565
6666
- name: Generate release notes
6767
id: changelog
68-
uses: orhun/git-cliff-action@v4
68+
uses: orhun/git-cliff-action@f50e11560dce63f7c33227798f90b924471a88b5 # v4
6969
with:
7070
config: cliff.toml
7171
args: --unreleased --tag v${{ steps.version.outputs.version }} --strip header
7272
env:
7373
OUTPUT: CHANGES.md
7474

75+
# Uses gh release create directly instead of a third-party release action
76+
# -- one less individual-maintainer action holding contents: write.
7577
- name: Create GitHub Release
76-
uses: softprops/action-gh-release@v3
77-
with:
78-
name: v${{ steps.version.outputs.version }}
79-
tag_name: v${{ steps.version.outputs.version }}
80-
target_commitish: ${{ github.sha }}
81-
body_path: CHANGES.md
82-
files: |
83-
./artifacts/*.nupkg
84-
./artifacts/*.snupkg
8578
env:
8679
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
80+
run: |
81+
gh release create "v${{ steps.version.outputs.version }}" \
82+
./artifacts/*.nupkg ./artifacts/*.snupkg \
83+
--repo "${{ github.repository }}" \
84+
--title "v${{ steps.version.outputs.version }}" \
85+
--notes-file CHANGES.md \
86+
--target "${{ github.sha }}"
8787
8888
- name: Publish to GitHub Packages
8989
run: |
@@ -99,7 +99,7 @@ jobs:
9999
# https://learn.microsoft.com/nuget/nuget-org/trusted-publishing
100100
- name: NuGet login (trusted publishing)
101101
id: nuget-login
102-
uses: NuGet/login@v1
102+
uses: NuGet/login@8d196754b4036150537f80ac539e15c2f1028841 # v1
103103
with:
104104
user: ${{ secrets.NUGET_USER }}
105105

@@ -149,7 +149,7 @@ jobs:
149149
150150
- name: Open public API promotion PR
151151
id: cpr
152-
uses: peter-evans/create-pull-request@v8
152+
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8
153153
with:
154154
# AUTOMATION_TOKEN lets the required `build` check run on the PR; with
155155
# the default GITHUB_TOKEN, pushes from a workflow do not trigger CI.

.github/workflows/version-bump.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ jobs:
4040
fi
4141
4242
- name: Open version bump PR
43-
uses: peter-evans/create-pull-request@v8
43+
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8
4444
with:
4545
# A PAT or GitHub App token (AUTOMATION_TOKEN) lets the required `build`
4646
# check run on the PR. With the default GITHUB_TOKEN, pushes from this

0 commit comments

Comments
 (0)