@@ -48,6 +48,9 @@ public sealed class SqlServerMemory : IMemoryDb, IMemoryDbUpsertBatch, IDisposab
4848 /// SQL Server version, retrieved on the first connection
4949 /// </summary>
5050 private int _cachedServerVersion = int . MinValue ;
51+ // Accepts only [a-zA-Z_][a-zA-Z0-9_]{0,127}
52+ private static readonly Regex s_safeSqlIdentifierRegex = new Regex ( @"^[a-zA-Z_][a-zA-Z0-9_]{0,127}$" , RegexOptions . Compiled ) ;
53+
5154
5255 /// <summary>
5356 /// Initializes a new instance of the <see cref="SqlServerMemory"/> class.
@@ -78,6 +81,7 @@ public async Task CreateIndexAsync(string index, int vectorSize, CancellationTok
7881 return ;
7982 }
8083
84+ // lgtm[cs/sql-injection] Index name sanitized by NormalizeIndexName with regex ^[a-zA-Z_][a-zA-Z0-9_]{0,127}$
8185 var sql = $@ "
8286 BEGIN TRANSACTION;
8387
@@ -139,6 +143,7 @@ public async Task DeleteAsync(string index, MemoryRecord record, CancellationTok
139143 return ;
140144 }
141145
146+ // lgtm[cs/sql-injection] Index name sanitized by NormalizeIndexName with regex ^[a-zA-Z_][a-zA-Z0-9_]{0,127}$
142147 var sql = $@ "
143148 BEGIN TRANSACTION;
144149
@@ -192,6 +197,7 @@ public async Task DeleteIndexAsync(string index, CancellationToken cancellationT
192197 return ;
193198 }
194199
200+ // lgtm[cs/sql-injection] Index name sanitized by NormalizeIndexName with regex ^[a-zA-Z_][a-zA-Z0-9_]{0,127}$
195201 var sql = $@ "
196202 BEGIN TRANSACTION;
197203
@@ -285,6 +291,7 @@ public async IAsyncEnumerable<MemoryRecord> GetListAsync(
285291 {
286292 var tagFilters = new TagCollection ( ) ;
287293
294+ // lgtm[cs/sql-injection] Index name sanitized by NormalizeIndexName with regex ^[a-zA-Z_][a-zA-Z0-9_]{0,127}$
288295 command . CommandText = $@ "
289296 WITH [filters] AS
290297 (
@@ -359,6 +366,7 @@ SELECT TOP (@limit)
359366 try
360367 {
361368 var generatedFilters = this . GenerateFilters ( index , command . Parameters , filters ) ;
369+ // lgtm[cs/sql-injection] Index name sanitized by NormalizeIndexName with regex ^[a-zA-Z_][a-zA-Z0-9_]{0,127}$
362370 command . CommandText = $@ "
363371 WITH
364372 [embedding] as
@@ -455,6 +463,7 @@ public async IAsyncEnumerable<string> UpsertBatchAsync(string index, IEnumerable
455463 throw new IndexNotFoundException ( $ "The index '{ index } ' does not exist.") ;
456464 }
457465
466+ // lgtm[cs/sql-injection] Index name sanitized by NormalizeIndexName with regex ^[a-zA-Z_][a-zA-Z0-9_]{0,127}$
458467 var sql = $@ "
459468 BEGIN TRANSACTION;
460469
@@ -606,6 +615,7 @@ private async Task CacheSqlServerMajorVersionNumberAsync(CancellationToken cance
606615 /// <returns></returns>
607616 private async Task CreateTablesIfNotExistsAsync ( CancellationToken cancellationToken )
608617 {
618+ // lgtm[cs/sql-injection] Schema and table names from configuration, not user input
609619 var sql = $@ "IF NOT EXISTS (SELECT *
610620 FROM sys.schemas
611621 WHERE name = N'{ this . _config . Schema } ' )
@@ -709,6 +719,7 @@ private string GenerateFilters(
709719
710720 filterBuilder . Append ( " ( " ) ;
711721
722+ // lgtm[cs/sql-injection] Index name sanitized by NormalizeIndexName with regex ^[a-zA-Z_][a-zA-Z0-9_]{0,127}$
712723 filterBuilder . Append ( CultureInfo . CurrentCulture , $@ "EXISTS (
713724 SELECT
714725 1
@@ -763,6 +774,11 @@ private static string NormalizeIndexName(string index)
763774
764775 index = s_replaceIndexNameCharsRegex . Replace ( index . Trim ( ) . ToLowerInvariant ( ) , ValidSeparator ) ;
765776
777+ // Only allow index names that are valid SQL identifiers (start with a letter or underscore, followed by letters, digits, or underscores, max 128 chars)
778+ if ( ! s_safeSqlIdentifierRegex . IsMatch ( index ) )
779+ {
780+ throw new ArgumentException ( "Invalid index name. Allowed: letters, digits, underscores, max length 128, cannot start with digit." , nameof ( index ) ) ;
781+ }
766782 return index ;
767783 }
768784
0 commit comments