From 16f7290fe30f8bb3e7daee001d052a95b849f321 Mon Sep 17 00:00:00 2001 From: M B Date: Sat, 7 Feb 2026 05:55:12 +0000 Subject: [PATCH 1/8] ci: fetch Sonar token from Azure Key Vault via OIDC --- .github/workflows/sonarcloud.yml | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index 4cdc3b8..0ba6678 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -10,7 +10,9 @@ on: jobs: sonarcloud: runs-on: ubuntu-latest + environment: org-prod permissions: + id-token: write contents: read pull-requests: write steps: @@ -18,6 +20,27 @@ jobs: with: fetch-depth: 0 + - name: Azure login (OIDC) + uses: azure/login@v2 + with: + client-id: ${{ vars.AZURE_CLIENT_ID }} + tenant-id: ${{ vars.AZURE_TENANT_ID }} + subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }} + + - name: Read SonarCloud token from Key Vault + shell: bash + run: | + SONAR_TOKEN="$(az keyvault secret show \ + --vault-name "${{ vars.AZURE_KEYVAULT_NAME }}" \ + --name "sonar-cloud-token" \ + --query value -o tsv)" + if [ -z "${SONAR_TOKEN}" ]; then + echo "Key Vault secret sonar-cloud-token is empty." + exit 1 + fi + echo "::add-mask::$SONAR_TOKEN" + echo "SONAR_TOKEN=$SONAR_TOKEN" >> "$GITHUB_ENV" + - uses: actions/setup-python@v6 with: python-version: '3.12' @@ -32,7 +55,7 @@ jobs: uses: SonarSource/sonarcloud-github-action@ffc3010689be73b8e5ae0c57ce35968afd7909e8 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + SONAR_TOKEN: ${{ env.SONAR_TOKEN }} with: args: > -Dsonar.host.url=https://sonarcloud.io @@ -47,6 +70,6 @@ jobs: with: scanMetadataReportFile: dist/quality/sonar/scannerwork/report-task.txt env: - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + SONAR_TOKEN: ${{ env.SONAR_TOKEN }} SONAR_HOST_URL: https://sonarcloud.io timeout-minutes: 5 From 5f34baf409857413e242db96b70b3c541edf3b0e Mon Sep 17 00:00:00 2001 From: M B Date: Sat, 7 Feb 2026 06:48:11 +0000 Subject: [PATCH 2/8] ci: scope Sonar token to scan steps after tests --- .github/workflows/sonarcloud.yml | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index 0ba6678..52745bd 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -20,6 +20,16 @@ jobs: with: fetch-depth: 0 + - uses: actions/setup-python@v6 + with: + python-version: '3.12' + + - name: Install dependencies + run: make install-dev + + - name: Run tests with coverage + run: make coverage-sonar + - name: Azure login (OIDC) uses: azure/login@v2 with: @@ -28,6 +38,7 @@ jobs: subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }} - name: Read SonarCloud token from Key Vault + id: sonar_token shell: bash run: | SONAR_TOKEN="$(az keyvault secret show \ @@ -39,23 +50,13 @@ jobs: exit 1 fi echo "::add-mask::$SONAR_TOKEN" - echo "SONAR_TOKEN=$SONAR_TOKEN" >> "$GITHUB_ENV" - - - uses: actions/setup-python@v6 - with: - python-version: '3.12' - - - name: Install dependencies - run: make install-dev - - - name: Run tests with coverage - run: make coverage-sonar + echo "value=$SONAR_TOKEN" >> "$GITHUB_OUTPUT" - name: SonarCloud scan uses: SonarSource/sonarcloud-github-action@ffc3010689be73b8e5ae0c57ce35968afd7909e8 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SONAR_TOKEN: ${{ env.SONAR_TOKEN }} + SONAR_TOKEN: ${{ steps.sonar_token.outputs.value }} with: args: > -Dsonar.host.url=https://sonarcloud.io @@ -70,6 +71,6 @@ jobs: with: scanMetadataReportFile: dist/quality/sonar/scannerwork/report-task.txt env: - SONAR_TOKEN: ${{ env.SONAR_TOKEN }} + SONAR_TOKEN: ${{ steps.sonar_token.outputs.value }} SONAR_HOST_URL: https://sonarcloud.io timeout-minutes: 5 From d3eac53bf09a4a9b96c640cd816ed63c96775212 Mon Sep 17 00:00:00 2001 From: M B Date: Sat, 7 Feb 2026 17:47:53 +0000 Subject: [PATCH 3/8] ci: gate Sonar secret access to trusted PR contexts --- .github/workflows/sonarcloud.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index 52745bd..f4d2be2 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -30,7 +30,14 @@ jobs: - name: Run tests with coverage run: make coverage-sonar + - name: Skip SonarCloud on untrusted fork PR + if: ${{ github.event_name == 'pull_request' && (github.event.pull_request.head.repo.fork || !contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.pull_request.author_association)) }} + run: | + echo "Skipping SonarCloud token retrieval for untrusted PR context." + echo "Fork PRs and non-collaborator authors do not receive Vault-backed token access." + - name: Azure login (OIDC) + if: ${{ github.event_name != 'pull_request' || (github.event.pull_request.head.repo.fork == false && contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.pull_request.author_association)) }} uses: azure/login@v2 with: client-id: ${{ vars.AZURE_CLIENT_ID }} @@ -38,6 +45,7 @@ jobs: subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }} - name: Read SonarCloud token from Key Vault + if: ${{ github.event_name != 'pull_request' || (github.event.pull_request.head.repo.fork == false && contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.pull_request.author_association)) }} id: sonar_token shell: bash run: | @@ -53,6 +61,7 @@ jobs: echo "value=$SONAR_TOKEN" >> "$GITHUB_OUTPUT" - name: SonarCloud scan + if: ${{ github.event_name != 'pull_request' || (github.event.pull_request.head.repo.fork == false && contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.pull_request.author_association)) }} uses: SonarSource/sonarcloud-github-action@ffc3010689be73b8e5ae0c57ce35968afd7909e8 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -67,6 +76,7 @@ jobs: -Dsonar.enableIssueAnnotation=true - name: SonarCloud quality gate + if: ${{ github.event_name != 'pull_request' || (github.event.pull_request.head.repo.fork == false && contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.pull_request.author_association)) }} uses: SonarSource/sonarqube-quality-gate-action@cf038b0e0cdecfa9e56c198bbb7d21d751d62c3b with: scanMetadataReportFile: dist/quality/sonar/scannerwork/report-task.txt From c25f203452b9b8bd47e133bdf4f6d2e7f4d96a2c Mon Sep 17 00:00:00 2001 From: M B Date: Sat, 7 Feb 2026 17:50:12 +0000 Subject: [PATCH 4/8] ci: guard Sonar secrets on trusted PRs only --- .github/workflows/sonarcloud.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index f4d2be2..ce087f2 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -38,7 +38,7 @@ jobs: - name: Azure login (OIDC) if: ${{ github.event_name != 'pull_request' || (github.event.pull_request.head.repo.fork == false && contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.pull_request.author_association)) }} - uses: azure/login@v2 + uses: azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 with: client-id: ${{ vars.AZURE_CLIENT_ID }} tenant-id: ${{ vars.AZURE_TENANT_ID }} @@ -49,6 +49,7 @@ jobs: id: sonar_token shell: bash run: | + set -euo pipefail SONAR_TOKEN="$(az keyvault secret show \ --vault-name "${{ vars.AZURE_KEYVAULT_NAME }}" \ --name "sonar-cloud-token" \ From 7124db056c57c80d12adbaca2e26ebffcbbb53d6 Mon Sep 17 00:00:00 2001 From: M B Date: Sat, 7 Feb 2026 21:08:53 +0000 Subject: [PATCH 5/8] ci: retrigger checks after org required workflow policy From 999111e140122214c735cbb66ffc9e0369a04f9a Mon Sep 17 00:00:00 2001 From: M B Date: Sat, 7 Feb 2026 21:11:01 +0000 Subject: [PATCH 6/8] ci: retrigger required workflow gate From 851f1586f51aeaa4b4e76831ff402ba2f83fd6f6 Mon Sep 17 00:00:00 2001 From: M B Date: Sat, 7 Feb 2026 21:15:35 +0000 Subject: [PATCH 7/8] ci: retrigger after required-workflow fix From b0a5ca52ec54b92b2dc710f7b81b07fd59d86dc3 Mon Sep 17 00:00:00 2001 From: M B Date: Sat, 7 Feb 2026 21:18:13 +0000 Subject: [PATCH 8/8] ci: satisfy required zizmor workflow policy --- .github/workflows/dependency-review.yml | 9 +++------ .github/workflows/sonarcloud.yml | 7 ++++--- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index a1547fb..ab890b6 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -20,20 +20,17 @@ on: # https://docs.github.com/en/enterprise-cloud@latest/code-security/supply-chain-security/understanding-your-software-supply-chain/using-the-dependency-submission-api permissions: contents: read - # Write permissions for pull-requests are required for using the `comment-summary-in-pr` option, comment out if you aren't using this option - pull-requests: write jobs: dependency-review: runs-on: ubuntu-latest steps: - name: 'Checkout repository' - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: 'Dependency Review' - uses: actions/dependency-review-action@v4 + uses: actions/dependency-review-action@3c4e3dcb1aa7874d2c16be7d79418e9b7efd6261 # v4 # Commonly enabled options, see https://github.com/actions/dependency-review-action#configuration-options for all available options. - with: - comment-summary-in-pr: always + # No PR comment output to keep permissions read-only. # fail-on-severity: moderate # deny-licenses: GPL-1.0-or-later, LGPL-2.0-or-later # retry-on-snapshot-warnings: true diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index ce087f2..114e07b 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -6,6 +6,7 @@ on: push: branches: - main +permissions: {} jobs: sonarcloud: @@ -16,11 +17,11 @@ jobs: contents: read pull-requests: write steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: fetch-depth: 0 - - uses: actions/setup-python@v6 + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 with: python-version: '3.12' @@ -63,7 +64,7 @@ jobs: - name: SonarCloud scan if: ${{ github.event_name != 'pull_request' || (github.event.pull_request.head.repo.fork == false && contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.pull_request.author_association)) }} - uses: SonarSource/sonarcloud-github-action@ffc3010689be73b8e5ae0c57ce35968afd7909e8 + uses: SonarSource/sonarqube-scan-action@a31c9398be7ace6bbfaf30c0bd5d415f843d45e9 # v7.0.0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ steps.sonar_token.outputs.value }}