fix(security): Resolve SonarQube security hotspots
SonarQube Analysis / sonarqube (pull_request) Successful in 3m9s
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:
@@ -8,6 +8,12 @@ namespace Strata.SqlTools.Rules.Rule.Expression;
|
||||
/// </summary>
|
||||
public static class Markdown
|
||||
{
|
||||
/// <summary>
|
||||
/// Match timeout applied to all regular expressions to guard against catastrophic
|
||||
/// backtracking / ReDoS denial-of-service attacks (SonarQube rule S6444).
|
||||
/// </summary>
|
||||
private static readonly TimeSpan RegexTimeout = TimeSpan.FromSeconds(1);
|
||||
|
||||
private static readonly Dictionary<string, Func<Expression, Expression, BoolExpr>> LogicalOperators = new()
|
||||
{
|
||||
{ "\\land", (left, right) => new And((BoolExpr)left, (BoolExpr)right) },
|
||||
@@ -70,12 +76,12 @@ public static class Markdown
|
||||
private static string StripMarkdownDelimiters(string text)
|
||||
{
|
||||
// Remove $...$ or $$...$$ delimiters
|
||||
text = Regex.Replace(text, @"^\$\$?\s*", "");
|
||||
text = Regex.Replace(text, @"\s*\$\$?$", "");
|
||||
text = Regex.Replace(text, @"^\$\$?\s*", "", RegexOptions.None, RegexTimeout);
|
||||
text = Regex.Replace(text, @"\s*\$\$?$", "", RegexOptions.None, RegexTimeout);
|
||||
|
||||
// Remove ```math...``` code fence
|
||||
text = Regex.Replace(text, @"^```math\s*", "", RegexOptions.Multiline);
|
||||
text = Regex.Replace(text, @"\s*```$", "", RegexOptions.Multiline);
|
||||
text = Regex.Replace(text, @"^```math\s*", "", RegexOptions.Multiline, RegexTimeout);
|
||||
text = Regex.Replace(text, @"\s*```$", "", RegexOptions.Multiline, RegexTimeout);
|
||||
|
||||
return text.Trim();
|
||||
}
|
||||
@@ -186,18 +192,18 @@ public static class Markdown
|
||||
private static Expression? TryParseProperty(string text)
|
||||
{
|
||||
// Parse property access (e.g., x.PropertyName or \text{x.PropertyName})
|
||||
var propertyMatch = Regex.Match(text, @"^([a-zA-Z_][a-zA-Z0-9_]*)\.([a-zA-Z_][a-zA-Z0-9_]*)$");
|
||||
var propertyMatch = Regex.Match(text, @"^([a-zA-Z_][a-zA-Z0-9_]*)\.([a-zA-Z_][a-zA-Z0-9_]*)$", RegexOptions.None, RegexTimeout);
|
||||
if (propertyMatch.Success)
|
||||
{
|
||||
return new Property(propertyMatch.Groups[1].Value, propertyMatch.Groups[2].Value);
|
||||
}
|
||||
|
||||
// Parse \text{...} property access
|
||||
var textMatch = Regex.Match(text, @"^\\text\{([^}]+)\}$");
|
||||
var textMatch = Regex.Match(text, @"^\\text\{([^}]+)\}$", RegexOptions.None, RegexTimeout);
|
||||
if (textMatch.Success)
|
||||
{
|
||||
var textContent = textMatch.Groups[1].Value;
|
||||
var propMatch = Regex.Match(textContent, @"^([a-zA-Z_][a-zA-Z0-9_]*)\.([a-zA-Z_][a-zA-Z0-9_]*)$");
|
||||
var propMatch = Regex.Match(textContent, @"^([a-zA-Z_][a-zA-Z0-9_]*)\.([a-zA-Z_][a-zA-Z0-9_]*)$", RegexOptions.None, RegexTimeout);
|
||||
if (propMatch.Success)
|
||||
{
|
||||
return new Property(propMatch.Groups[1].Value, propMatch.Groups[2].Value);
|
||||
@@ -215,7 +221,7 @@ public static class Markdown
|
||||
}
|
||||
|
||||
// Single property name
|
||||
if (Regex.IsMatch(textContent, @"^[a-zA-Z_][a-zA-Z0-9_]*$"))
|
||||
if (Regex.IsMatch(textContent, @"^[a-zA-Z_][a-zA-Z0-9_]*$", RegexOptions.None, RegexTimeout))
|
||||
{
|
||||
return new Property(textContent);
|
||||
}
|
||||
@@ -236,7 +242,7 @@ public static class Markdown
|
||||
}
|
||||
|
||||
// Parse simple property without parameter
|
||||
if (Regex.IsMatch(text, @"^[a-zA-Z_][a-zA-Z0-9_]*$"))
|
||||
if (Regex.IsMatch(text, @"^[a-zA-Z_][a-zA-Z0-9_]*$", RegexOptions.None, RegexTimeout))
|
||||
{
|
||||
return new Property(text);
|
||||
}
|
||||
@@ -247,7 +253,7 @@ public static class Markdown
|
||||
private static Expression? TryParseLiteral(string text)
|
||||
{
|
||||
// Parse string literals (quoted)
|
||||
var stringMatch = Regex.Match(text, @"^[""'](.+?)[""']$");
|
||||
var stringMatch = Regex.Match(text, @"^[""'](.+?)[""']$", RegexOptions.None, RegexTimeout);
|
||||
if (stringMatch.Success)
|
||||
{
|
||||
return new StringLiteral(stringMatch.Groups[1].Value);
|
||||
|
||||
Reference in New Issue
Block a user