From d56735a85cd2dc5d43a75228527cc42740ca60db Mon Sep 17 00:00:00 2001 From: Krzysztof Ploch Date: Sat, 22 Aug 2026 18:04:38 +0200 Subject: [PATCH 1/2] fix(build-test-sonar)!: Authenticate Sonar scanner with sonar.token The action supplied the SonarCloud credential only as the sonar.login analysis property. sonar.login was deprecated in favour of sonar.token and is no longer honoured by the current SonarScanner engine, so the scanner authenticated anonymously and post-processing failed at "Create analysis" with a misleading "Not authorized or project not found". Because the token was interpolated successfully, this failed silently rather than erroring on a missing input. Set SONAR_TOKEN as an environment variable on the begin and end steps, which the scanner reads natively, and migrate the property name to sonar.token. Both steps keep shell: pwsh, so the value is referenced as $env:SONAR_TOKEN; a bare $SONAR_TOKEN is bash syntax and would expand to an empty string under pwsh, reintroducing the same anonymous-auth bug in a new form. Also: - Add a Validate SonarCloud Token step that fails fast with a workflow error annotation when the input is empty, instead of proceeding anonymously. GitHub does not enforce required: true for composite action inputs at runtime, so an empty secret previously sailed through. Only the token length is logged, never the value. - Stop writing the token into the Action Properties log line. Registered secrets are masked, but a token supplied through a non-secret path would have been printed in clear text. - Update actions/setup-dotnet v3 to v6 and actions/setup-java v4 to v5. v4 of setup-dotnet was also stale and still runs on a Node runtime heading for deprecation, so it would have re-created the warning this change is meant to clear. - Remove the dead outputs.random-number block, a leftover from the action template that referenced a non-existent step and always evaluated to an empty string. - Document the dotnet-version default rather than changing it, so no consumer's SDK selection shifts under them. - Apply the same sonar.token migration to build-test-snar-ps/build-test-sonar.ps1, whose begin line also referenced an undefined $sonarToken. That script is not currently wired to any action. BREAKING CHANGE: actions/setup-dotnet v6 and actions/setup-java v5 run on the Node 24 runtime and require a runner at v2.327.1 or newer. GitHub-hosted runners satisfy this automatically; self-hosted runners must be updated. The unused random-number output is also no longer declared. Refs: #1 --- build-test-snar-ps/build-test-sonar.ps1 | 4 +-- build-test-sonar/action.yml | 36 +++++++++++++++++-------- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/build-test-snar-ps/build-test-sonar.ps1 b/build-test-snar-ps/build-test-sonar.ps1 index 06901f9..f00b3c4 100644 --- a/build-test-snar-ps/build-test-sonar.ps1 +++ b/build-test-snar-ps/build-test-sonar.ps1 @@ -1,7 +1,7 @@ dotnet tool install --global dotnet-sonarscanner dotnet tool install --global dotnet-coverage dotnet restore Ploch.Common.sln -dotnet sonarscanner begin /k:"mrploch_ploch-common" /o:"mrploch" /d:sonar.login="$sonarToken" /d:sonar.cs.opencover.reportsPaths=**/CoverageResults/coverage.opencover.xml /d:sonar.host.url="https://sonarcloud.io" +dotnet sonarscanner begin /k:"mrploch_ploch-common" /o:"mrploch" /d:sonar.token="$env:SONAR_TOKEN" /d:sonar.cs.opencover.reportsPaths=**/CoverageResults/coverage.opencover.xml /d:sonar.host.url="https://sonarcloud.io" dotnet build Ploch.Common.sln --no-incremental --no-restore dotnet test Ploch.Common.sln --verbosity normal --no-build --logger "trx;LogFileName=TestOutputResults.xml" /p:CollectCoverage=true /p:CoverletOutput=./CoverageResults/ "/p:CoverletOutputFormat=cobertura%2copencover" -dotnet sonarscanner end /d:sonar.login="$env:SONAR_TOKEN" \ No newline at end of file +dotnet sonarscanner end /d:sonar.token="$env:SONAR_TOKEN" \ No newline at end of file diff --git a/build-test-sonar/action.yml b/build-test-sonar/action.yml index 931894c..126f958 100644 --- a/build-test-sonar/action.yml +++ b/build-test-sonar/action.yml @@ -3,9 +3,13 @@ description: 'Build and Test .NET Solution with SonarCloud' author: 'Kris Ploch' inputs: dotnet-version: - description: '.NET version' + description: >- + .NET SDK version to install, in actions/setup-dotnet syntax. Defaults to + 9.0.x. Consumers targeting a newer framework (for example net10.0) should + set this explicitly, or commit a global.json, rather than relying on + whichever SDK the runner image happens to ship. required: false - default: 9.0.x + default: 9.0.x solution-path: description: 'Solution path to build' required: true @@ -22,25 +26,31 @@ inputs: description: 'SonarQube Url, defaults to SonarCloud' default: 'https://sonarcloud.io' required: false -outputs: - random-number: - description: "Random number" - value: ${{ steps.random-number-generator.outputs.random-number }} runs: using: "composite" steps: + - name: Validate SonarCloud Token + shell: pwsh + env: + SONAR_TOKEN: ${{ inputs.sonar-token }} + run: | + if ([string]::IsNullOrWhiteSpace($env:SONAR_TOKEN)) { + Write-Output "::error::The 'sonar-token' input is empty. SonarCloud analysis would run anonymously and then fail during post-processing with a misleading 'Not authorized or project not found'. Check that the calling workflow passes a non-empty secret and that the secret is available to this job." + exit 1 + } + Write-Output "SonarCloud token supplied ($($env:SONAR_TOKEN.Length) characters)." - name: Action Properties shell: pwsh run: | $InformationPreference="Continue" Write-Information "Building ${{ inputs.solution-path }} with .NET ${{ inputs.dotnet-version }}" - Write-Information "SonarCloud Organization: ${{ inputs.sonar-organization }} Project Key: ${{ inputs.sonar-project-key }} Login: ${{ inputs.sonar-token }}" - - uses: actions/setup-java@v4 + Write-Information "SonarCloud Organization: ${{ inputs.sonar-organization }} Project Key: ${{ inputs.sonar-project-key }}" + - uses: actions/setup-java@v5 with: distribution: 'zulu' # See 'Supported distributions' for available options java-version: '17' - name: Setup .NET - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v6 with: dotnet-version: ${{ inputs.dotnet-version }} - name: Restore dependencies @@ -54,7 +64,9 @@ runs: run: dotnet tool install --global dotnet-coverage - name: SonarScanner Begin shell: pwsh - run: dotnet sonarscanner begin /k:"${{ inputs.sonar-project-key }}" /o:"${{ inputs.sonar-organization }}" /d:sonar.login="${{ inputs.sonar-token }}" /d:sonar.cs.opencover.reportsPaths=**/CoverageResults/coverage.opencover.xml /d:sonar.host.url="${{ inputs.sonar-url }}" + env: + SONAR_TOKEN: ${{ inputs.sonar-token }} + run: dotnet sonarscanner begin /k:"${{ inputs.sonar-project-key }}" /o:"${{ inputs.sonar-organization }}" /d:sonar.token="$env:SONAR_TOKEN" /d:sonar.cs.opencover.reportsPaths=**/CoverageResults/coverage.opencover.xml /d:sonar.host.url="${{ inputs.sonar-url }}" - name: Build shell: pwsh run: dotnet build ${{ inputs.solution-path }} --no-restore @@ -63,4 +75,6 @@ runs: run: dotnet test ${{ inputs.solution-path }} --verbosity normal --no-build --logger "trx;LogFileName=test-results.trx" /p:CollectCoverage=true /p:CoverletOutput=./CoverageResults/ "/p:CoverletOutputFormat=cobertura%2copencover" - name: SonarScanner End shell: pwsh - run: dotnet sonarscanner end /d:sonar.login="${{ inputs.sonar-token }}" + env: + SONAR_TOKEN: ${{ inputs.sonar-token }} + run: dotnet sonarscanner end /d:sonar.token="$env:SONAR_TOKEN" From bc2af898e2fb070987dc85d83a1a0ab8610e7872 Mon Sep 17 00:00:00 2001 From: Krzysztof Ploch Date: Sat, 22 Aug 2026 18:16:28 +0200 Subject: [PATCH 2/2] fix(build-test-sonar): Correct global.json guidance, guard script token Address review findings on PR #3. Because dotnet-version carries a default, the action always passes it to actions/setup-dotnet, which therefore never falls back to a global.json in the consuming repository. The input description said consumers could "commit a global.json" as an alternative to setting the input, which is wrong. Reword it to state plainly that the value is always passed and must be set explicitly for other target frameworks. The default is unchanged at 9.0.x, and the input stays optional, so no consumer's SDK selection shifts. Add the same empty-token guard to build-test-snar-ps/build-test-sonar.ps1 that the composite action now has. Without it the script expanded an empty token and ran the scanner anonymously, failing later with the same misleading authorisation error this branch exists to fix. Refs: #1 --- build-test-snar-ps/build-test-sonar.ps1 | 5 +++++ build-test-sonar/action.yml | 9 +++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/build-test-snar-ps/build-test-sonar.ps1 b/build-test-snar-ps/build-test-sonar.ps1 index f00b3c4..330bf35 100644 --- a/build-test-snar-ps/build-test-sonar.ps1 +++ b/build-test-snar-ps/build-test-sonar.ps1 @@ -1,3 +1,8 @@ +if ([string]::IsNullOrWhiteSpace($env:SONAR_TOKEN)) { + Write-Error "SONAR_TOKEN is not set. Without it the scanner authenticates anonymously and the analysis fails later with 'Not authorized or project not found'." + exit 1 +} + dotnet tool install --global dotnet-sonarscanner dotnet tool install --global dotnet-coverage dotnet restore Ploch.Common.sln diff --git a/build-test-sonar/action.yml b/build-test-sonar/action.yml index 126f958..ab45a6c 100644 --- a/build-test-sonar/action.yml +++ b/build-test-sonar/action.yml @@ -4,10 +4,11 @@ author: 'Kris Ploch' inputs: dotnet-version: description: >- - .NET SDK version to install, in actions/setup-dotnet syntax. Defaults to - 9.0.x. Consumers targeting a newer framework (for example net10.0) should - set this explicitly, or commit a global.json, rather than relying on - whichever SDK the runner image happens to ship. + .NET SDK version to install, in actions/setup-dotnet syntax. This value is + always passed to actions/setup-dotnet, which therefore never falls back to + a global.json in the consuming repository. Consumers targeting a different + framework (for example net10.0) must set this explicitly rather than + relying on whichever SDK the runner image happens to ship. required: false default: 9.0.x solution-path: