[Decision][SonarQube] Rule-level noise policy: CA1873 (3.5k), CA1861 (1.4k), S101 (663), S1192 (404), S1075 (74) — ~70% of open backend findings #643

Open
opened 2026-07-17 06:52:48 +00:00 by spikerj · 2 comments
Owner

Split from #638 (whose S927 fix half merged) so the decision doesn't get buried in a closed ticket. Full analysis in #638's body/comments; short version: five rule families contribute ~70% of all open backend findings, none realistically burned down issue-by-issue, all burying real signal (the 44 real BUGs were invisible under them until the triage loop).

Recommended (narrowest blast radius): scanner-side sonar.issue.ignore.multicriteria entries in sonar-scan.yml for CA1873 at minimum (precedent exists in that workflow; sonar-only; local builds unchanged; reversible one-liner). CA1861 same tier. S101/S1192/S1075 are judgment calls — S101 fires on the deliberate Marker_*/MongoDB_HealthCheck underscore style; S1075 is dominated by canonical constants (/var/run/docker.sock, Vault path delimiters).

Awaiting spikerj's call — one comment with the chosen subset and the loop implements it as a small PR.

Split from #638 (whose S927 fix half merged) so the decision doesn't get buried in a closed ticket. Full analysis in #638's body/comments; short version: five rule families contribute ~70% of all open backend findings, none realistically burned down issue-by-issue, all burying real signal (the 44 real BUGs were invisible under them until the triage loop). **Recommended (narrowest blast radius):** scanner-side `sonar.issue.ignore.multicriteria` entries in sonar-scan.yml for CA1873 at minimum (precedent exists in that workflow; sonar-only; local builds unchanged; reversible one-liner). CA1861 same tier. S101/S1192/S1075 are judgment calls — S101 fires on the deliberate Marker_*/MongoDB_HealthCheck underscore style; S1075 is dominated by canonical constants (/var/run/docker.sock, Vault path delimiters). Awaiting spikerj's call — one comment with the chosen subset and the loop implements it as a small PR.
Author
Owner

Adding a data-backed rule to the noise-policy list: csharpsquid:S3267 (loops should be simplified with LINQ, 45 findings) has a HIGH false-positive/degradation rate on this codebase. I hand-verified 5 representative sites: 4 are false positives where LINQ Where/Select cannot cleanly (or should not) express the loop — VramBudgetTracker (mutation + out-param + early return), StatusRollupMapper (max-reduction with a seed), ClaimAnonymousProgress (stateful HashSet-dedup side effect), JavaScriptCodeExecutionValidator (loop throws with the SPECIFIC blocked fragment; .Any() would lose that security diagnostic) — and only 1 (SpikerSoftRoles -> .Any()) was a genuine, low-value simplification. So ~80% of the sample are cases where following the rule would degrade or break code.

Recommendation: treat S3267 like the other readability-noise families — either deactivate in the quality profile or scanner-ignore it — rather than hand-triage 45 findings where the majority are traps. I FP'd the 4 verified false positives; the remaining ~40 each need per-loop reading, which is disproportionate for a readability rule with this FP rate. This is a policy call, not per-item work.

Current #643 candidate set (≈70% of all open backend findings): CA1873 (3.5k), CA1861 (1.4k), S101 (663), S1192 (404), S1075 (74), and now S3267 (45, high-FP).

Adding a data-backed rule to the noise-policy list: **csharpsquid:S3267 (loops should be simplified with LINQ, 45 findings)** has a HIGH false-positive/degradation rate on this codebase. I hand-verified 5 representative sites: 4 are false positives where LINQ Where/Select cannot cleanly (or should not) express the loop — VramBudgetTracker (mutation + out-param + early return), StatusRollupMapper (max-reduction with a seed), ClaimAnonymousProgress (stateful HashSet-dedup side effect), JavaScriptCodeExecutionValidator (loop throws with the SPECIFIC blocked fragment; .Any() would lose that security diagnostic) — and only 1 (SpikerSoftRoles -> .Any()) was a genuine, low-value simplification. So ~80% of the sample are cases where following the rule would degrade or break code. Recommendation: treat S3267 like the other readability-noise families — either deactivate in the quality profile or scanner-ignore it — rather than hand-triage 45 findings where the majority are traps. I FP'd the 4 verified false positives; the remaining ~40 each need per-loop reading, which is disproportionate for a readability rule with this FP rate. This is a policy call, not per-item work. Current #643 candidate set (≈70% of all open backend findings): CA1873 (3.5k), CA1861 (1.4k), S101 (663), S1192 (404), S1075 (74), and now S3267 (45, high-FP).
Author
Owner

