diff --git a/dotnet/WorkbenchConnector/AgentBase.cs b/dotnet/WorkbenchConnector/AgentBase.cs index f2cfbead..8249a2d8 100644 --- a/dotnet/WorkbenchConnector/AgentBase.cs +++ b/dotnet/WorkbenchConnector/AgentBase.cs @@ -93,7 +93,7 @@ public virtual async Task UpdateAgentConfigAsync( IAgentConfig? config, CancellationToken cancellationToken = default) { - this.Log.LogDebug("Updating agent '{0}' config", this.Id); + this.Log.LogDebug("Updating agent '{0}' config", this.Id.HtmlEncode()); this.RawConfig ??= this.GetDefaultConfig(); config ??= this.GetDefaultConfig(); @@ -143,7 +143,8 @@ public virtual async Task CreateConversationAsync( string conversationId, CancellationToken cancellationToken = default) { - this.Log.LogDebug("Creating conversation '{0}' on agent '{1}'", conversationId, this.Id); + this.Log.LogDebug("Creating conversation '{0}' on agent '{1}'", + conversationId.HtmlEncode(), this.Id.HtmlEncode()); Conversation conversation = await this.Storage.GetConversationAsync(conversationId, this.Id, cancellationToken).ConfigureAwait(false) ?? new Conversation(conversationId, this.Id); @@ -164,7 +165,8 @@ public virtual Task DeleteConversationAsync( string conversationId, CancellationToken cancellationToken = default) { - this.Log.LogDebug("Deleting conversation '{0}' on agent '{1}'", conversationId, this.Id); + this.Log.LogDebug("Deleting conversation '{0}' on agent '{1}'", + conversationId.HtmlEncode(), this.Id.HtmlEncode()); return this.Storage.DeleteConversationAsync(conversationId, this.Id, cancellationToken); } @@ -178,7 +180,8 @@ public virtual async Task ConversationExistsAsync( string conversationId, CancellationToken cancellationToken = default) { - this.Log.LogDebug("Checking if conversation '{0}' on agent '{1}' exists", conversationId, this.Id); + this.Log.LogDebug("Checking if conversation '{0}' on agent '{1}' exists", + conversationId.HtmlEncode(), this.Id.HtmlEncode()); var conversation = await this.Storage.GetConversationAsync(conversationId, this.Id, cancellationToken).ConfigureAwait(false); return conversation != null; } @@ -194,7 +197,8 @@ public virtual async Task AddParticipantAsync( Participant participant, CancellationToken cancellationToken = default) { - this.Log.LogDebug("Adding participant to conversation '{0}' on agent '{1}'", conversationId, this.Id); + this.Log.LogDebug("Adding participant to conversation '{0}' on agent '{1}'", + conversationId.HtmlEncode(), this.Id.HtmlEncode()); Conversation conversation = await this.Storage.GetConversationAsync(conversationId, this.Id, cancellationToken).ConfigureAwait(false) ?? new Conversation(conversationId, this.Id); @@ -214,7 +218,8 @@ public virtual async Task RemoveParticipantAsync( Participant participantUpdatedEvent, CancellationToken cancellationToken = default) { - this.Log.LogDebug("Removing participant from conversation '{0}' on agent '{1}'", conversationId, this.Id); + this.Log.LogDebug("Removing participant from conversation '{0}' on agent '{1}'", + conversationId.HtmlEncode(), this.Id.HtmlEncode()); Conversation? conversation = await this.Storage.GetConversationAsync(conversationId, this.Id, cancellationToken).ConfigureAwait(false); if (conversation == null) { return; } @@ -235,7 +240,7 @@ public virtual Task ReceiveMessageAsync( CancellationToken cancellationToken = default) { this.Log.LogDebug("Received {0} chat message in conversation '{1}' with agent '{2}' from '{3}' '{4}'", - message.ContentType, conversationId, this.Id, message.Sender.Role, message.Sender.Id); + message.ContentType.HtmlEncode(), conversationId.HtmlEncode(), this.Id.HtmlEncode(), message.Sender.Role.HtmlEncode(), message.Sender.Id.HtmlEncode()); // Update the chat history to include the message received return this.AddMessageToHistoryAsync(conversationId, message, cancellationToken); @@ -255,7 +260,7 @@ public virtual Task ReceiveNoticeAsync( CancellationToken cancellationToken = default) { this.Log.LogDebug("Received {0} notice in conversation '{1}' with agent '{2}' from '{3}' '{4}': {5}", - message.ContentType, conversationId, this.Id, message.Sender.Role, message.Sender.Id, message.Content); + message.ContentType.HtmlEncode(), conversationId.HtmlEncode(), this.Id.HtmlEncode(), message.Sender.Role.HtmlEncode(), message.Sender.Id.HtmlEncode(), message.Content.HtmlEncode()); return Task.CompletedTask; } @@ -273,7 +278,7 @@ public virtual Task ReceiveNoteAsync( CancellationToken cancellationToken = default) { this.Log.LogDebug("Received {0} note in conversation '{1}' with agent '{2}' from '{3}' '{4}': {5}", - message.ContentType, conversationId, this.Id, message.Sender.Role, message.Sender.Id, message.Content); + message.ContentType.HtmlEncode(), conversationId.HtmlEncode(), this.Id.HtmlEncode(), message.Sender.Role.HtmlEncode(), message.Sender.Id.HtmlEncode(), message.Content.HtmlEncode()); return Task.CompletedTask; } @@ -290,7 +295,7 @@ public virtual Task ReceiveCommandAsync( CancellationToken cancellationToken = default) { this.Log.LogDebug("Received '{0}' command in conversation '{1}' with agent '{2}' from '{3}' '{4}': {5}", - command.CommandName, conversationId, this.Id, command.Sender.Role, command.Sender.Id, command.Content); + command.CommandName.HtmlEncode(), conversationId.HtmlEncode(), this.Id.HtmlEncode(), command.Sender.Role.HtmlEncode(), command.Sender.Id.HtmlEncode(), command.Content.HtmlEncode()); return Task.CompletedTask; } @@ -307,7 +312,7 @@ public virtual Task ReceiveCommandResponseAsync( CancellationToken cancellationToken = default) { this.Log.LogDebug("Received {0} command response in conversation '{1}' with agent '{2}' from '{3}' '{4}': {5}", - message.ContentType, conversationId, this.Id, message.Sender.Role, message.Sender.Id, message.Content); + message.ContentType.HtmlEncode(), conversationId.HtmlEncode(), this.Id.HtmlEncode(), message.Sender.Role.HtmlEncode(), message.Sender.Id.HtmlEncode(), message.Content.HtmlEncode()); return Task.CompletedTask; } @@ -324,7 +329,7 @@ public virtual async Task DeleteMessageAsync( CancellationToken cancellationToken = default) { this.Log.LogDebug("Deleting message in conversation '{0}' with agent '{1}', message from '{2}' '{3}'", - conversationId, this.Id, message.Sender.Role, message.Sender.Id); + conversationId.HtmlEncode(), this.Id.HtmlEncode(), message.Sender.Role.HtmlEncode(), message.Sender.Id.HtmlEncode()); // return this.DeleteMessageFromHistoryAsync(conversationId, message, cancellationToken); Conversation? conversation = await this.Storage.GetConversationAsync(conversationId, this.Id, cancellationToken).ConfigureAwait(false); @@ -379,7 +384,7 @@ protected virtual Task SetAgentStatusAsync( string content, CancellationToken cancellationToken = default) { - this.Log.LogWarning("Change agent '{0}' status in conversation '{1}'", this.Id, conversationId); + this.Log.LogWarning("Change agent '{0}' status in conversation '{1}'", this.Id.HtmlEncode(), conversationId.HtmlEncode()); return this.WorkbenchConnector.SetAgentStatusAsync(this.Id, conversationId, content, cancellationToken); } @@ -392,7 +397,7 @@ protected virtual Task ResetAgentStatusAsync( string conversationId, CancellationToken cancellationToken = default) { - this.Log.LogWarning("Reset agent '{0}' status in conversation '{1}'", this.Id, conversationId); + this.Log.LogWarning("Reset agent '{0}' status in conversation '{1}'", this.Id.HtmlEncode(), conversationId.HtmlEncode()); return this.WorkbenchConnector.ResetAgentStatusAsync(this.Id, conversationId, cancellationToken); } } diff --git a/dotnet/WorkbenchConnector/Storage/AgentServiceStorage.cs b/dotnet/WorkbenchConnector/Storage/AgentServiceStorage.cs index 39910d47..8f995703 100644 --- a/dotnet/WorkbenchConnector/Storage/AgentServiceStorage.cs +++ b/dotnet/WorkbenchConnector/Storage/AgentServiceStorage.cs @@ -110,7 +110,9 @@ public Task DeleteInsightAsync(string agentId, string conversationId, string ins private async Task> GetAllAsync(string prefix, string suffix, CancellationToken cancellationToken = default) { - this._log.LogTrace("Searching all files with prefix '{0}' and suffix '{1}'", prefix, suffix); + this._log.LogTrace("Searching all files with prefix '{0}' and suffix '{1}'", + prefix.HtmlEncode(), suffix.HtmlEncode()); + var result = new List(); string[] fileEntries = Directory.GetFiles(this._path); foreach (string filePath in fileEntries) diff --git a/dotnet/WorkbenchConnector/StringLoggingExtensions.cs b/dotnet/WorkbenchConnector/StringLoggingExtensions.cs new file mode 100644 index 00000000..b1ea6349 --- /dev/null +++ b/dotnet/WorkbenchConnector/StringLoggingExtensions.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Text; +using System.Web; +using Microsoft.AspNetCore.Http; + +namespace Microsoft.SemanticWorkbench.Connector; + +public static class StringLoggingExtensions +{ + public static string HtmlEncode(this string? value) + { + return string.IsNullOrWhiteSpace(value) + ? $"{value}" + : HttpUtility.HtmlEncode(value); + } + + public static string HtmlEncode(this PathString value) + { + return string.IsNullOrWhiteSpace(value) + ? $"{value}" + : HttpUtility.HtmlEncode(value); + } + + public static string HtmlEncode(this StringBuilder value) + { + var s = value.ToString(); + return string.IsNullOrWhiteSpace(value.ToString()) + ? $"{s}" + : HttpUtility.HtmlEncode(s); + } + + public static string HtmlEncode(this object? value) + { + return value == null + ? string.Empty + : HttpUtility.HtmlEncode(value.ToString() ?? string.Empty); + } +} diff --git a/dotnet/WorkbenchConnector/Webservice.cs b/dotnet/WorkbenchConnector/Webservice.cs index 3cb2836b..c990da59 100644 --- a/dotnet/WorkbenchConnector/Webservice.cs +++ b/dotnet/WorkbenchConnector/Webservice.cs @@ -64,7 +64,9 @@ public static IEndpointRouteBuilder UseCreateAgentEndpoint( string? name = agentId; Dictionary? settings = JsonSerializer.Deserialize>(data); settings?.TryGetValue("assistant_name", out name); - log.LogDebug("Received request to create/update agent instance '{0}', name '{1}'", agentId, name); + + log.LogDebug("Received request to create/update agent instance '{0}', name '{1}'", + agentId.HtmlEncode(), name.HtmlEncode()); var agent = workbenchConnector.GetAgent(agentId); if (agent == null) @@ -91,7 +93,7 @@ public static IEndpointRouteBuilder UseDeleteAgentEndpoint( [FromServices] ILogger log, CancellationToken cancellationToken) => { - log.LogDebug("Received request to deleting agent instance '{0}'", agentId); + log.LogDebug("Received request to deleting agent instance '{0}'", agentId.HtmlEncode()); await workbenchConnector.DeleteAgentAsync(agentId, cancellationToken).ConfigureAwait(false); return Results.Ok(); }); @@ -109,7 +111,7 @@ public static IEndpointRouteBuilder UseFetchAgentConfigEndpoint( [FromServices] WorkbenchConnector workbenchConnector, [FromServices] ILogger log) => { - log.LogDebug("Received request to fetch agent '{0}' configuration", agentId); + log.LogDebug("Received request to fetch agent '{0}' configuration", agentId.HtmlEncode()); var agent = workbenchConnector.GetAgent(agentId); if (agent == null) @@ -135,13 +137,14 @@ public static IEndpointRouteBuilder UseSaveAgentConfigEndpoint( [FromServices] ILogger log, CancellationToken cancellationToken) => { - log.LogDebug("Received request to update agent '{0}' configuration", agentId); + log.LogDebug("Received request to update agent '{0}' configuration", agentId.HtmlEncode()); var agent = workbenchConnector.GetAgent(agentId); if (agent == null) { return Results.NotFound("Agent Not Found"); } var config = agent.ParseConfig(data["config"]); - IAgentConfig newConfig = await agent.UpdateAgentConfigAsync(config, cancellationToken).ConfigureAwait(false); + IAgentConfig newConfig = + await agent.UpdateAgentConfigAsync(config, cancellationToken).ConfigureAwait(false); var tmp = workbenchConnector.GetAgent(agentId); @@ -165,7 +168,8 @@ private static IEndpointRouteBuilder UseCreateConversationEndpoint( [FromServices] ILogger log, CancellationToken cancellationToken) => { - log.LogDebug("Received request to create conversation '{0}' on agent '{1}'", conversationId, agentId); + log.LogDebug("Received request to create conversation '{0}' on agent '{1}'", + conversationId.HtmlEncode(), agentId.HtmlEncode()); var agent = workbenchConnector.GetAgent(agentId); if (agent == null) { return Results.NotFound("Agent Not Found"); } @@ -190,7 +194,8 @@ public static IEndpointRouteBuilder UseFetchConversationStatesEndpoint( [FromServices] ILogger log, CancellationToken cancellationToken) => { - log.LogDebug("Received request to fetch agent '{0}' conversation '{1}' states", agentId, conversationId); + log.LogDebug("Received request to fetch agent '{0}' conversation '{1}' states", + agentId.HtmlEncode(), conversationId.HtmlEncode()); var agent = workbenchConnector.GetAgent(agentId); if (agent == null) { return Results.NotFound("Conversation Not Found"); } @@ -260,7 +265,8 @@ public static IEndpointRouteBuilder UseFetchConversationInsightEndpoint( [FromServices] ILogger log, CancellationToken cancellationToken) => { - log.LogDebug("Received request to fetch agent '{0}' conversation '{1}' insight '{2}'", agentId, conversationId, insightId); + log.LogDebug("Received request to fetch agent '{0}' conversation '{1}' insight '{2}'", + agentId.HtmlEncode(), conversationId.HtmlEncode(), insightId.HtmlEncode()); var agent = workbenchConnector.GetAgent(agentId); if (agent == null) { return Results.NotFound("Agent Not Found"); } @@ -323,7 +329,8 @@ private static IEndpointRouteBuilder UseCreateConversationEventEndpoint( [FromServices] ILogger log, CancellationToken cancellationToken) => { - log.LogDebug("Received request to process new event for agent '{0}' on conversation '{1}'", agentId, conversationId); + log.LogDebug("Received request to process new event for agent '{0}' on conversation '{1}'", + agentId.HtmlEncode(), conversationId.HtmlEncode()); if (data == null || !data.TryGetValue("event", out object? eventType)) { @@ -340,7 +347,8 @@ private static IEndpointRouteBuilder UseCreateConversationEventEndpoint( } var json = JsonSerializer.Serialize(data); - log.LogDebug("Agent '{0}', conversation '{1}', Event '{2}'", agentId, conversationId, eventType); + log.LogDebug("Agent '{0}', conversation '{1}', Event '{2}'", + agentId.HtmlEncode(), conversationId.HtmlEncode(), eventType.HtmlEncode()); switch (eventType.ToString()) { case "participant.created": @@ -401,8 +409,11 @@ private static IEndpointRouteBuilder UseCreateConversationEventEndpoint( break; default: - log.LogInformation($"{message.MessageType}: {message.Content}"); - log.LogWarning("Agent '{0}', conversation '{1}', Message type '{2}' ignored", agentId, conversationId, message.MessageType); + log.LogInformation("{0}: {1}", message.MessageType.HtmlEncode(), + message.Content.HtmlEncode()); + log.LogWarning("Agent '{0}', conversation '{1}', Message type '{2}' ignored", + agentId.HtmlEncode(), conversationId.HtmlEncode(), + message.MessageType.HtmlEncode()); break; } @@ -483,8 +494,8 @@ private static IEndpointRouteBuilder UseCreateConversationEventEndpoint( } } */ - log.LogWarning("Event type '{0}' not supported", eventType); - log.LogTrace(json); + log.LogWarning("Event type '{0}' not supported", eventType.HtmlEncode()); + log.LogTrace(json.HtmlEncode()); break; } @@ -507,7 +518,8 @@ public static IEndpointRouteBuilder UseDeleteConversationEndpoint( [FromServices] ILogger log, CancellationToken cancellationToken) => { - log.LogDebug("Received request to delete conversation '{0}' on agent instance '{1}'", conversationId, agentId); + log.LogDebug("Received request to delete conversation '{0}' on agent instance '{1}'", + conversationId.HtmlEncode(), agentId.HtmlEncode()); var agent = workbenchConnector.GetAgent(agentId); if (agent == null) { return Results.Ok(); } @@ -542,13 +554,13 @@ private static IEndpointRouteBuilder UseCatchAllEndpoint( string requestBody = await reader.ReadToEndAsync().ConfigureAwait(false); context.Request.Body.Position = 0; - log.LogWarning("Unknown request: {0} Path: {1}", context.Request.Method, context.Request.Path); + log.LogWarning("Unknown request: {0} Path: {1}", context.Request.Method, context.Request.Path.HtmlEncode()); string? query = context.Request.QueryString.Value; - if (!string.IsNullOrEmpty(query)) { log.LogDebug("Query: {0}", context.Request.QueryString.Value); } + if (!string.IsNullOrEmpty(query)) { log.LogDebug("Query: {0}", context.Request.QueryString.Value.HtmlEncode()); } - log.LogDebug("Headers: {0}", headersStringBuilder.ToString()); - log.LogDebug("Body: {0}", requestBody); + log.LogDebug("Headers: {0}", headersStringBuilder.HtmlEncode()); + log.LogDebug("Body: {0}", requestBody.HtmlEncode()); return Results.NotFound("Request not supported"); }); diff --git a/dotnet/WorkbenchConnector/WorkbenchConnector.cs b/dotnet/WorkbenchConnector/WorkbenchConnector.cs index 8f466386..60c9f2b1 100644 --- a/dotnet/WorkbenchConnector/WorkbenchConnector.cs +++ b/dotnet/WorkbenchConnector/WorkbenchConnector.cs @@ -98,7 +98,7 @@ public virtual async Task DeleteAgentAsync( var agent = this.GetAgent(agentId); if (agent == null) { return; } - this.Log.LogInformation("Deleting agent '{0}'", agentId); + this.Log.LogInformation("Deleting agent '{0}'", agentId.HtmlEncode()); await agent.StopAsync(cancellationToken).ConfigureAwait(false); this.Agents.Remove(agentId); await agent.StopAsync(cancellationToken).ConfigureAwait(false); @@ -118,7 +118,7 @@ public virtual async Task UpdateAgentConversationInsightAsync( Insight insight, CancellationToken cancellationToken = default) { - this.Log.LogDebug("Updating agent '{0}' '{1}' insight", agentId, insight.Id); + this.Log.LogDebug("Updating agent '{0}' '{1}' insight", agentId.HtmlEncode(), insight.Id.HtmlEncode()); var data = new { @@ -157,7 +157,8 @@ public virtual async Task SetAgentStatusAsync( string status, CancellationToken cancellationToken = default) { - this.Log.LogDebug("Setting agent status in conversation '{0}' with agent '{1}'", conversationId, agentId); + this.Log.LogDebug("Setting agent status in conversation '{0}' with agent '{1}'", + conversationId.HtmlEncode(), agentId.HtmlEncode()); var data = new { @@ -184,7 +185,8 @@ public virtual async Task ResetAgentStatusAsync( string conversationId, CancellationToken cancellationToken = default) { - this.Log.LogDebug("Setting agent status in conversation '{0}' with agent '{1}'", conversationId, agentId); + this.Log.LogDebug("Setting agent status in conversation '{0}' with agent '{1}'", + conversationId.HtmlEncode(), agentId.HtmlEncode()); string payload = """ { @@ -215,7 +217,8 @@ public virtual async Task SendMessageAsync( Message message, CancellationToken cancellationToken = default) { - this.Log.LogDebug("Sending message in conversation '{0}' with agent '{1}'", conversationId, agentId); + this.Log.LogDebug("Sending message in conversation '{0}' with agent '{1}'", + conversationId.HtmlEncode(), agentId.HtmlEncode()); string url = Constants.SendAgentMessage.Path .Replace(Constants.SendAgentMessage.ConversationPlaceholder, conversationId); @@ -234,7 +237,7 @@ public virtual async Task GetFilesAsync( string conversationId, CancellationToken cancellationToken = default) { - this.Log.LogDebug("Fetching list of files in conversation '{0}'", conversationId); + this.Log.LogDebug("Fetching list of files in conversation '{0}'", conversationId.HtmlEncode()); string url = Constants.GetConversationFiles.Path .Replace(Constants.GetConversationFiles.ConversationPlaceholder, conversationId); @@ -276,7 +279,7 @@ public virtual async Task DownloadFileAsync( string fileName, CancellationToken cancellationToken = default) { - this.Log.LogDebug("Downloading file from conversation '{0}'", conversationId); + this.Log.LogDebug("Downloading file from conversation '{0}'", conversationId.HtmlEncode()); string url = Constants.ConversationFile.Path .Replace(Constants.ConversationFile.ConversationPlaceholder, conversationId) @@ -310,7 +313,7 @@ public virtual async Task UploadFileAsync( string fileName, CancellationToken cancellationToken = default) { - this.Log.LogDebug("Deleting file {0} from a conversation '{1}'", fileName, conversationId); + this.Log.LogDebug("Deleting file {0} from a conversation '{1}'", fileName.HtmlEncode(), conversationId.HtmlEncode()); string url = Constants.UploadConversationFile.Path .Replace(Constants.UploadConversationFile.ConversationPlaceholder, conversationId); @@ -333,7 +336,7 @@ public virtual async Task DeleteFileAsync( string fileName, CancellationToken cancellationToken = default) { - this.Log.LogDebug("Deleting file {0} from a conversation '{1}'", fileName, conversationId); + this.Log.LogDebug("Deleting file {0} from a conversation '{1}'", fileName.HtmlEncode(), conversationId.HtmlEncode()); string url = Constants.ConversationFile.Path .Replace(Constants.ConversationFile.ConversationPlaceholder, conversationId) @@ -386,7 +389,7 @@ protected virtual async Task SendAsync( { try { - this.Log.LogTrace("Sending request {0} {1}", method, url); + this.Log.LogTrace("Sending request {0} {1}", method, url.HtmlEncode()); HttpRequestMessage request = this.PrepareRequest(method, url, data, agentId); HttpResponseMessage result = await this.HttpClient .SendAsync(request, cancellationToken) @@ -396,7 +399,7 @@ protected virtual async Task SendAsync( } catch (HttpRequestException e) { - this.Log.LogError("HTTP request failed: {0}. Request: {1} {2}", e.Message, method, url); + this.Log.LogError("HTTP request failed: {0}. Request: {1} {2}", e.Message.HtmlEncode(), method, url.HtmlEncode()); throw; } catch (Exception e)