From cb25d1ae2b03128568ad1f7a02a6554a9e1cb6de Mon Sep 17 00:00:00 2001 From: Erlend Ellefsen Date: Sat, 24 Jan 2026 15:00:07 +0100 Subject: [PATCH] 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;