Audited against origin/masterdecision still not recorded, but the mechanism and precedent already exist, so this is smaller than it looks. Also flagging something adjacent I found while checking.

No suppression for any of this ticket's five rules. .gitea/workflows/sonar-scan.yml:75 declares sonar.issue.ignore.multicriteria="secApi,secGameInit,secGameEvents" — three entries, none covering CA1873, CA1861, S101, S1192 or S1075. .editorconfig has no severity entries for them either. The ticket says "Awaiting spikerj's call" and has been untouched since filing on 2026-07-17, which matches.

The good news: the pattern is already established in this exact file. Each existing entry is three lines (ruleKey + resourceKey + a name in the list). Adding CA1873 and CA1861 is the same shape — so once you make the call, it's genuinely a one-PR change, not an investigation.


Adjacent finding worth its own look. The three existing suppressions are secret-detection rules being silenced on appsettings.json files:

entry rule file
secApi secrets:S7374 **/SpikerSoft.Api/appsettings.json
secGameInit secrets:S6694 **/SpikerSoft.GameServer.Initializer/appsettings.json
secGameEvents secrets:S6694 **/SpikerSoft.EventHandlers.GameEvents/appsettings.json

That means SonarQube's secret detection is switched off for three of the files most likely to contain committed credentials. For SpikerSoft.Api/appsettings.json that's currently defensible — I verified today that it's fully migrated (Influx, Keycloak, Stripe, Twilio, email, DNS all empty, Bao seam wired), so the finding was presumably stale and got quieted.

But the suppressions are file-scoped, not value-scoped, so they will also silence any future secret added to those files. And this is live rather than theoretical: #633 tracks six sets of committed plaintext credentials right now, and #857 confirms the registry serves anonymous pull tokens, so anything baked into an image is publicly readable.

I'm not proposing removing them — quieting a tracked, known finding is reasonable. But it's worth a deliberate check that these three are still justified, and worth knowing that "SonarQube is clean on secrets" doesn't currently mean what it appears to for those files. That may deserve a separate ticket rather than riding on this one.

Audited against `origin/master` — **decision still not recorded, but the mechanism and precedent already exist**, so this is smaller than it looks. Also flagging something adjacent I found while checking. **No suppression for any of this ticket's five rules.** `.gitea/workflows/sonar-scan.yml:75` declares `sonar.issue.ignore.multicriteria="secApi,secGameInit,secGameEvents"` — three entries, none covering CA1873, CA1861, S101, S1192 or S1075. `.editorconfig` has no severity entries for them either. The ticket says "Awaiting spikerj's call" and has been untouched since filing on 2026-07-17, which matches. **The good news: the pattern is already established in this exact file.** Each existing entry is three lines (`ruleKey` + `resourceKey` + a name in the list). Adding CA1873 and CA1861 is the same shape — so once you make the call, it's genuinely a one-PR change, not an investigation. --- **Adjacent finding worth its own look.** The three existing suppressions are **secret-detection** rules being silenced on `appsettings.json` files: | entry | rule | file | |---|---|---| | `secApi` | `secrets:S7374` | `**/SpikerSoft.Api/appsettings.json` | | `secGameInit` | `secrets:S6694` | `**/SpikerSoft.GameServer.Initializer/appsettings.json` | | `secGameEvents` | `secrets:S6694` | `**/SpikerSoft.EventHandlers.GameEvents/appsettings.json` | That means SonarQube's secret detection is switched off for three of the files most likely to contain committed credentials. For `SpikerSoft.Api/appsettings.json` that's currently defensible — I verified today that it's fully migrated (Influx, Keycloak, Stripe, Twilio, email, DNS all empty, Bao seam wired), so the finding was presumably stale and got quieted. But the suppressions are **file-scoped, not value-scoped**, so they will also silence any *future* secret added to those files. And this is live rather than theoretical: **#633** tracks six sets of committed plaintext credentials right now, and **#857** confirms the registry serves anonymous pull tokens, so anything baked into an image is publicly readable. I'm not proposing removing them — quieting a tracked, known finding is reasonable. But it's worth a deliberate check that these three are still justified, and worth knowing that "SonarQube is clean on secrets" doesn't currently mean what it appears to for those files. That may deserve a separate ticket rather than riding on this one.
Sign in to join this conversation.