Skip to content

Commit fe12fc9

Browse files
efiacorCopilot
andauthored
Harden SonarCloud workflow: scope permissions, fix injection vectors (#1057)
* Harden SonarCloud workflow: scope permissions, fix injection vectors - Move permissions from workflow level to job level (least privilege) - Remove unused checks:write permission - Move clone_url expression to env var (consistent with other hardening) - Add missing -Dproject.settings to push scan step Signed-off-by: Fiachra Corcoran <fiachra.corcoran@est.tech> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Fiachra Corcoran <fiachra.corcoran@est.tech> * Pin actions to SHA, add actions:read to sonarqube job, remove debug step Address Copilot review comments: - Pin all third-party actions to commit SHAs (latest releases) - Add actions:read permission to sonarqube job for artifact downloads - Remove debug echo event step (leaks PR metadata to logs) Signed-off-by: Fiachra Corcoran <fiachra.corcoran@est.tech> --------- Signed-off-by: Fiachra Corcoran <fiachra.corcoran@est.tech> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
1 parent 5feb88a commit fe12fc9

1 file changed

Lines changed: 87 additions & 77 deletions

File tree

.github/workflows/sonarcloud.yml

Lines changed: 87 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: SonarCloud analysis
22

3-
on:
3+
on:
44
workflow_run:
55
workflows: [CI test report]
66
types: [completed]
@@ -9,12 +9,14 @@ jobs:
99
check-artifacts:
1010
runs-on: ubuntu-latest
1111
if: github.event.workflow_run.conclusion == 'success'
12+
permissions:
13+
actions: read
1214
outputs:
1315
has-artifacts: ${{ steps.check.outputs.has-artifacts }}
1416
steps:
1517
- name: Check for coverage artifact
1618
id: check
17-
uses: actions/github-script@v7
19+
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
1820
with:
1921
script: |
2022
const artifacts = await github.rest.actions.listWorkflowRunArtifacts({
@@ -24,92 +26,100 @@ jobs:
2426
});
2527
const hasCoverage = artifacts.data.artifacts.some(a => a.name === 'coverage-report');
2628
core.setOutput('has-artifacts', hasCoverage);
27-
return hasCoverage;
2829
2930
sonarqube:
3031
needs: check-artifacts
3132
if: needs.check-artifacts.outputs.has-artifacts == 'true'
3233
runs-on: ubuntu-latest
34+
permissions:
35+
actions: read
36+
contents: read
37+
pull-requests: read
3338
steps:
39+
- name: Download PR number artifact
40+
if: github.event.workflow_run.event == 'pull_request'
41+
uses: dawidd6/action-download-artifact@b6e2e70617bc3265edd6dab6c906732b2f1ae151 # v21
42+
with:
43+
workflow: CI test report
44+
run_id: ${{ github.event.workflow_run.id }}
45+
name: PR_NUMBER
3446

35-
- name: echo event
36-
run: cat $GITHUB_EVENT_PATH
37-
38-
- name: Download PR number artifact
39-
if: github.event.workflow_run.event == 'pull_request'
40-
uses: dawidd6/action-download-artifact@v9
41-
with:
42-
workflow: CI test report
43-
run_id: ${{ github.event.workflow_run.id }}
44-
name: PR_NUMBER
45-
46-
- name: Read PR_NUMBER.txt
47-
if: github.event.workflow_run.event == 'pull_request'
48-
id: pr_number
49-
uses: juliangruber/read-file-action@v1.1.7
50-
with:
51-
path: ./PR_NUMBER.txt
47+
- name: Read PR_NUMBER.txt
48+
if: github.event.workflow_run.event == 'pull_request'
49+
id: pr_number
50+
uses: juliangruber/read-file-action@271ff311a4947af354c6abcd696a306553b9ec18 # v1.1.8
51+
with:
52+
path: ./PR_NUMBER.txt
5253

53-
- name: Request GitHub API for PR data
54-
if: github.event.workflow_run.event == 'pull_request'
55-
uses: octokit/request-action@v2.x
56-
id: get_pr_data
57-
with:
58-
route: GET /repos/{full_name}/pulls/{number}
59-
number: ${{ steps.pr_number.outputs.content }}
60-
full_name: ${{ github.event.repository.full_name }}
61-
env:
62-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
54+
- name: Request GitHub API for PR data
55+
if: github.event.workflow_run.event == 'pull_request'
56+
uses: octokit/request-action@b91aabaa861c777dcdb14e2387e30eddf04619ae # v3.0.0
57+
id: get_pr_data
58+
with:
59+
route: GET /repos/{full_name}/pulls/{number}
60+
number: ${{ steps.pr_number.outputs.content }}
61+
full_name: ${{ github.event.repository.full_name }}
62+
env:
63+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
6364

64-
- name: Checkout repo
65-
uses: actions/checkout@v4
66-
with:
65+
# Use SHA for checkout — immune to branch-name injection
66+
- name: Checkout PR head
67+
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
68+
with:
6769
repository: ${{ github.event.workflow_run.head_repository.full_name }}
68-
ref: ${{ github.event.workflow_run.head_branch }}
70+
ref: ${{ github.event.workflow_run.head_sha }}
6971
fetch-depth: 0
72+
persist-credentials: false
7073

71-
- name: Checkout base branch
72-
if: github.event.workflow_run.event == 'pull_request'
73-
run: |
74-
git remote add upstream ${{ github.event.repository.clone_url }}
75-
git fetch upstream
76-
git checkout -B ${{ fromJson(steps.get_pr_data.outputs.data).base.ref }} upstream/${{ fromJson(steps.get_pr_data.outputs.data).base.ref }}
77-
git checkout ${{ github.event.workflow_run.head_branch }}
78-
git clean -ffdx && git reset --hard HEAD
74+
# Branch names passed via env (not expression interpolation in run:)
75+
- name: Checkout base branch
76+
if: github.event.workflow_run.event == 'pull_request'
77+
env:
78+
BASE_REF: ${{ fromJson(steps.get_pr_data.outputs.data).base.ref }}
79+
HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
80+
CLONE_URL: ${{ github.event.repository.clone_url }}
81+
run: |
82+
git remote add upstream "$CLONE_URL"
83+
git fetch upstream
84+
git checkout -B "$BASE_REF" "upstream/$BASE_REF"
85+
git checkout "$HEAD_SHA"
86+
git clean -ffdx && git reset --hard HEAD
7987
80-
- name: Download coverage artifact
81-
uses: dawidd6/action-download-artifact@v9
82-
with:
83-
workflow: CI test report
84-
run_id: ${{ github.event.workflow_run.id }}
85-
name: coverage-report
86-
use_unzip: true
87-
88-
- name: Fix Go module paths in coverage
89-
run: |
90-
sed -i 's|github.com/kptdev/porch|.|g' coverage.out
91-
92-
- name: SonarQube Scan on PR
93-
if: github.event.workflow_run.event == 'pull_request'
94-
uses: SonarSource/sonarqube-scan-action@v7.0.0
95-
env:
96-
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
97-
with:
98-
args:
99-
-Dsonar.projectKey=kptdev_porch
100-
-Dsonar.organization=kptdev
101-
-Dproject.settings=sonar-project.properties
102-
-Dsonar.pullrequest.key=${{ fromJson(steps.get_pr_data.outputs.data).number }}
103-
-Dsonar.pullrequest.branch=${{ fromJson(steps.get_pr_data.outputs.data).head.ref }}
104-
-Dsonar.pullrequest.base=${{ fromJson(steps.get_pr_data.outputs.data).base.ref }}
88+
- name: Download coverage artifact
89+
uses: dawidd6/action-download-artifact@b6e2e70617bc3265edd6dab6c906732b2f1ae151 # v21
90+
with:
91+
workflow: CI test report
92+
run_id: ${{ github.event.workflow_run.id }}
93+
name: coverage-report
94+
use_unzip: true
95+
96+
- name: Fix Go module paths in coverage
97+
run: |
98+
sed -i 's|github.com/kptdev/porch|.|g' coverage.out
10599
106-
- name: SonarCloud Scan on push
107-
if: github.event.workflow_run.event == 'push' && github.event.workflow_run.head_repository.full_name == github.event.repository.full_name
108-
uses: SonarSource/sonarqube-scan-action@v7.0.0
109-
env:
110-
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
111-
with:
112-
args:
113-
-Dsonar.projectKey=kptdev_porch
114-
-Dsonar.organization=kptdev
115-
-Dproject.settings=sonar-project.properties
100+
- name: SonarQube Scan on PR
101+
if: github.event.workflow_run.event == 'pull_request'
102+
uses: SonarSource/sonarqube-scan-action@713881670b6b3676cda39549040e2d88c70d582e # v8.2.0
103+
env:
104+
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
105+
with:
106+
args:
107+
-Dsonar.projectKey=kptdev_porch
108+
-Dsonar.organization=kptdev
109+
-Dproject.settings=sonar-project.properties
110+
-Dsonar.pullrequest.key=${{ fromJson(steps.get_pr_data.outputs.data).number }}
111+
-Dsonar.pullrequest.branch=${{ fromJson(steps.get_pr_data.outputs.data).head.ref }}
112+
-Dsonar.pullrequest.base=${{ fromJson(steps.get_pr_data.outputs.data).base.ref }}
113+
114+
- name: SonarCloud Scan on push
115+
if: >-
116+
github.event.workflow_run.event == 'push' &&
117+
github.event.workflow_run.head_repository.full_name == github.event.repository.full_name
118+
uses: SonarSource/sonarqube-scan-action@713881670b6b3676cda39549040e2d88c70d582e # v8.2.0
119+
env:
120+
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
121+
with:
122+
args:
123+
-Dsonar.projectKey=kptdev_porch
124+
-Dsonar.organization=kptdev
125+
-Dproject.settings=sonar-project.properties

0 commit comments

Comments
 (0)