@@ -490,9 +490,13 @@ public void Dispose()
490490
491491 #region private ================================================================================
492492
493- // Note: "_" is allowed in SQL Server, but we normalize it to "-" for consistency with other DBs
494- private static readonly Regex s_replaceIndexNameCharsRegex = new ( @"[\s|\\|/|.|_|:]" ) ;
495- private const string ValidSeparator = "-" ;
493+ // Note: "_" is allowed in SQL Server and used as the safe separator
494+ private static readonly Regex s_replaceIndexNameCharsRegex = new ( @"[\s|\\|/|.|:]" ) ;
495+ private const string ValidSeparator = "_" ;
496+
497+ // Validation regex to ensure normalized index names contain only safe SQL identifier characters
498+ private static readonly Regex s_validIndexNameRegex = new ( @"^[a-z0-9_]+$" ) ;
499+ private const int MaxIndexNameLength = 128 ;
496500
497501 /// <summary>
498502 /// Prepare instance, ensuring tables exist and reusable info is cached.
@@ -586,6 +590,7 @@ private async Task<bool> DoesIndexExistsAsync(string indexName,
586590 /// <returns></returns>
587591 private string GetFullTableName ( string tableName )
588592 {
593+ ValidateTableName ( tableName ) ;
589594 return $ "[{ this . _config . Schema } ].[{ tableName } ]";
590595 }
591596
@@ -601,6 +606,9 @@ private string GenerateFilters(
601606 SqlParameterCollection parameters ,
602607 ICollection < MemoryFilter > ? filters = null )
603608 {
609+ // Validate index before using it in SQL construction (defense in depth)
610+ ValidateIndexName ( index ) ;
611+
604612 var filterBuilder = new StringBuilder ( ) ;
605613
606614 if ( filters is null || filters . Count <= 0 || filters . All ( f => f . Count <= 0 ) )
@@ -684,8 +692,61 @@ private static string NormalizeIndexName(string index)
684692
685693 index = s_replaceIndexNameCharsRegex . Replace ( index . Trim ( ) . ToLowerInvariant ( ) , ValidSeparator ) ;
686694
695+ // Validate the normalized index name
696+ ValidateIndexName ( index ) ;
697+
687698 return index ;
688699 }
689700
701+ /// <summary>
702+ /// Validates that an index name contains only safe SQL identifier characters.
703+ /// This prevents SQL injection when index names are used in dynamic SQL.
704+ /// </summary>
705+ /// <param name="index">The index name to validate.</param>
706+ /// <exception cref="ConfigurationException">Thrown if the index name is invalid.</exception>
707+ private static void ValidateIndexName ( string index )
708+ {
709+ if ( string . IsNullOrEmpty ( index ) )
710+ {
711+ throw new ConfigurationException ( "The index name is empty after normalization" ) ;
712+ }
713+
714+ if ( index . Length > MaxIndexNameLength )
715+ {
716+ throw new ConfigurationException ( $ "The index name '{ index } ' exceeds the maximum allowed length of { MaxIndexNameLength } characters") ;
717+ }
718+
719+ if ( ! s_validIndexNameRegex . IsMatch ( index ) )
720+ {
721+ throw new ConfigurationException ( $ "The index name '{ index } ' contains invalid characters. Only lowercase letters, numbers, and underscores are allowed") ;
722+ }
723+ }
724+
725+ /// <summary>
726+ /// Validates that a table name contains only safe SQL identifier characters.
727+ /// This prevents SQL injection when table names are used in dynamic SQL.
728+ /// </summary>
729+ /// <param name="tableName">The table name to validate.</param>
730+ /// <exception cref="ConfigurationException">Thrown if the table name is invalid.</exception>
731+ private static void ValidateTableName ( string tableName )
732+ {
733+ if ( string . IsNullOrWhiteSpace ( tableName ) )
734+ {
735+ throw new ConfigurationException ( "The table name is empty" ) ;
736+ }
737+
738+ if ( tableName . Length > MaxIndexNameLength )
739+ {
740+ throw new ConfigurationException ( $ "The table name '{ tableName } ' exceeds the maximum allowed length of { MaxIndexNameLength } characters") ;
741+ }
742+
743+ // Table names can contain letters, digits, underscores, and hyphens
744+ // This covers both base table names and index-suffixed table names like "TableName_indexname"
745+ if ( ! s_validIndexNameRegex . IsMatch ( tableName ) )
746+ {
747+ throw new ConfigurationException ( $ "The table name '{ tableName } ' contains invalid characters. Only lowercase letters, numbers, and underscores are allowed") ;
748+ }
749+ }
750+
690751 #endregion
691752}
0 commit comments