11name : Changelog Preview
22
33on :
4- # Allow this workflow to be called from other repositories
4+ # Allow this workflow to be called from other repositories.
55 #
66 # USAGE REQUIREMENTS:
77 # When calling this workflow from another repository, you must:
1010 # - This is required to post comments on PRs from forks
1111 # - pull_request event has read-only GITHUB_TOKEN for fork PRs
1212 #
13- # 2. Grant required permissions:
14- # - contents: read (to checkout repo and read git history)
15- # - pull-requests: write (to post/update PR comments in comment mode)
16- # - statuses: write (to create commit statuses in status check mode)
13+ # 2. Grant required permissions at the call site:
14+ # - contents: read (to checkout repo and read git history)
15+ # - pull-requests: write (to post/update PR comments)
1716 #
1817 # 3. Inherit secrets:
19- # - secrets: inherit (ensures caller's GITHUB_TOKEN is used)
18+ # - secrets: inherit (ensures caller's GITHUB_TOKEN is used)
2019 #
21- # Example caller workflow (comment mode) :
20+ # Example caller workflow:
2221 #
2322 # on:
2423 # pull_request_target:
3332 # uses: getsentry/craft/.github/workflows/changelog-preview.yml@v2
3433 # secrets: inherit
3534 #
36- # Example caller workflow (status check mode):
37- #
38- # permissions:
39- # contents: read
40- # statuses: write
41- #
42- # jobs:
43- # changelog-preview:
44- # uses: getsentry/craft/.github/workflows/changelog-preview.yml@v2
45- # with:
46- # comment: false
47- # secrets: inherit
48- #
49- # SECURITY NOTE:
50- # This workflow is safe to use with pull_request_target because:
51- # - The Craft binary is downloaded from releases, NOT from the PR
52- # - Only git metadata (commits, tags) and .craft.yml config are read
53- # - No code from the PR is ever executed
54- #
35+ # SECURITY NOTES FOR CALLERS:
36+ # Callers that invoke this workflow on pull_request_target are checking out
37+ # attacker-controlled code and running Craft against it. To reduce blast
38+ # radius, callers SHOULD:
39+ # - Pin this workflow to a commit SHA, not a moving tag like @v2.
40+ # - Grant only the minimum permissions shown above; never add
41+ # `contents: write` or other scopes to this job.
42+ # - Be aware that Craft reads `.craft.env` from the config-file directory
43+ # and copies its keys into the environment; hardening for that case is
44+ # tracked separately.
5545 workflow_call :
5646 inputs :
5747 working-directory :
6353 description : ' Version of Craft to use (tag or "latest")'
6454 required : false
6555 type : string
66- comment :
67- description : ' Post changelog as PR comment (true) or as check run with job summary (false)'
68- required : false
69- type : boolean
70- default : true
7156
72- # Also run on PRs in this repository (dogfooding)
73- pull_request_target :
57+ # Also run on PRs in this repository (dogfooding).
58+ #
59+ # We intentionally use `pull_request` (not `pull_request_target`) here so the
60+ # job runs with a read-only GITHUB_TOKEN for fork PRs. Same-repo branch PRs
61+ # still get the declared permissions and a working comment; fork PRs won't
62+ # get a preview comment, which is an acceptable trade-off for eliminating
63+ # the pull_request_target attack surface on this repository.
64+ pull_request :
7465 types : [opened, synchronize, reopened, edited, labeled, unlabeled]
7566
76- permissions :
77- contents : read
78- pull-requests : write # For comment mode
79- statuses : write # For status check mode
80-
8167jobs :
8268 preview :
8369 runs-on : ubuntu-latest
70+ permissions :
71+ contents : read
72+ pull-requests : write
8473 steps :
85- # For pull_request_target, we must explicitly specify the ref to get the PR commits.
86- # Try the merge ref first; fall back to head ref if PR has merge conflicts.
87- - uses : actions/checkout@v6
74+ # For pull_request_target callers we must explicitly specify the ref to
75+ # get the PR commits. Try the merge ref first; fall back to head ref if
76+ # the PR has merge conflicts.
77+ #
78+ # `persist-credentials: false` ensures the GITHUB_TOKEN is not written
79+ # into `.git/config` on disk, which would otherwise leak if a later step
80+ # executes PR-controlled code.
81+ - uses : actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
8882 id : checkout-merge
8983 continue-on-error : true
9084 with :
9185 fetch-depth : 0
9286 ref : refs/pull/${{ github.event.pull_request.number }}/merge
87+ persist-credentials : false
9388
94- - uses : actions/checkout@v6
89+ - uses : actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
9590 if : steps.checkout-merge.outcome == 'failure'
9691 with :
9792 fetch-depth : 0
9893 ref : ${{ github.event.pull_request.head.sha }}
94+ persist-credentials : false
9995
10096 - name : Install Craft
10197 shell : bash
@@ -165,9 +161,7 @@ jobs:
165161 # CalVer projects don't use semver bumps — skip the impact badge
166162 if [[ "$VERSIONING_POLICY" == "calver" ]]; then
167163 BUMP_BADGE=""
168- BUMP_SHORT="CalVer"
169164 SECTION_HEADING="Changelog Preview"
170- STATUS_CONTEXT="Changelog Preview"
171165 else
172166 case "$BUMP_TYPE" in
173167 major) BUMP_BADGE="🔴 **Major** (breaking changes)" ;;
@@ -176,72 +170,11 @@ jobs:
176170 *) BUMP_BADGE="⚪ **None** (no version bump detected)" ;;
177171 esac
178172
179- case "$BUMP_TYPE" in
180- major) BUMP_SHORT="Major" ;;
181- minor) BUMP_SHORT="Minor" ;;
182- patch) BUMP_SHORT="Patch" ;;
183- *) BUMP_SHORT="None" ;;
184- esac
185-
186173 SECTION_HEADING="Semver Impact of This PR"
187- STATUS_CONTEXT="Changelog Preview / Semver Impact"
188174 fi
189175
190- # Determine mode: use status check mode when comment is false OR when running internally (no input)
191- USE_COMMENT_MODE="${{ inputs.comment }}"
192- if [[ "$USE_COMMENT_MODE" == "false" ]] || [[ -z "$USE_COMMENT_MODE" ]]; then
193- # Status check mode (new feature or internal dogfooding)
194- echo "Using status check mode..."
195-
196- HEAD_SHA="${{ github.event.pull_request.head.sha || github.sha }}"
197- TARGET_URL="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}"
198- PR_URL="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/pull/${PR_NUMBER}"
199-
200- # Create commit status via GitHub API
201- echo "Creating commit status..."
202- gh api --method POST \
203- -H "Accept: application/vnd.github+json" \
204- -H "X-GitHub-Api-Version: 2022-11-28" \
205- "repos/$GITHUB_REPOSITORY/statuses/$HEAD_SHA" \
206- -f state="success" \
207- -f context="$STATUS_CONTEXT" \
208- -f description="$BUMP_SHORT" \
209- -f target_url="$TARGET_URL"
210-
211- echo "✓ Commit status created"
212-
213- # 2. Write to job summary
214- cat >> $GITHUB_STEP_SUMMARY << CRAFT_CHANGELOG_SUMMARY_END
215- # Changelog Preview for PR #${PR_NUMBER}
216-
217- [→ View PR #${PR_NUMBER}](${PR_URL})
218-
219- ## ${SECTION_HEADING}
220-
221- ${BUMP_BADGE}
222-
223- <details>
224- <summary>📋 Changelog Preview</summary>
225-
226- This is how your changes will appear in the changelog.
227- Entries from this PR are highlighted with a left border (blockquote style).
228-
229- ---
230-
231- ${CHANGELOG}
232-
233- ---
234-
235- </details>
236- CRAFT_CHANGELOG_SUMMARY_END
237-
238- echo "✓ Job summary written"
239- else
240- # Comment mode (original behavior)
241- echo "Using comment mode..."
242-
243- COMMENT_FILE=$(mktemp)
244- cat > "$COMMENT_FILE" << CRAFT_CHANGELOG_COMMENT_END
176+ COMMENT_FILE=$(mktemp)
177+ cat > "$COMMENT_FILE" << CRAFT_CHANGELOG_COMMENT_END
245178 <!-- craft-changelog-preview -->
246179 ## ${SECTION_HEADING}
247180
@@ -264,23 +197,22 @@ jobs:
264197 <sub>🤖 This preview updates automatically when you update the PR.</sub>
265198 CRAFT_CHANGELOG_COMMENT_END
266199
267- COMMENT_ID=$(gh api \
268- "repos/$GITHUB_REPOSITORY/issues/$PR_NUMBER/comments" \
269- --jq '.[] | select(.body | contains("<!-- craft-changelog-preview -->")) | .id' \
270- | head -1)
271-
272- if [[ -n "$COMMENT_ID" ]]; then
273- echo "Updating existing comment $COMMENT_ID..."
274- gh api -X PATCH \
275- "repos/$GITHUB_REPOSITORY/issues/comments/$COMMENT_ID" \
276- -F body=@"$COMMENT_FILE"
277- else
278- echo "Creating new comment..."
279- gh api -X POST \
280- "repos/$GITHUB_REPOSITORY/issues/$PR_NUMBER/comments" \
281- -F body=@"$COMMENT_FILE"
282- fi
200+ COMMENT_ID=$(gh api \
201+ "repos/$GITHUB_REPOSITORY/issues/$PR_NUMBER/comments" \
202+ --jq '.[] | select(.body | contains("<!-- craft-changelog-preview -->")) | .id' \
203+ | head -1)
283204
284- rm -f "$COMMENT_FILE"
285- echo "✓ Comment posted"
205+ if [[ -n "$COMMENT_ID" ]]; then
206+ echo "Updating existing comment $COMMENT_ID..."
207+ gh api -X PATCH \
208+ "repos/$GITHUB_REPOSITORY/issues/comments/$COMMENT_ID" \
209+ -F body=@"$COMMENT_FILE"
210+ else
211+ echo "Creating new comment..."
212+ gh api -X POST \
213+ "repos/$GITHUB_REPOSITORY/issues/$PR_NUMBER/comments" \
214+ -F body=@"$COMMENT_FILE"
286215 fi
216+
217+ rm -f "$COMMENT_FILE"
218+ echo "✓ Comment posted"
0 commit comments