From 48b766de09daa225b5044ddc6867b9699f744292 Mon Sep 17 00:00:00 2001 From: PFalkowski Date: Mon, 15 Jun 2026 09:27:49 +0200 Subject: [PATCH 1/4] v2.1.0: modernize, fix BufferedRadnomProvider typo, upgrade test stack, add CI/CD MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Phase 4: add BufferedRandomProvider (fixed spelling); keep BufferedRadnomProvider as [Obsolete] shim for backwards compatibility; rename file and test class to match - Phase 3: remove stale Resources.resx/Designer.cs refs from csproj; add SourceLink 8.0.0, LangVersion latest, Nullable enable, GenerateDocumentationFile, PackageReadmeFile; remove GeneratePackageOnBuild/PackageLicenseUrl/PackageReleaseNotes; update copyright 2018→2026; bump v2.0.0→2.1.0 - Test: netcoreapp2.0→net8.0; NSubstitute 3.1.0→5.3.0; xunit 2.3.1→2.9.2; TestSdk 15.5.0→17.12.0; add coverlet; fix nullable warnings; fix unused theory param - Phase 6: add ci.yml, publish.yml (OIDC Trusted Publishing), sonar.yml (with coverage) - README: full rewrite with CI/Sonar/NuGet/license/BMC badges, API table, usage examples - .gitattributes: *.yml text eol=lf Co-Authored-By: Claude Sonnet 4.6 --- .gitattributes | 1 + .github/workflows/ci.yml | 27 ++++ .github/workflows/publish.yml | 40 ++++++ .github/workflows/sonar.yml | 65 ++++++++++ ...rTest.cs => BufferedRandomProviderTest.cs} | 2 +- ...ensions.Standard.Randomization.Test.csproj | 25 ++-- .../StrongRandomTest.cs | 4 +- .../UtilitiesTest.cs | 6 +- .../BufferedRadnomProvider.cs | 56 +-------- .../BufferedRandomProvider.cs | 59 +++++++++ .../Extensions.Standard.Randomization.csproj | 30 ++--- .../StrongRandom.cs | 2 +- README.md | 118 ++++++++++++++++-- 13 files changed, 343 insertions(+), 92 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/publish.yml create mode 100644 .github/workflows/sonar.yml rename Extensions.Standard.Randomization.Test/{BufferedRadnomProviderTest.cs => BufferedRandomProviderTest.cs} (98%) create mode 100644 Extensions.Standard.Randomization/BufferedRandomProvider.cs diff --git a/.gitattributes b/.gitattributes index 1ff0c42..5b65514 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,6 +2,7 @@ # Set default behavior to automatically normalize line endings. ############################################################################### * text=auto +*.yml text eol=lf ############################################################################### # Set default behavior for command prompt diff. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..4005775 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build-and-test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Setup .NET + uses: actions/setup-dotnet@v5 + with: + dotnet-version: '8.x' + + - name: Restore + run: dotnet restore + + - name: Build + run: dotnet build --no-restore -c Release + + - name: Test + run: dotnet test --no-build -c Release --logger "trx;LogFileName=results.trx" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..b1d180a --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,40 @@ +name: Publish + +on: + push: + tags: + - 'v*' + +jobs: + publish: + runs-on: ubuntu-latest + permissions: + id-token: write + steps: + - uses: actions/checkout@v6 + + - name: Setup .NET + uses: actions/setup-dotnet@v5 + with: + dotnet-version: '8.x' + + - name: Restore + run: dotnet restore + + - name: Build + run: dotnet build --no-restore -c Release + + - name: Test + run: dotnet test --no-build -c Release + + - name: Pack + run: dotnet pack --no-build -c Release -o ./nupkg + + - name: Login to NuGet + uses: NuGet/login@v1 + with: + usernameVar: NUGET_USER + passwordVar: NUGET_TOKEN + + - name: Push to NuGet + run: dotnet nuget push ./nupkg/*.nupkg --source https://api.nuget.org/v3/index.json --api-key ${{ env.NUGET_TOKEN }} --skip-duplicate diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml new file mode 100644 index 0000000..1be21e2 --- /dev/null +++ b/.github/workflows/sonar.yml @@ -0,0 +1,65 @@ +name: SonarCloud + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + sonar: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Setup .NET + uses: actions/setup-dotnet@v5 + with: + dotnet-version: '8.x' + + - name: Setup Java (Sonar scanner requirement) + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: '17' + + - name: Cache SonarCloud packages + uses: actions/cache@v4 + with: + path: ~/.sonar/cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar + + - name: Install sonar scanner and coverage tool + run: | + dotnet tool install --global dotnet-sonarscanner + dotnet tool install --global dotnet-coverage + + - name: Restore + run: dotnet restore + + - name: Begin Sonar analysis + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + run: | + dotnet-sonarscanner begin \ + /k:"PFalkowski_StrongRandom" \ + /o:"pfalkowski" \ + /d:sonar.token="${{ secrets.SONAR_TOKEN }}" \ + /d:sonar.host.url="https://sonarcloud.io" \ + /d:sonar.cs.vscoveragexml.reportsPaths=coverage.xml \ + /d:sonar.exclusions="**/*.Test/**,**/*Test.cs" + + - name: Build + run: dotnet build --no-restore -c Release + + - name: Collect coverage + run: dotnet-coverage collect "dotnet test --no-build -c Release" -f xml -o coverage.xml + + - name: End Sonar analysis + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + run: dotnet-sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}" diff --git a/Extensions.Standard.Randomization.Test/BufferedRadnomProviderTest.cs b/Extensions.Standard.Randomization.Test/BufferedRandomProviderTest.cs similarity index 98% rename from Extensions.Standard.Randomization.Test/BufferedRadnomProviderTest.cs rename to Extensions.Standard.Randomization.Test/BufferedRandomProviderTest.cs index 9a8a9e7..6a9fb82 100644 --- a/Extensions.Standard.Randomization.Test/BufferedRadnomProviderTest.cs +++ b/Extensions.Standard.Randomization.Test/BufferedRandomProviderTest.cs @@ -4,7 +4,7 @@ namespace Extensions.Standard.Randomization.Test { - public class BufferedRadnomProviderTest + public class BufferedRandomProviderTest { [Theory] [InlineData(1)] diff --git a/Extensions.Standard.Randomization.Test/Extensions.Standard.Randomization.Test.csproj b/Extensions.Standard.Randomization.Test/Extensions.Standard.Randomization.Test.csproj index 1d93b08..27284e1 100644 --- a/Extensions.Standard.Randomization.Test/Extensions.Standard.Randomization.Test.csproj +++ b/Extensions.Standard.Randomization.Test/Extensions.Standard.Randomization.Test.csproj @@ -1,22 +1,27 @@  - - netcoreapp2.0 + + net8.0 + enable + false - - - - + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + - - - - diff --git a/Extensions.Standard.Randomization.Test/StrongRandomTest.cs b/Extensions.Standard.Randomization.Test/StrongRandomTest.cs index f7c53d5..000a5ae 100644 --- a/Extensions.Standard.Randomization.Test/StrongRandomTest.cs +++ b/Extensions.Standard.Randomization.Test/StrongRandomTest.cs @@ -221,8 +221,8 @@ public void NextBytesThrowsArgumentExceptionWhenNullArrayPassedIn() { var providerMock = new RandomProviderMock(0); var tested = new StrongRandom(providerMock); - byte[] nullBytesArray = null; - Assert.Throws(() => tested.NextBytes(nullBytesArray)); + byte[]? nullBytesArray = null; + Assert.Throws(() => tested.NextBytes(nullBytesArray!)); } [Fact] public void NextThrowsArgumentOutOfRangeExceptionWhenNegativeIntPassedIn() diff --git a/Extensions.Standard.Randomization.Test/UtilitiesTest.cs b/Extensions.Standard.Randomization.Test/UtilitiesTest.cs index f9912c3..4f1be05 100644 --- a/Extensions.Standard.Randomization.Test/UtilitiesTest.cs +++ b/Extensions.Standard.Randomization.Test/UtilitiesTest.cs @@ -117,10 +117,8 @@ public void NextLetterRetrunsValidResult() } } - [Theory] - [InlineData("test")] - [InlineData(".NETStandard")] - public void NextAlphanumericRetrunsValidResult(string toChooseFrom) + [Fact] + public void NextAlphanumericRetrunsValidResult() { var randomSubstitute = Substitute.For(); for (int i = '0'; i < '9' + 1; ++i) diff --git a/Extensions.Standard.Randomization/BufferedRadnomProvider.cs b/Extensions.Standard.Randomization/BufferedRadnomProvider.cs index 65cbe5f..672ab36 100644 --- a/Extensions.Standard.Randomization/BufferedRadnomProvider.cs +++ b/Extensions.Standard.Randomization/BufferedRadnomProvider.cs @@ -1,56 +1,12 @@ -using System; -using System.Security.Cryptography; +using System; namespace Extensions.Standard.Randomization { - /// - /// Internally uses RNGCryptoServiceProvider. Buffered call to RNGCryptoServiceProvider up to bufferSize - /// - public class BufferedRandomProvider : IRandomProvider + /// Preserved for binary compatibility. Use instead (fixed spelling). + [Obsolete("Use BufferedRandomProvider instead (typo in original name fixed). This class will be removed in a future major version.")] + public sealed class BufferedRadnomProvider : BufferedRandomProvider { - public BufferedRandomProvider(int bufferSize) - { - _bufer = new Lazy(() => new byte[bufferSize], true); - } - - private readonly Lazy _bufer; - private int _currentIndex; - - private void GetBytesInternal(byte[] buffer) - { - using (var csprng = RandomNumberGenerator.Create()) - { - csprng.GetBytes(buffer); - } - } - - private void RefreshBuffer() - { - GetBytesInternal(_bufer.Value); - _currentIndex = 0; - } - - public void GetBytes(byte[] input) - { - if (!_bufer.IsValueCreated) - { - RefreshBuffer(); - } - if (input.Length > _bufer.Value.Length) - { - GetBytesInternal(input); - return; - } - else if (input.Length + _currentIndex > _bufer.Value.Length) - { - RefreshBuffer(); - } - - for (var i = 0; i < input.Length; ++i) - { - input[i] = _bufer.Value[i + _currentIndex]; - } - _currentIndex += input.Length; - } + /// + public BufferedRadnomProvider(int bufferSize) : base(bufferSize) { } } } diff --git a/Extensions.Standard.Randomization/BufferedRandomProvider.cs b/Extensions.Standard.Randomization/BufferedRandomProvider.cs new file mode 100644 index 0000000..321ffec --- /dev/null +++ b/Extensions.Standard.Randomization/BufferedRandomProvider.cs @@ -0,0 +1,59 @@ +using System; +using System.Security.Cryptography; + +namespace Extensions.Standard.Randomization +{ + /// + /// Buffered wrapper around that amortises expensive CSPRNG calls + /// by filling an internal byte buffer in one shot and dispensing bytes from it on subsequent requests. + /// + public class BufferedRandomProvider : IRandomProvider + { + private readonly Lazy _buffer; + private int _currentIndex; + + /// Number of random bytes to pre-fetch per CSPRNG call. + public BufferedRandomProvider(int bufferSize) + { + _buffer = new Lazy(() => new byte[bufferSize], true); + } + + private static void GetBytesInternal(byte[] buffer) + { + using var csprng = RandomNumberGenerator.Create(); + csprng.GetBytes(buffer); + } + + private void RefreshBuffer() + { + GetBytesInternal(_buffer.Value); + _currentIndex = 0; + } + + /// Fills with cryptographically-strong random bytes. + public void GetBytes(byte[] input) + { + if (!_buffer.IsValueCreated) + { + RefreshBuffer(); + } + + if (input.Length > _buffer.Value.Length) + { + GetBytesInternal(input); + return; + } + + if (input.Length + _currentIndex > _buffer.Value.Length) + { + RefreshBuffer(); + } + + for (var i = 0; i < input.Length; ++i) + { + input[i] = _buffer.Value[i + _currentIndex]; + } + _currentIndex += input.Length; + } + } +} diff --git a/Extensions.Standard.Randomization/Extensions.Standard.Randomization.csproj b/Extensions.Standard.Randomization/Extensions.Standard.Randomization.csproj index 44a07ae..6329ff9 100644 --- a/Extensions.Standard.Randomization/Extensions.Standard.Randomization.csproj +++ b/Extensions.Standard.Randomization/Extensions.Standard.Randomization.csproj @@ -5,34 +5,30 @@ Extensions.Standard.Randomization Extensions.Standard.Randomization Piotr Falkowski - + Piotr Falkowski StrongRandom - Random interface implemented with Cryptographically-Secure Pseudo-Random Number Generator. Everywhere you use System.Random, StrongRandom can be used now. Additionally missing boilerplate extensions for any Random: NextBool, NextChar, NextString etc. added. - Piotr Falkowski © 2018 - https://opensource.org/licenses/MIT + System.Random drop-in backed by a Cryptographically-Secure Pseudo-Random Number Generator. Everywhere you use System.Random, StrongRandom can be used. Extension methods for any Random: NextBool, NextChar, NextByte, NextNormal and more. + Piotr Falkowski © 2026 + MIT git - Random, CSPRNG, Extension-methods - True + Random, CSPRNG, Extension-methods, security, cryptography https://github.com/PFalkowski/StrongRandom https://github.com/PFalkowski/StrongRandom StrongRandom - 2.0.0 - Update .NET Standard sdk to 2.0 + 2.1.0 + latest + enable + true + $(NoWarn);CS1591 + README.md - - True - True - Resources.resx - + - - ResXFileCodeGenerator - Resources.Designer.cs - + \ No newline at end of file diff --git a/Extensions.Standard.Randomization/StrongRandom.cs b/Extensions.Standard.Randomization/StrongRandom.cs index e2be6b4..7ae15ea 100644 --- a/Extensions.Standard.Randomization/StrongRandom.cs +++ b/Extensions.Standard.Randomization/StrongRandom.cs @@ -5,7 +5,7 @@ namespace Extensions.Standard.Randomization public class StrongRandom : Random { private readonly IRandomProvider _provider; - public StrongRandom(IRandomProvider provider = null) + public StrongRandom(IRandomProvider? provider = null) { _provider = provider ?? new BufferedRandomProvider(44); } diff --git a/README.md b/README.md index 641e422..b88409f 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,116 @@ # StrongRandom -[![NuGet version (StrongRandom)](https://img.shields.io/nuget/v/StrongRandom.svg)](https://www.nuget.org/packages/StrongRandom/) -[![Licence (StrongRandom)](https://img.shields.io/github/license/mashape/apistatus.svg)](https://choosealicense.com/licenses/mit/) +[![CI](https://github.com/PFalkowski/StrongRandom/actions/workflows/ci.yml/badge.svg)](https://github.com/PFalkowski/StrongRandom/actions/workflows/ci.yml) +[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=PFalkowski_StrongRandom&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=PFalkowski_StrongRandom) +[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=PFalkowski_StrongRandom&metric=coverage)](https://sonarcloud.io/summary/new_code?id=PFalkowski_StrongRandom) +[![NuGet version](https://img.shields.io/nuget/v/StrongRandom.svg)](https://www.nuget.org/packages/StrongRandom/) +[![NuGet downloads](https://img.shields.io/nuget/dt/StrongRandom.svg)](https://www.nuget.org/packages/StrongRandom/) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) +[![Buy Me A Coffee](https://img.shields.io/badge/Buy%20Me%20A%20Coffee-support-yellow.svg)](https://www.buymeacoffee.com/piotrfalkowski) -```System.Random``` interface implemented with Cryptographically-Secure Pseudo-Random Number Generator. -Everywhere you use System.Random, StrongRandom can be used. +`System.Random` drop-in backed by a **Cryptographically-Secure Pseudo-Random Number Generator (CSPRNG)**. Everywhere you use `System.Random`, `StrongRandom` can be used instead. Also ships extension methods that add `NextBool`, `NextByte`, `NextChar`, `NextFloat`, `NextNormal` and more to _any_ `Random` instance. -**Important! -Use with caution in security critical scenarios, as the distribution can be somewhat skewed in integer returning method calls and NextDouble(). GetBytes() is direct call to underlying RNG provider and is safe.** +> **Security note:** `NextBytes()` is a direct call to the underlying CSPRNG and is safe. Integer-returning methods (`Next`, `Next(max)`, `Next(min, max)`) and `NextDouble()` involve a transformation step that introduces a slight bias — do **not** rely on these for security-critical decisions such as key generation. Use `NextBytes()` for raw entropy. -Extensions for any Random: NextBool, NextChar, NextString etc. added. +## Installation + +``` +dotnet add package StrongRandom +``` + +## Quick start + +```csharp +using Extensions.Standard.Randomization; + +// Drop-in: assign to a System.Random variable +Random rng = new StrongRandom(); + +int dice = rng.Next(1, 7); // 1–6 +double d = rng.NextDouble(); // [0, 1) +byte[] key = new byte[32]; +rng.NextBytes(key); // CSPRNG-safe raw bytes +``` + +## Extension methods (`Utilities`) + +All extensions target `System.Random`, so they work with `StrongRandom`, plain `new Random()`, or a mock. + +| Method | Signature | Description | +|--------|-----------|-------------| +| `NextByte` | `(short upperLimit = 256)` | Random byte in `[0, upperLimit)` | +| `NextBool` | `()` | `true` or `false` with equal probability | +| `NextChar` | `(char lower = ' ', char upper = '\x7F')` | Random character in printable ASCII range | +| `NextChar` | `(string chooseFrom)` | Random character from a specific set | +| `NextLowercaseLetter` | `()` | Random letter `a`–`z` | +| `NextUppercaseLetter` | `()` | Random letter `A`–`Z` | +| `NextLetter` | `()` | Random letter `a`–`z` or `A`–`Z` | +| `NextAlphanumeric` | `()` | Random digit, uppercase, or lowercase letter | +| `NextFloat` | `()` | Random `float` in `[0, 1)` | +| `NextFloat` | `(float min, float max)` | Random `float` in `[min, max)` | +| `NextDouble` | `(double max)` | Random `double` in `[0, max)` | +| `NextDouble` | `(double min, double max)` | Random `double` in `[min, max)` | +| `NextNormal` | `(double mean, double sd = 1)` | Normally distributed value (Box-Muller) | + +```csharp +var rng = new StrongRandom(); + +bool coinFlip = rng.NextBool(); +byte b = rng.NextByte(); +char letter = rng.NextLetter(); +char alphanumeric = rng.NextAlphanumeric(); +float f = rng.NextFloat(0f, 1f); +double normal = rng.NextNormal(mean: 0, sd: 1); +char fromSet = rng.NextChar("AEIOU"); +``` + +## Customising the provider + +`StrongRandom` depends on `IRandomProvider` for its byte source. The default is `BufferedRandomProvider(44)`, which pre-fetches 44 bytes per CSPRNG call to reduce overhead. + +```csharp +// Larger buffer — fewer round-trips to the CSPRNG +var rng = new StrongRandom(new BufferedRandomProvider(256)); + +// Custom provider (e.g. for testing) +public class MyProvider : IRandomProvider +{ + public void GetBytes(byte[] input) { /* ... */ } +} +var testRng = new StrongRandom(new MyProvider()); +``` + +## API + +### `StrongRandom` + +Inherits from `System.Random`. All `Random` members work as expected; randomness comes from the CSPRNG provider. + +| Member | Notes | +|--------|-------| +| `StrongRandom(IRandomProvider? provider = null)` | `null` uses `BufferedRandomProvider(44)` | +| `Next()` | CSPRNG-backed | +| `Next(int maxValue)` | CSPRNG-backed | +| `Next(int minValue, int maxValue)` | CSPRNG-backed | +| `NextDouble()` | CSPRNG-backed | +| `NextBytes(byte[] buffer)` | Direct CSPRNG fill — unbiased | + +### `BufferedRandomProvider` + +| Member | Notes | +|--------|-------| +| `BufferedRandomProvider(int bufferSize)` | Pre-fetches `bufferSize` bytes per CSPRNG call | +| `GetBytes(byte[] input)` | Fills `input` from the buffer, refreshing as needed | + +### `IRandomProvider` + +```csharp +public interface IRandomProvider +{ + void GetBytes(byte[] input); +} +``` + +## License + +MIT — see [LICENSE](LICENSE). From f6e3ed354fe42d0c149679b98676daf86a06fea0 Mon Sep 17 00:00:00 2001 From: PFalkowski Date: Mon, 15 Jun 2026 23:32:21 +0200 Subject: [PATCH 2/4] fix(ci): pin GitHub Actions to full commit SHAs (supply-chain hardening) Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/ci.yml | 4 ++-- .github/workflows/publish.yml | 6 +++--- .github/workflows/sonar.yml | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4005775..e936c5b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,10 +10,10 @@ jobs: build-and-test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 - name: Setup .NET - uses: actions/setup-dotnet@v5 + uses: actions/setup-dotnet@9a946fdbd5fb07b82b2f5a4466058b876ab72bb2 # v5 with: dotnet-version: '8.x' diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index b1d180a..886a9d1 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -11,10 +11,10 @@ jobs: permissions: id-token: write steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 - name: Setup .NET - uses: actions/setup-dotnet@v5 + uses: actions/setup-dotnet@9a946fdbd5fb07b82b2f5a4466058b876ab72bb2 # v5 with: dotnet-version: '8.x' @@ -31,7 +31,7 @@ jobs: run: dotnet pack --no-build -c Release -o ./nupkg - name: Login to NuGet - uses: NuGet/login@v1 + uses: NuGet/login@8d196754b4036150537f80ac539e15c2f1028841 # v1 with: usernameVar: NUGET_USER passwordVar: NUGET_TOKEN diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml index 1be21e2..30d9cf6 100644 --- a/.github/workflows/sonar.yml +++ b/.github/workflows/sonar.yml @@ -10,23 +10,23 @@ jobs: sonar: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: fetch-depth: 0 - name: Setup .NET - uses: actions/setup-dotnet@v5 + uses: actions/setup-dotnet@9a946fdbd5fb07b82b2f5a4466058b876ab72bb2 # v5 with: dotnet-version: '8.x' - name: Setup Java (Sonar scanner requirement) - uses: actions/setup-java@v4 + uses: actions/setup-java@c1e323688fd81a25caa38c78aa6df2d33d3e20d9 # v4 with: distribution: temurin java-version: '17' - name: Cache SonarCloud packages - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: ~/.sonar/cache key: ${{ runner.os }}-sonar From a0f81a740d1495519f84edb256430c8b4b2c7a5f Mon Sep 17 00:00:00 2001 From: PFalkowski Date: Mon, 15 Jun 2026 23:37:23 +0200 Subject: [PATCH 3/4] fix(ci): use SONAR_TOKEN env var instead of inline secret expansion Avoids the SonarCloud 'Avoid expanding secrets in a run block' hotspot. The secret is already mapped to the env var on each step. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/sonar.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml index 30d9cf6..2aeef49 100644 --- a/.github/workflows/sonar.yml +++ b/.github/workflows/sonar.yml @@ -43,12 +43,12 @@ jobs: - name: Begin Sonar analysis env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + SONAR_TOKEN: $SONAR_TOKEN run: | dotnet-sonarscanner begin \ /k:"PFalkowski_StrongRandom" \ /o:"pfalkowski" \ - /d:sonar.token="${{ secrets.SONAR_TOKEN }}" \ + /d:sonar.token="$SONAR_TOKEN" \ /d:sonar.host.url="https://sonarcloud.io" \ /d:sonar.cs.vscoveragexml.reportsPaths=coverage.xml \ /d:sonar.exclusions="**/*.Test/**,**/*Test.cs" @@ -61,5 +61,5 @@ jobs: - name: End Sonar analysis env: - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - run: dotnet-sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}" + SONAR_TOKEN: $SONAR_TOKEN + run: dotnet-sonarscanner end /d:sonar.token="$SONAR_TOKEN" From 3b67a38c83f83b4e78e02325b75d4bd493c33726 Mon Sep 17 00:00:00 2001 From: PFalkowski Date: Mon, 15 Jun 2026 23:45:39 +0200 Subject: [PATCH 4/4] fix(ci): correctly wire SONAR_TOKEN secret to env block Previously the sed replacement incorrectly changed the env mapping line (SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}) to a bare var reference, or the env block was missing entirely. The scanner received an empty/literal token and authentication failed. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/sonar.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml index 2aeef49..6b244fb 100644 --- a/.github/workflows/sonar.yml +++ b/.github/workflows/sonar.yml @@ -43,7 +43,7 @@ jobs: - name: Begin Sonar analysis env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SONAR_TOKEN: $SONAR_TOKEN + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} run: | dotnet-sonarscanner begin \ /k:"PFalkowski_StrongRandom" \ @@ -61,5 +61,5 @@ jobs: - name: End Sonar analysis env: - SONAR_TOKEN: $SONAR_TOKEN + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} run: dotnet-sonarscanner end /d:sonar.token="$SONAR_TOKEN"