fix(security): Resolve SonarQube security hotspots
SonarQube Analysis / sonarqube (pull_request) Successful in 3m9s

Introduce a default regex match timeout across the library to prevent potential ReDoS attacks (SonarQube rule S6444).
Implement `[OnDeserialized]` methods to re-establish object invariants and validate state after deserialization, addressing SonarQube rule S5766.
This commit is contained in:
Thom Lamb
2026-05-20 17:19:17 -05:00
parent df1805a402
commit e3153e58c4
26 changed files with 268 additions and 58 deletions
@@ -48,6 +48,18 @@ public abstract class SqlBreakdownBase : ISqlBreakdown
/// </summary>
public bool IsUsingFinishClause => FinishClauses.Count > 0;
/// <summary>
/// Re-establishes the invariants normally guaranteed by the constructors after the object
/// is reconstructed by deserialization. Deserialization bypasses constructors, so the
/// collection state must be re-validated to avoid a partially-initialized object
/// (SonarQube rule S5766).
/// </summary>
protected void RevalidateBreakdownState()
{
SetupClauses ??= new List<string>();
FinishClauses ??= new ArrayList();
}
/// <summary>
/// Gets the SQL breakdown as a string. Must be implemented by derived classes.
/// </summary>
@@ -1,3 +1,4 @@
using System.Runtime.Serialization;
using System.Text;
using Strata.SqlTools.SqlBreakdown.Interfaces;
using Strata.SqlTools.SqlBreakdown.Interfaces.QueryEngine;
@@ -35,6 +36,20 @@ public class SqlBreakdownCollection : ICollection<ISqlBreakdown>
_breakdowns = new List<ISqlBreakdown>(breakdowns ?? Enumerable.Empty<ISqlBreakdown>());
}
/// <summary>
/// Validates that the backing list survived deserialization, since deserialization bypasses
/// the constructors that normally initialize it (SonarQube rule S5766).
/// </summary>
/// <param name="context">The streaming context for the deserialization operation.</param>
[OnDeserialized]
private void OnDeserialized(StreamingContext context)
{
if (_breakdowns is null)
{
throw new SerializationException("Deserialized SqlBreakdownCollection is missing its backing list.");
}
}
/// <summary>
/// Gets the collection of SQL breakdowns.
/// </summary>
@@ -1,3 +1,4 @@
using System.Runtime.Serialization;
using System.Text;
using Strata.SqlTools.SqlBreakdown.Interfaces.Core;
@@ -58,6 +59,21 @@ public class SqlFilter : ISqlAppendable
}
}
/// <summary>
/// Validates that the expression and parameter state survived deserialization, since
/// deserialization bypasses the constructors that normally initialize them and enforce the
/// even parameter-name/value pairing (SonarQube rule S5766).
/// </summary>
/// <param name="context">The streaming context for the deserialization operation.</param>
[OnDeserialized]
private void OnDeserialized(StreamingContext context)
{
if (_sqlExpression is null || _parameterValues is null)
{
throw new SerializationException("Deserialized SqlFilter is missing its expression or parameter state.");
}
}
/// <summary>
/// Gets or sets the SQL expression.
/// </summary>