From cb25d1ae2b03128568ad1f7a02a6554a9e1cb6de Mon Sep 17 00:00:00 2001 From: Erlend Ellefsen Date: Sat, 24 Jan 2026 15:00:07 +0100 Subject: [PATCH 1/2] fix(security): prevent log forging and add workflow permissions - Sanitize user input before logging to prevent log forging attacks (3 high-severity fixes) - Add explicit least-privilege permissions to CI/CD workflow jobs (2 medium-severity fixes) - Closes CodeQL security alerts --- .config/dotnet-tools.json | 7 ---- .github/workflows/ci-cd.yml | 5 +++ .../Filtering/NestedPropertyNavigator.cs | 33 ++++++++++++++++--- 3 files changed, 34 insertions(+), 11 deletions(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index e400278..393d187 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -8,13 +8,6 @@ "dotnet-csharpier" ], "rollForward": false - }, - "dotnet-ef": { - "version": "9.0.2", - "commands": [ - "dotnet-ef" - ], - "rollForward": false } } } \ No newline at end of file diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 2a5be5b..115c73d 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -25,6 +25,8 @@ concurrency: jobs: build-and-test: runs-on: ubuntu-latest + permissions: + contents: read steps: - uses: actions/checkout@v4 @@ -52,6 +54,9 @@ jobs: needs: build-and-test if: github.event_name == 'release' runs-on: ubuntu-latest + permissions: + contents: read + packages: write steps: - uses: actions/checkout@v4 diff --git a/JsonApiToolkit/Extensions/Querying/Filtering/NestedPropertyNavigator.cs b/JsonApiToolkit/Extensions/Querying/Filtering/NestedPropertyNavigator.cs index 3b5a29e..1768fd9 100644 --- a/JsonApiToolkit/Extensions/Querying/Filtering/NestedPropertyNavigator.cs +++ b/JsonApiToolkit/Extensions/Querying/Filtering/NestedPropertyNavigator.cs @@ -1,12 +1,37 @@ using System.Linq.Expressions; using System.Reflection; +using System.Text.RegularExpressions; using JsonApiToolkit.Models.Querying.Filtering; using Microsoft.Extensions.Logging; namespace JsonApiToolkit.Extensions.Querying; -internal static class NestedPropertyNavigator +internal static partial class NestedPropertyNavigator { + private const int MaxLogValueLength = 100; + + /// + /// Sanitizes user input for safe logging by removing control characters + /// and truncating long values to prevent log forging attacks. + /// + private static string SanitizeForLog(string? value) + { + if (string.IsNullOrEmpty(value)) + return "(empty)"; + + // Remove control characters (newlines, tabs, etc.) that could forge log entries + string sanitized = ControlCharRegex().Replace(value, " "); + + // Truncate long values + if (sanitized.Length > MaxLogValueLength) + return string.Concat(sanitized.AsSpan(0, MaxLogValueLength), "...(truncated)"); + + return sanitized; + } + + [GeneratedRegex(@"[\x00-\x1F\x7F]")] + private static partial Regex ControlCharRegex(); + internal static Expression? BuildSafeNestedFilterExpression( ParameterExpression parameter, FilterParameter filter, @@ -268,7 +293,7 @@ internal static class NestedPropertyNavigator { logger?.LogWarning( "Failed to convert '{Value}' to {ElementType} for collection filter", - filter.Value, + SanitizeForLog(filter.Value), elementType.Name ); return null; @@ -295,7 +320,7 @@ internal static class NestedPropertyNavigator { logger?.LogWarning( "Failed to convert '{Value}' to {ElementType} for collection filter", - filter.Value, + SanitizeForLog(filter.Value), elementType.Name ); return null; @@ -412,7 +437,7 @@ internal static class NestedPropertyNavigator { logger?.LogWarning( "Failed to convert '{Value}' to {PropertyType}", - filter.Value, + SanitizeForLog(filter.Value), targetType.Name ); return null; From 42ae6713d6a5cc8857cbc042f755c28c475571fb Mon Sep 17 00:00:00 2001 From: Erlend Ellefsen Date: Sat, 24 Jan 2026 15:10:32 +0100 Subject: [PATCH 2/2] fix(security): prevent log forging and update tooling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Sanitize user input before logging to prevent log forging attacks - Add explicit least-privilege permissions to CI/CD workflow - Upgrade CSharpier 0.30.6 → 1.2.5 (new CLI syntax) - Remove unused dotnet-ef tool --- .config/dotnet-tools.json | 4 ++-- .github/workflows/ci-cd.yml | 2 +- .../Extensions/IncludeFilterParserTests.cs | 4 ++-- JsonApiToolkit.Tests/Extensions/QueryHelpersTests.cs | 12 ++++++------ JsonApiToolkit.Tests/JsonApiToolkit.Tests.csproj | 2 -- JsonApiToolkit/JsonApiToolkit.csproj | 8 +++++--- 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 393d187..cdb3c8d 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,9 +3,9 @@ "isRoot": true, "tools": { "csharpier": { - "version": "0.30.6", + "version": "1.2.5", "commands": [ - "dotnet-csharpier" + "csharpier" ], "rollForward": false } diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 115c73d..25c02ec 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -42,7 +42,7 @@ jobs: - name: Check formatting run: | dotnet tool restore - dotnet csharpier . --check + dotnet csharpier check . - name: Build run: dotnet build --no-restore --configuration Release diff --git a/JsonApiToolkit.Tests/Extensions/IncludeFilterParserTests.cs b/JsonApiToolkit.Tests/Extensions/IncludeFilterParserTests.cs index b2bd3a1..369842d 100644 --- a/JsonApiToolkit.Tests/Extensions/IncludeFilterParserTests.cs +++ b/JsonApiToolkit.Tests/Extensions/IncludeFilterParserTests.cs @@ -352,8 +352,8 @@ public void SeparateIncludeFilters_WithTooDeepNesting_ThrowsException() var includePaths = new List { "a.b.c.d" }; // Act & Assert - var exception = Assert.Throws( - () => IncludeFilterParser.SeparateIncludeFilters(filters, includePaths) + var exception = Assert.Throws(() => + IncludeFilterParser.SeparateIncludeFilters(filters, includePaths) ); Assert.Contains("Filter depth exceeds maximum", exception.Message); diff --git a/JsonApiToolkit.Tests/Extensions/QueryHelpersTests.cs b/JsonApiToolkit.Tests/Extensions/QueryHelpersTests.cs index 31749ed..c6a51e2 100644 --- a/JsonApiToolkit.Tests/Extensions/QueryHelpersTests.cs +++ b/JsonApiToolkit.Tests/Extensions/QueryHelpersTests.cs @@ -24,8 +24,8 @@ public void ConvertToPropertyType_WithValidEnumIgnoreCase_ConvertsCorrectly() [Fact] public void ConvertToPropertyType_WithInvalidEnum_ThrowsArgumentException() { - var exception = Assert.Throws( - () => QueryHelpers.ConvertToPropertyType("InvalidStatus", typeof(TestStatus)) + var exception = Assert.Throws(() => + QueryHelpers.ConvertToPropertyType("InvalidStatus", typeof(TestStatus)) ); Assert.Contains( @@ -45,8 +45,8 @@ public void ConvertToPropertyType_WithNullableEnum_ConvertsCorrectly() [Fact] public void ConvertToPropertyType_WithEmptyStringForEnum_ThrowsArgumentException() { - var exception = Assert.Throws( - () => QueryHelpers.ConvertToPropertyType("", typeof(TestStatus)) + var exception = Assert.Throws(() => + QueryHelpers.ConvertToPropertyType("", typeof(TestStatus)) ); Assert.Contains("Invalid enum value '' for type 'TestStatus'", exception.Message); @@ -71,8 +71,8 @@ public void ConvertToPropertyType_WithInt_ConvertsCorrectly() [Fact] public void ConvertToPropertyType_WithInvalidInt_ThrowsFormatException() { - var exception = Assert.Throws( - () => QueryHelpers.ConvertToPropertyType("not-a-number", typeof(int)) + var exception = Assert.Throws(() => + QueryHelpers.ConvertToPropertyType("not-a-number", typeof(int)) ); Assert.Contains( diff --git a/JsonApiToolkit.Tests/JsonApiToolkit.Tests.csproj b/JsonApiToolkit.Tests/JsonApiToolkit.Tests.csproj index 145e308..bb0654f 100644 --- a/JsonApiToolkit.Tests/JsonApiToolkit.Tests.csproj +++ b/JsonApiToolkit.Tests/JsonApiToolkit.Tests.csproj @@ -1,5 +1,4 @@  - net9.0 enable @@ -24,5 +23,4 @@ - diff --git a/JsonApiToolkit/JsonApiToolkit.csproj b/JsonApiToolkit/JsonApiToolkit.csproj index 165dd4e..1e21540 100644 --- a/JsonApiToolkit/JsonApiToolkit.csproj +++ b/JsonApiToolkit/JsonApiToolkit.csproj @@ -1,5 +1,4 @@  - net9.0 enable @@ -15,7 +14,7 @@ https://github.com/intility/Intility.JsonApiToolkit git README.md - + true @@ -25,7 +24,10 @@ - +