Skip to content

Scope Sonar analysis to the workspace, narrow workflow permissions - #67

Merged
matt-edmondson merged 2 commits into
mainfrom
fix/sonar-exclusions-and-workflow-permissions
Aug 5, 2026
Merged

Scope Sonar analysis to the workspace, narrow workflow permissions#67
matt-edmondson merged 2 commits into
mainfrom
fix/sonar-exclusions-and-workflow-permissions

Conversation

@matt-edmondson

Copy link
Copy Markdown
Contributor

Summary

Three CI fixes. Nothing in the tool itself changes.

The quality gate on #66 failed on reliability and security ratings that had nothing to do with that PR's code, and the run log was full of warnings belonging to KtsuBuild. Both turned out to have the same root cause.

Build KtsuBuild before the SonarQube window opens

SonarScanner injects its Roslyn analyzers into every project compiled between begin and end. The workflow cloned and built KtsuBuild inside that window, so roughly 25 of KtsuBuild's own code smells (S1192, S3776, S107 and friends) were reported as warnings against this repository, and the scanner indexed KtsuBuild.CLI and KtsuBuild as modules of this analysis:

INFO: Indexing files of module 'KtsuBuild.CLI'
INFO: Indexing files of module 'KtsuBuild'
INFO: Indexing files of module 'IconHelper'

Those are compiler diagnostics rather than reported issues, so sonar.exclusions could never suppress them. The pre-existing **/KtsuBuild/** entry had no effect on them at all.

The clone now happens before begin, a new step compiles KtsuBuild.CLI there, and the pipeline step runs it with --no-build so nothing is recompiled inside the window.

Pin sonar.projectBaseDir to the workspace

Scanner for .NET v8 derives the base directory from the projects it sees. A project compiled under the runner temp directory widened it to the common ancestor of the workspace and that directory, which pulled the runner's own action checkouts under _actions into the file scan. That is what actually failed the gate: the reliability rating was driven entirely by a bug in merge.test.ts, which belongs to actions/checkout, and four other TypeScript files from the same source contributed code smells.

With the base directory pinned, only this repository is ever in scope. That makes the _actions, _temp and KtsuBuild exclusions all unnecessary, so the Configure SonarQube exclusions step is deleted. sonar.exclusions keeps only NativeExports.cs, a genuine in-repository exclusion, passed inline like the coverage exclusions already were.

Narrow update-sdks.yml permissions

The workflow declared permissions: read-all, which SonarCloud flagged as a vulnerability and which drove the security rating. The default is now contents: read. The one job that writes already declares the contents: write and pull-requests: write it needs, and job level permissions override the workflow default, so behaviour is unchanged.

Shells

Every step across these workflows now uses pwsh, replacing a mix of bash, Windows PowerShell and pwsh. The converted steps do no redirection into GITHUB_ENV or GITHUB_OUTPUT, so the UTF-16 versus UTF-8 encoding difference between Windows PowerShell and pwsh does not apply to any of them. The two steps that do write there were already pwsh and are untouched.

Test plan

Verified locally where possible:

  • dotnet build then dotnet run --no-build against the real KtsuBuild.CLI, confirming the CLI runs without recompiling
  • The rewritten tag selection sorts by [version] rather than as text, so v1.8.15 beats v1.8.9. The bash and pwsh versions were run side by side and both selected v1.8.15
  • The rewritten clone step performs a real shallow clone of v1.8.15 with the CLI present
  • All three workflow files parse, and no non-pwsh shell remains

What to check on this run

The base directory behaviour only manifests under a real scanner run, so this PR is its own test. In the End SonarQube step, look at the Indexing files of module lines:

  • Only IconHelper and IconHelper.Test means the diagnosis holds
  • KtsuBuild still appearing means --no-build is not taking effect
  • _actions files still being flagged means projectBaseDir is not being honoured

🤖 Generated with Claude Code

https://claude.ai/code/session_01Ruh9nAQiuU5VGHV8uJCXtu

…sions

Three CI fixes, no change to the tool itself.

Build KtsuBuild before the SonarQube window opens. SonarScanner injects its
Roslyn analyzers into every project compiled between begin and end, so cloning
and building KtsuBuild inside that window made the run report roughly 25 of
KtsuBuild's own code smells, S1192, S3776, S107 and friends, as warnings
against this repository, and indexed KtsuBuild.CLI and KtsuBuild as modules of
this analysis. Those warnings are compiler diagnostics rather than reported
issues, so sonar.exclusions could never suppress them, which is why the
**/KtsuBuild/** entry never had any effect. The clone now happens before the
begin step, a new step compiles KtsuBuild.CLI there, and the pipeline step runs
it with --no-build so nothing is recompiled inside the window.

Pin sonar.projectBaseDir to the workspace. Scanner for .NET v8 otherwise
derives the base directory from the projects it sees, so a project compiled
under the runner temp directory widened it to the common ancestor and pulled
the runner's own action checkouts into the file scan. That is what failed the
quality gate on PR #66, where the reliability rating was driven entirely by a
bug in merge.test.ts belonging to actions/checkout. With the base directory
pinned, only this repository is ever in scope, so the _actions, _temp and
KtsuBuild exclusions are all unnecessary and the Configure SonarQube exclusions
step is gone. sonar.exclusions keeps only NativeExports.cs, which is a genuine
in-repository exclusion, and is now passed inline like the coverage exclusions
already were.

Narrow update-sdks.yml permissions. The workflow declared read-all, which
SonarCloud flagged as a vulnerability and which drove the security rating. The
default is now contents: read. The one job that writes already declares the
contents: write and pull-requests: write it needs, so behaviour is unchanged.

Every step across these workflows now uses pwsh, replacing the mix of bash,
Windows PowerShell and pwsh that was there before. The converted steps run no
redirection into GITHUB_ENV or GITHUB_OUTPUT, so the UTF-16 encoding difference
between Windows PowerShell and pwsh does not apply to any of them. The tag
selection in the clone step sorts by [version] rather than as text, matching
what sort -V did, and both were checked to select the same tag.

Claude-Session: https://claude.ai/code/session_01Ruh9nAQiuU5VGHV8uJCXtu
…g it in the run block

Expanding ${{ secrets.SONAR_TOKEN }} inside a run block interpolates the secret
into the script text itself before the shell ever sees it, which is what the
"Avoid expanding secrets in a run block" rule objects to.

Both scanner steps already declared SONAR_TOKEN in their step env, so they now
read $env:SONAR_TOKEN instead. The value reaches the scanner identically, it is
just resolved at runtime rather than baked into the rendered script. Verified
that pwsh expands it inside the quoted native command argument and passes
/d:sonar.token=<value> as before.

The remaining ${{ secrets.* }} references are all env declarations, which is the
pattern the rule asks for, and are unchanged.

Claude-Session: https://claude.ai/code/session_01Ruh9nAQiuU5VGHV8uJCXtu
@sonarqubecloud

sonarqubecloud Bot commented Aug 5, 2026

Copy link
Copy Markdown

@matt-edmondson
matt-edmondson merged commit f62ead2 into main Aug 5, 2026
5 checks passed
@matt-edmondson
matt-edmondson deleted the fix/sonar-exclusions-and-workflow-permissions branch August 5, 2026 09:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant