diff --git a/dotnet/Directory.Packages.props b/dotnet/Directory.Packages.props index e2e795ecd269..31a6cdbc7511 100644 --- a/dotnet/Directory.Packages.props +++ b/dotnet/Directory.Packages.props @@ -24,8 +24,8 @@ - - + + @@ -65,7 +65,7 @@ - + @@ -92,17 +92,20 @@ - - - - - - - + + + + + + + + + + - + @@ -110,9 +113,9 @@ - - - + + + @@ -120,10 +123,10 @@ - - + + - + @@ -139,7 +142,7 @@ - + @@ -152,7 +155,7 @@ - + diff --git a/dotnet/SK-release.slnf b/dotnet/SK-release.slnf index 1b497da3f66e..e3abf0b9db6e 100644 --- a/dotnet/SK-release.slnf +++ b/dotnet/SK-release.slnf @@ -3,74 +3,8 @@ "path": "SK-dotnet.slnx", "projects": [ - "src\\SemanticKernel.Abstractions\\SemanticKernel.Abstractions.csproj", - "src\\SemanticKernel.Core\\SemanticKernel.Core.csproj", - "src\\SemanticKernel.MetaPackage\\SemanticKernel.MetaPackage.csproj", - - "src\\Agents\\A2A\\Agents.A2A.csproj", - "src\\Agents\\Abstractions\\Agents.Abstractions.csproj", - "src\\Agents\\AzureAI\\Agents.AzureAI.csproj", - "src\\Agents\\Bedrock\\Agents.Bedrock.csproj", - "src\\Agents\\Copilot\\Agents.CopilotStudio.csproj", - "src\\Agents\\Core\\Agents.Core.csproj", - "src\\Agents\\Magentic\\Agents.Magentic.csproj", - "src\\Agents\\OpenAI\\Agents.OpenAI.csproj", - "src\\Agents\\Orchestration\\Agents.Orchestration.csproj", - "src\\Agents\\Yaml\\Agents.Yaml.csproj", - - "src\\Agents\\Runtime\\Abstractions\\Runtime.Abstractions.csproj", - "src\\Agents\\Runtime\\Core\\Runtime.Core.csproj", - "src\\Agents\\Runtime\\InProcess\\Runtime.InProcess.csproj", - - "src\\Connectors\\Connectors.Amazon\\Connectors.Amazon.csproj", - "src\\Connectors\\Connectors.AzureAIInference\\Connectors.AzureAIInference.csproj", - "src\\Connectors\\Connectors.AzureOpenAI\\Connectors.AzureOpenAI.csproj", - "src\\Connectors\\Connectors.Google\\Connectors.Google.csproj", - "src\\Connectors\\Connectors.HuggingFace\\Connectors.HuggingFace.csproj", - "src\\Connectors\\Connectors.MistralAI\\Connectors.MistralAI.csproj", - "src\\Connectors\\Connectors.Ollama\\Connectors.Ollama.csproj", - "src\\Connectors\\Connectors.Onnx\\Connectors.Onnx.csproj", - "src\\Connectors\\Connectors.OpenAI\\Connectors.OpenAI.csproj", - - "src\\VectorData\\AzureAISearch\\AzureAISearch.csproj", - "src\\VectorData\\Chroma\\Chroma.csproj", - "src\\VectorData\\CosmosMongoDB\\CosmosMongoDB.csproj", - "src\\VectorData\\CosmosNoSql\\CosmosNoSql.csproj", - "src\\VectorData\\InMemory\\InMemory.csproj", - "src\\VectorData\\Milvus\\Milvus.csproj", - "src\\VectorData\\MongoDB\\MongoDB.csproj", - "src\\VectorData\\PgVector\\PgVector.csproj", - "src\\VectorData\\Pinecone\\Pinecone.csproj", - "src\\VectorData\\Qdrant\\Qdrant.csproj", "src\\VectorData\\Redis\\Redis.csproj", - "src\\VectorData\\SqliteVec\\SqliteVec.csproj", "src\\VectorData\\SqlServer\\SqlServer.csproj", - "src\\VectorData\\VectorData.Abstractions\\VectorData.Abstractions.csproj", - "src\\VectorData\\Weaviate\\Weaviate.csproj", - - "src\\Experimental\\Orchestration.Flow\\Experimental.Orchestration.Flow.csproj", - - "src\\Experimental\\Process.Abstractions\\Process.Abstractions.csproj", - "src\\Experimental\\Process.Core\\Process.Core.csproj", - "src\\Experimental\\Process.LocalRuntime\\Process.LocalRuntime.csproj", - "src\\Experimental\\Process.Runtime.Dapr\\Process.Runtime.Dapr.csproj", - - "src\\Functions\\Functions.Grpc\\Functions.Grpc.csproj", - "src\\Functions\\Functions.OpenApi.Extensions\\Functions.OpenApi.Extensions.csproj", - "src\\Functions\\Functions.OpenApi\\Functions.OpenApi.csproj", - "src\\Functions\\Functions.Prompty\\Functions.Prompty.csproj", - "src\\Functions\\Functions.Yaml\\Functions.Yaml.csproj", - - "src\\Extensions\\PromptTemplates.Handlebars\\PromptTemplates.Handlebars.csproj", - "src\\Extensions\\PromptTemplates.Liquid\\PromptTemplates.Liquid.csproj", - - "src\\Plugins\\Plugins.AI\\Plugins.AI.csproj", - "src\\Plugins\\Plugins.Core\\Plugins.Core.csproj", - "src\\Plugins\\Plugins.Document\\Plugins.Document.csproj", - "src\\Plugins\\Plugins.Memory\\Plugins.Memory.csproj", - "src\\Plugins\\Plugins.MsGraph\\Plugins.MsGraph.csproj", - "src\\Plugins\\Plugins.StructuredData.EntityFramework\\Plugins.StructuredData.EntityFramework.csproj", - "src\\Plugins\\Plugins.Web\\Plugins.Web.csproj" ] } } diff --git a/dotnet/nuget/nuget-package.props b/dotnet/nuget/nuget-package.props index 016deb5eaf36..cf4203674e08 100644 --- a/dotnet/nuget/nuget-package.props +++ b/dotnet/nuget/nuget-package.props @@ -1,7 +1,7 @@ - 1.74.0 + 1.74.1 $(VersionPrefix)-$(VersionSuffix) $(VersionPrefix) diff --git a/dotnet/samples/Demos/TelemetryWithAppInsights/TelemetryWithAppInsights.csproj b/dotnet/samples/Demos/TelemetryWithAppInsights/TelemetryWithAppInsights.csproj index 2b245bf45b41..e75746d4b69e 100644 --- a/dotnet/samples/Demos/TelemetryWithAppInsights/TelemetryWithAppInsights.csproj +++ b/dotnet/samples/Demos/TelemetryWithAppInsights/TelemetryWithAppInsights.csproj @@ -13,6 +13,10 @@ + + + + diff --git a/dotnet/src/SemanticKernel.Core/Functions/KernelFunctionFromMethod.cs b/dotnet/src/SemanticKernel.Core/Functions/KernelFunctionFromMethod.cs index cc6d3eabf498..4dab37f0f8a4 100644 --- a/dotnet/src/SemanticKernel.Core/Functions/KernelFunctionFromMethod.cs +++ b/dotnet/src/SemanticKernel.Core/Functions/KernelFunctionFromMethod.cs @@ -720,7 +720,7 @@ private static (Func value.Replace("\"", "\\\"", StringComparison.Ordinal); + => value.Replace("\\", "\\\\", StringComparison.Ordinal).Replace("\"", "\\\"", StringComparison.Ordinal); #else - => value.Replace("\"", "\\\""); + => value.Replace("\\", "\\\\").Replace("\"", "\\\""); #endif } diff --git a/dotnet/src/VectorData/SqlServer/SqlServerCommandBuilder.cs b/dotnet/src/VectorData/SqlServer/SqlServerCommandBuilder.cs index eabfc794f34e..1da2a7422805 100644 --- a/dotnet/src/VectorData/SqlServer/SqlServerCommandBuilder.cs +++ b/dotnet/src/VectorData/SqlServer/SqlServerCommandBuilder.cs @@ -31,7 +31,7 @@ internal static List CreateTable( if (ifNotExists) { sb.Append("IF OBJECT_ID(N'"); - sb.AppendTableName(schema, tableName); + sb.AppendTableNameInsideLiteral(schema, tableName); sb.AppendLine("', N'U') IS NULL"); } sb.AppendLine("BEGIN"); @@ -125,22 +125,22 @@ internal static List CreateTable( // Full-text indexes require a unique index (we use the primary key) sb.AppendLine("DECLARE @pkIndexName NVARCHAR(128);"); sb.Append("SELECT @pkIndexName = name FROM sys.indexes WHERE object_id = OBJECT_ID(N'"); - sb.AppendTableName(schema, tableName); + sb.AppendTableNameInsideLiteral(schema, tableName); sb.AppendLine("') AND is_primary_key = 1;"); sb.AppendLine("DECLARE @ftSql NVARCHAR(MAX);"); sb.Append("SET @ftSql = N'CREATE FULLTEXT INDEX ON "); - sb.AppendTableName(schema, tableName).Append(" ("); + sb.AppendTableNameInsideLiteral(schema, tableName).Append(" ("); for (int i = 0; i < fullTextProperties.Count; i++) { - sb.AppendIdentifier(fullTextProperties[i].StorageName); + sb.AppendIdentifierInsideLiteral(fullTextProperties[i].StorageName); if (i < fullTextProperties.Count - 1) { sb.Append(','); } } sb.Append(") KEY INDEX ' + QUOTENAME(@pkIndexName) + N' ON "); - sb.AppendIdentifier(catalogName).AppendLine("';"); + sb.AppendIdentifierInsideLiteral(catalogName).AppendLine("';"); sb.AppendLine("EXEC sp_executesql @ftSql;"); } @@ -864,6 +864,30 @@ internal static StringBuilder AppendIdentifier(this StringBuilder sb, string ide return sb; } + /// + /// Same as , but for use inside a SQL string literal (N'...'), + /// where single quotes must be escaped by doubling them. + /// + internal static StringBuilder AppendTableNameInsideLiteral(this StringBuilder sb, string? schema, string tableName) + { + int start = sb.Length; + sb.AppendTableName(schema, tableName); + sb.Replace("'", "''", start, sb.Length - start); + return sb; + } + + /// + /// Same as , but for use inside a SQL string literal (N'...'), + /// where single quotes must be escaped by doubling them. + /// + internal static StringBuilder AppendIdentifierInsideLiteral(this StringBuilder sb, string identifier) + { + int start = sb.Length; + sb.AppendIdentifier(identifier); + sb.Replace("'", "''", start, sb.Length - start); + return sb; + } + private static StringBuilder AppendIdentifiers(this StringBuilder sb, IEnumerable properties, string? prefix = null, diff --git a/dotnet/test/VectorData/Redis.UnitTests/RedisFilterTranslatorTests.cs b/dotnet/test/VectorData/Redis.UnitTests/RedisFilterTranslatorTests.cs index 789a1c25142e..3b937aa3fe4e 100644 --- a/dotnet/test/VectorData/Redis.UnitTests/RedisFilterTranslatorTests.cs +++ b/dotnet/test/VectorData/Redis.UnitTests/RedisFilterTranslatorTests.cs @@ -108,6 +108,58 @@ public void Equal_with_double_quote_in_value() Assert.Equal("""@Name:{"foo\"bar"}""", result); } + [Fact] + public void Equal_with_backslash_in_value() + { + var result = Translate(r => r.Name == "foo\\bar"); + Assert.Equal("""@Name:{"foo\\bar"}""", result); + } + + [Fact] + public void Equal_with_single_backslash() + { + var result = Translate(r => r.Name == "\\"); + Assert.Equal("""@Name:{"\\"}""", result); + } + + [Fact] + public void Equal_with_backslash_quote_injection_attempt() + { + // Input: \" which should NOT break out of the quoted string + var result = Translate(r => r.Name == "\\\""); + Assert.Equal("""@Name:{"\\\""}""", result); + } + + [Fact] + public void Equal_with_backslash_quote_wildcard_injection() + { + // The specific attack payload: \" | * | \" + var result = Translate(r => r.Name == "\\\" | * | \\\""); + Assert.Equal("""@Name:{"\\\" | * | \\\""}""", result); + } + + [Fact] + public void Contains_with_backslash_in_value() + { + var result = Translate(r => r.Tags.Contains("foo\\bar")); + Assert.Equal("""@Tags:{"foo\\bar"}""", result); + } + + [Fact] + public void Contains_with_backslash_quote_injection_attempt() + { + var result = Translate(r => r.Tags.Contains("\\\"")); + Assert.Equal("""@Tags:{"\\\""}""", result); + } + + [Fact] + public void Any_with_backslash_in_values() + { + var values = new[] { "a\\b", "c\\d" }; + var result = Translate(r => r.Tags.Any(t => values.Contains(t))); + Assert.Equal("""@Tags:{"a\\b" | "c\\d"}""", result); + } + private static string Translate(Expression> filter) { var model = BuildModel(); diff --git a/dotnet/test/VectorData/SqlServer.ConformanceTests/SqlServerCommandBuilderTests.cs b/dotnet/test/VectorData/SqlServer.ConformanceTests/SqlServerCommandBuilderTests.cs index b78a010d6b4c..5d56967ed1ca 100644 --- a/dotnet/test/VectorData/SqlServer.ConformanceTests/SqlServerCommandBuilderTests.cs +++ b/dotnet/test/VectorData/SqlServer.ConformanceTests/SqlServerCommandBuilderTests.cs @@ -27,6 +27,36 @@ public void AppendTableName(string? schema, string table, string expectedFullNam Assert.Equal(expectedFullName, result.ToString()); } + [Theory] + [InlineData("schema", "name", "[schema].[name]")] + [InlineData(null, "name", "[name]")] + [InlineData("schema", "it's", "[schema].[it''s]")] + [InlineData("it's", "name", "[it''s].[name]")] + [InlineData("it's", "it's", "[it''s].[it''s]")] + [InlineData(null, "it's", "[it''s]")] + [InlineData("schema", "[brackets]", "[schema].[[brackets]]]")] + public void AppendTableNameInsideLiteral(string? schema, string table, string expectedFullName) + { + StringBuilder result = new(); + + SqlServerCommandBuilder.AppendTableNameInsideLiteral(result, schema, table); + + Assert.Equal(expectedFullName, result.ToString()); + } + + [Theory] + [InlineData("name", "[name]")] + [InlineData("it's", "[it''s]")] + [InlineData("two''quotes", "[two''''quotes]")] + public void AppendIdentifierInsideLiteral(string identifier, string expected) + { + StringBuilder result = new(); + + SqlServerCommandBuilder.AppendIdentifierInsideLiteral(result, identifier); + + Assert.Equal(expected, result.ToString()); + } + [Theory] [InlineData("name", "@name_")] // typical name [InlineData("na me", "@na_")] // contains a whitespace, an illegal parameter name character @@ -149,6 +179,34 @@ PRIMARY KEY ([id]) Assert.Equal(expectedCommand, command.CommandText, ignoreLineEndingDifferences: true); } + [Fact] + public void CreateTable_WithSingleQuoteInName() + { + var model = BuildModel( + [ + new VectorStoreKeyProperty("id", typeof(long)), + new VectorStoreDataProperty("name", typeof(string)), + ]); + + using SqlConnection connection = CreateConnection(); + + var commands = SqlServerCommandBuilder.CreateTable(connection, "it's", "ta'ble", ifNotExists: true, model); + + var command = Assert.Single(commands); + Assert.Equal( + "IF OBJECT_ID(N'[it''s].[ta''ble]', N'U') IS NULL" + Environment.NewLine + + """ + BEGIN + CREATE TABLE [it's].[ta'ble] ( + [id] BIGINT IDENTITY, + [name] NVARCHAR(MAX), + PRIMARY KEY ([id]) + ); + END; + """, + command.CommandText, ignoreLineEndingDifferences: true); + } + [Fact] public void CreateTable_WithDiskAnnIndex() {