Skip to content

Commit fe95835

Browse files
ci: workflow security changes (#5643)
1 parent 2b6cbb3 commit fe95835

2 files changed

Lines changed: 220 additions & 76 deletions

File tree

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
# Privileged follow-up for "PR testing - if Node project".
2+
# Downloads check/coverage artifacts and posts PR comments / Codecov uploads.
3+
# Never checks out or executes pull request code.
4+
name: PR testing results - if Node project
5+
6+
on:
7+
workflow_run:
8+
workflows: ["PR testing - if Node project"]
9+
types: [completed]
10+
11+
permissions: {}
12+
13+
jobs:
14+
publish-pr-results:
15+
name: Publish PR check results
16+
if: github.event.workflow_run.event == 'pull_request'
17+
runs-on: ubuntu-latest
18+
permissions:
19+
actions: read
20+
contents: read
21+
pull-requests: write
22+
steps:
23+
- name: Resolve pull request number
24+
id: pr
25+
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
26+
with:
27+
script: |
28+
const wr = context.payload.workflow_run;
29+
const headSha = wr.head_sha;
30+
const headBranch = wr.head_branch;
31+
const headOwner =
32+
wr.head_repository?.owner?.login ||
33+
wr.head_repository?.full_name?.split('/')[0];
34+
35+
if (!headSha || !headBranch || !headOwner) {
36+
core.setOutput('number', '');
37+
core.info(
38+
`Missing required workflow_run head fields (sha=${headSha}, branch=${headBranch}, owner=${headOwner})`
39+
);
40+
return;
41+
}
42+
43+
const headRef = `${headOwner}:${headBranch}`;
44+
const pulls = await github.paginate(github.rest.pulls.list, {
45+
owner: context.repo.owner,
46+
repo: context.repo.repo,
47+
state: 'open',
48+
head: headRef,
49+
per_page: 100,
50+
});
51+
52+
const matches = pulls.filter((p) => p.head.sha === headSha);
53+
if (matches.length === 1) {
54+
core.setOutput('number', String(matches[0].number));
55+
core.info(`Resolved PR #${matches[0].number} for ${headRef} @ ${headSha}`);
56+
return;
57+
}
58+
59+
if (matches.length > 1) {
60+
core.warning(
61+
`Ambiguous PR match for ${headRef} @ ${headSha}: ${matches
62+
.map((p) => '#' + p.number)
63+
.join(', ')}`
64+
);
65+
} else {
66+
core.info(`No open PR for head ref ${headRef} with SHA ${headSha}`);
67+
}
68+
core.setOutput('number', '');
69+
70+
- name: Download PR check results
71+
id: download_checks
72+
if: steps.pr.outputs.number != ''
73+
continue-on-error: true
74+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
75+
with:
76+
name: pr-check-results
77+
path: pr-check-results
78+
github-token: ${{ github.token }}
79+
run-id: ${{ github.event.workflow_run.id }}
80+
81+
- name: Download coverage artifact
82+
id: download_coverage
83+
if: steps.pr.outputs.number != ''
84+
continue-on-error: true
85+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
86+
with:
87+
name: coverage-lcov
88+
path: coverage
89+
github-token: ${{ github.token }}
90+
run-id: ${{ github.event.workflow_run.id }}
91+
92+
- name: Read markdown check output
93+
id: markdown
94+
if: steps.pr.outputs.number != '' && steps.download_checks.outcome == 'success'
95+
run: |
96+
if [ -s pr-check-results/markdown_output.txt ]; then
97+
DELIMITER="EOF_MARKDOWN_$(openssl rand -hex 8)"
98+
echo "output<<${DELIMITER}" >> "$GITHUB_OUTPUT"
99+
cat pr-check-results/markdown_output.txt >> "$GITHUB_OUTPUT"
100+
echo >> "$GITHUB_OUTPUT"
101+
echo "${DELIMITER}" >> "$GITHUB_OUTPUT"
102+
else
103+
echo "output=" >> "$GITHUB_OUTPUT"
104+
fi
105+
106+
- name: Read locale check output
107+
id: locale
108+
if: steps.pr.outputs.number != '' && steps.download_checks.outcome == 'success'
109+
run: |
110+
if [ -s pr-check-results/locale_output.txt ]; then
111+
DELIMITER="EOF_LOCALE_$(openssl rand -hex 8)"
112+
echo "output<<${DELIMITER}" >> "$GITHUB_OUTPUT"
113+
cat pr-check-results/locale_output.txt >> "$GITHUB_OUTPUT"
114+
echo >> "$GITHUB_OUTPUT"
115+
echo "${DELIMITER}" >> "$GITHUB_OUTPUT"
116+
else
117+
echo "output=" >> "$GITHUB_OUTPUT"
118+
fi
119+
120+
- name: Comment on PR with markdown issues
121+
if: steps.pr.outputs.number != '' && steps.download_checks.outcome == 'success' && steps.markdown.outputs.output != ''
122+
uses: marocchino/sticky-pull-request-comment@5770ad5eb8f42dd2c4f34da00c94c5381e49af88 # v3.0.5
123+
with:
124+
number: ${{ steps.pr.outputs.number }}
125+
header: markdown-check-error
126+
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
127+
message: |
128+
### Markdown Check Results
129+
130+
We found issues in the following markdown files:
131+
132+
```
133+
${{ steps.markdown.outputs.output }}
134+
```
135+
136+
- name: Delete markdown check comment
137+
if: steps.pr.outputs.number != '' && steps.download_checks.outcome == 'success' && steps.markdown.outputs.output == ''
138+
uses: marocchino/sticky-pull-request-comment@5770ad5eb8f42dd2c4f34da00c94c5381e49af88 # v3.0.5
139+
with:
140+
number: ${{ steps.pr.outputs.number }}
141+
header: markdown-check-error
142+
delete: true
143+
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
144+
145+
- name: Comment on PR with locale issues
146+
if: steps.pr.outputs.number != '' && steps.download_checks.outcome == 'success' && steps.locale.outputs.output != ''
147+
uses: marocchino/sticky-pull-request-comment@5770ad5eb8f42dd2c4f34da00c94c5381e49af88 # v3.0.5
148+
with:
149+
number: ${{ steps.pr.outputs.number }}
150+
header: locale-check-error
151+
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
152+
message: |
153+
### Locale Check Results
154+
We found issues with locale keys:
155+
```
156+
${{ steps.locale.outputs.output }}
157+
```
158+
Please make sure all locale files have the same translation keys.
159+
160+
- name: Delete locale check comment if no issues
161+
if: steps.pr.outputs.number != '' && steps.download_checks.outcome == 'success' && steps.locale.outputs.output == ''
162+
uses: marocchino/sticky-pull-request-comment@5770ad5eb8f42dd2c4f34da00c94c5381e49af88 # v3.0.5
163+
with:
164+
number: ${{ steps.pr.outputs.number }}
165+
header: locale-check-error
166+
delete: true
167+
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
168+
169+
- name: Upload Coverage to Codecov
170+
if: steps.pr.outputs.number != '' && steps.download_coverage.outcome == 'success' && hashFiles('coverage/lcov.info') != ''
171+
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
172+
with:
173+
fail_ci_if_error: true
174+
files: ./coverage/lcov.info
175+
token: ${{ secrets.CODECOV_TOKEN }}
176+
verbose: true
177+
# workflow_run context points at the default branch; tell Codecov which PR/commit this coverage belongs to
178+
override_commit: ${{ github.event.workflow_run.head_sha }}
179+
override_pr: ${{ steps.pr.outputs.number }}
180+
override_branch: ${{ github.event.workflow_run.head_branch }}

.github/workflows/if-nodejs-pr-testing.yml

Lines changed: 40 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
# It does magic only if there is package.json file in the root of the project
1+
# It does magic only if there is package.json file in the root of the project.
2+
# Runs untrusted PR code under pull_request (no secrets). Privileged follow-up
3+
# (comments + Codecov for PRs) lives in if-nodejs-pr-results.yml.
24
name: PR testing - if Node project
35

46
on:
5-
pull_request_target:
7+
pull_request:
68
types: [opened, reopened, synchronize, ready_for_review]
79
push:
810
branches: [master]
@@ -41,11 +43,7 @@ jobs:
4143
shell: bash
4244
- if: steps.should_run.outputs.shouldrun == 'true'
4345
name: Checkout repository
44-
uses: actions/checkout@v4
45-
with:
46-
# Checkout the merge commit instead of the pull request head commit for a pull request
47-
# Fallback to the head commit if its not a pull request
48-
ref: ${{ github.event.pull_request.number != '' && format('refs/pull/{0}/merge', github.event.pull_request.number) || github.ref }}
46+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
4947

5048
- if: steps.should_run.outputs.shouldrun == 'true'
5149
name: Check if Node.js project and has package.json
@@ -64,7 +62,7 @@ jobs:
6462
shell: bash
6563
- if: steps.packagejson.outputs.exists == 'true' && steps.nvmrc.outputs.exists == 'true'
6664
name: Setup Node.js
67-
uses: actions/setup-node@v4
65+
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
6866
with:
6967
node-version: '${{ steps.nodeversion.outputs.version }}'
7068
- if: steps.packagejson.outputs.exists == 'true'
@@ -81,95 +79,61 @@ jobs:
8179
shell: bash
8280
run: npm run generate:assets --if-present
8381

84-
# Run the test:md script and capture output
82+
# Run the test:md script; on PRs write results for the privileged comment workflow
8583
- if: ${{ steps.packagejson.outputs.exists == 'true' && matrix.os == 'ubuntu-latest' }}
8684
name: Run markdown checks
8785
id: markdown_check
8886
run: |
87+
set +e
8988
ERRORS=$(npm run test:md | sed -n '/Errors in file/,$p')
90-
echo "markdown_output<<EOF" >> $GITHUB_OUTPUT
91-
echo "$ERRORS" >> $GITHUB_OUTPUT
92-
echo "EOF" >> $GITHUB_OUTPUT
93-
94-
# Post a comment using sticky-pull-request-comment
95-
- name: Comment on PR with markdown issues
96-
if: ${{ steps.markdown_check.outputs.markdown_output != '' && matrix.os == 'ubuntu-latest' }}
97-
uses: marocchino/sticky-pull-request-comment@3d60a5b2dae89d44e0c6ddc69dd7536aec2071cd
98-
with:
99-
header: markdown-check-error
100-
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
101-
message: |
102-
### Markdown Check Results
103-
104-
We found issues in the following markdown files:
105-
106-
```
107-
${{ steps.markdown_check.outputs.markdown_output }}
108-
```
109-
110-
# Delete the comment if there are no issues
111-
- if: ${{ steps.markdown_check.outputs.markdown_output == '' && matrix.os == 'ubuntu-latest' }}
112-
name: Delete markdown check comment
113-
uses: marocchino/sticky-pull-request-comment@3d60a5b2dae89d44e0c6ddc69dd7536aec2071cd
114-
with:
115-
header: markdown-check-error
116-
delete: true
117-
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
89+
set -e
90+
if [ "${{ github.event_name }}" = "pull_request" ]; then
91+
mkdir -p pr-check-results
92+
printf '%s' "$ERRORS" > pr-check-results/markdown_output.txt
93+
fi
11894
119-
# Run the test:locales script and capture output with cleaner formatting
95+
# Run the test:locales script; on PRs write results for the privileged comment workflow
12096
- if: ${{ steps.packagejson.outputs.exists == 'true' && matrix.os == 'ubuntu-latest' }}
12197
name: Run locale checks
12298
id: locale_check
12399
run: |
124-
# Run the test and capture any errors
125100
set +e
126101
LOCALE_OUTPUT=$(npm run test:locales 2>&1)
127102
EXIT_CODE=$?
128103
set -e
129-
130-
# If the command failed, extract and format the error message
131-
if [ $EXIT_CODE -ne 0 ]; then
132-
# Extract the found languages
133-
LANGUAGES=$(echo "$LOCALE_OUTPUT" | grep "Found" | sed 's/.*Found \(.*\) languages.*/Found \1 languages:/')
134-
135-
# Extract the missing keys information without timestamps
136-
MISSING_KEYS=$(echo "$LOCALE_OUTPUT" | grep -A 100 "Missing keys in" | grep -v "\[.*PM\] \|^\s*$")
137-
138-
# Combine the cleaned output
139-
CLEANED_OUTPUT="$LANGUAGES\n\n$MISSING_KEYS"
140-
141-
echo "locale_output<<EOF" >> $GITHUB_OUTPUT
142-
echo -e "$CLEANED_OUTPUT" >> $GITHUB_OUTPUT
143-
echo "EOF" >> $GITHUB_OUTPUT
104+
105+
if [ "${{ github.event_name }}" = "pull_request" ]; then
106+
mkdir -p pr-check-results
107+
if [ $EXIT_CODE -ne 0 ]; then
108+
LANGUAGES=$(echo "$LOCALE_OUTPUT" | grep "Found" | sed 's/.*Found \(.*\) languages.*/Found \1 languages:/')
109+
MISSING_KEYS=$(echo "$LOCALE_OUTPUT" | grep -A 100 "Missing keys in" | grep -v "\[.*PM\] \|^\s*$")
110+
CLEANED_OUTPUT="$LANGUAGES\n\n$MISSING_KEYS"
111+
echo -e "$CLEANED_OUTPUT" > pr-check-results/locale_output.txt
112+
else
113+
: > pr-check-results/locale_output.txt
114+
fi
144115
fi
145116
146-
# Post a comment using sticky-pull-request-comment
147-
- name: Comment on PR with locale issues
148-
if: ${{ steps.locale_check.outputs.locale_output != '' && matrix.os == 'ubuntu-latest' }}
149-
uses: marocchino/sticky-pull-request-comment@3d60a5b2dae89d44e0c6ddc69dd7536aec2071cd
117+
- if: ${{ steps.packagejson.outputs.exists == 'true' && matrix.os == 'ubuntu-latest' && github.event_name == 'pull_request' }}
118+
name: Upload PR check results
119+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
150120
with:
151-
header: locale-check-error
152-
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
153-
message: |
154-
### Locale Check Results
155-
We found issues with locale keys:
156-
```
157-
${{ steps.locale_check.outputs.locale_output }}
158-
```
159-
Please make sure all locale files have the same translation keys.
121+
name: pr-check-results
122+
path: pr-check-results/
123+
if-no-files-found: ignore
160124

161-
# Delete the comment if there are no issues
162-
- if: ${{ steps.locale_check.outputs.locale_output == '' && steps.packagejson.outputs.exists == 'true' && matrix.os == 'ubuntu-latest' }}
163-
name: Delete locale check comment if no issues
164-
uses: marocchino/sticky-pull-request-comment@3d60a5b2dae89d44e0c6ddc69dd7536aec2071cd
125+
- if: ${{ steps.packagejson.outputs.exists == 'true' && matrix.os == 'ubuntu-latest' && github.event_name == 'pull_request' }}
126+
name: Upload coverage artifact
127+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
165128
with:
166-
header: locale-check-error
167-
delete: true
168-
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
129+
name: coverage-lcov
130+
path: coverage/lcov.info
131+
if-no-files-found: ignore
169132

170-
- if: steps.packagejson.outputs.exists == 'true'
133+
# Trusted push to master: upload coverage with secrets in this workflow
134+
- if: steps.packagejson.outputs.exists == 'true' && github.event_name == 'push'
171135
name: Upload Coverage to Codecov
172-
uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673
136+
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
173137
with:
174138
fail_ci_if_error: true
175139
files: ./coverage/lcov.info

0 commit comments

Comments
 (0)