diff --git a/src/LanguageServers/PowerPlatformLS/Impl.Core/DependencyInjection/HostApplicationBuilderExtensions.cs b/src/LanguageServers/PowerPlatformLS/Impl.Core/DependencyInjection/HostApplicationBuilderExtensions.cs index 81801f93..0286bacf 100644 --- a/src/LanguageServers/PowerPlatformLS/Impl.Core/DependencyInjection/HostApplicationBuilderExtensions.cs +++ b/src/LanguageServers/PowerPlatformLS/Impl.Core/DependencyInjection/HostApplicationBuilderExtensions.cs @@ -112,12 +112,12 @@ private static ILspTransport CreateIpcTransport(IServiceProvider serviceProvider if (useNamedPipes) { - logger.LogInformation($"Creating IPC: namedpipe={namedPipeInfo}"); + logger.LogInformation("Creating IPC: namedpipe"); transport = new NamedPipeIpc(namedPipeInfo, logger); } else { - logger.LogInformation($"Creating IPC: unixdomainsockets={namedPipeInfo}"); + logger.LogInformation("Creating IPC: unixdomainsockets"); transport = new UnixDomainSocketsIPC(namedPipeInfo, logger); } } diff --git a/src/LanguageServers/PowerPlatformLS/Impl.Core/LSP/Handlers/DidChangeWatchedFilesHandler.cs b/src/LanguageServers/PowerPlatformLS/Impl.Core/LSP/Handlers/DidChangeWatchedFilesHandler.cs index f05f194b..46613fc4 100644 --- a/src/LanguageServers/PowerPlatformLS/Impl.Core/LSP/Handlers/DidChangeWatchedFilesHandler.cs +++ b/src/LanguageServers/PowerPlatformLS/Impl.Core/LSP/Handlers/DidChangeWatchedFilesHandler.cs @@ -104,7 +104,12 @@ public async Task HandleNotificationAsync(DidChangeWatchedFilesParams request, R var fileInfo = _fileProvider.GetFileInfo(filePath); if (!fileInfo.Exists) { + // File name may be an agent/component name (EUII); keep it out of telemetry + // (preserved for local debugging via LogSensitiveInformation). _logger.LogWarning( + $"Can't process '{changeFileEvent.Type}' event for a watched file: " + + "The file does not exist."); + _logger.LogSensitiveInformation( $"Can't process '{changeFileEvent.Type}' event for '{filePath.FileName}': " + "The file does not exist."); continue; diff --git a/src/LanguageServers/PowerPlatformLS/Impl.Core/LSP/LanguageServer.cs b/src/LanguageServers/PowerPlatformLS/Impl.Core/LSP/LanguageServer.cs index ab6c581c..2f32ecf5 100644 --- a/src/LanguageServers/PowerPlatformLS/Impl.Core/LSP/LanguageServer.cs +++ b/src/LanguageServers/PowerPlatformLS/Impl.Core/LSP/LanguageServer.cs @@ -64,7 +64,10 @@ public override bool TryGetLanguageForRequest(string methodName, object? seriali if (!lspWorkspaceManager.TryGetLanguageForDocument(typedLspUri, out var languageObject)) { - Logger.LogError($"Failed to get language for {typedLspUri.Raw}"); + // The raw URI is a full file path (EUII); keep it out of telemetry while + // preserving the full detail for local debugging. + Logger.LogError("Failed to get language for document."); + Logger.LogSensitiveInformation($"Failed to get language for {typedLspUri.Raw}"); language = null; return false; } diff --git a/src/LanguageServers/PowerPlatformLS/Impl.Core/LSP/Uris/LspUriFactory.cs b/src/LanguageServers/PowerPlatformLS/Impl.Core/LSP/Uris/LspUriFactory.cs index 683b89a5..cd733fad 100644 --- a/src/LanguageServers/PowerPlatformLS/Impl.Core/LSP/Uris/LspUriFactory.cs +++ b/src/LanguageServers/PowerPlatformLS/Impl.Core/LSP/Uris/LspUriFactory.cs @@ -141,7 +141,10 @@ private static void LogUnsupportedSchemeOnce(string scheme, string sampleRaw, IL { // Truncate sample for logging var truncatedSample = sampleRaw.Length > 100 ? sampleRaw[..97] + "..." : sampleRaw; - logger.LogInformation($"UnsupportedSchemeObserved: scheme='{scheme}', sample='{truncatedSample}'"); + // The sample may be a full file path or URI (EUII); only the scheme is safe for telemetry. + logger.LogSensitiveInformation( + $"UnsupportedSchemeObserved: scheme='{scheme}', sample='{truncatedSample}'", + $"UnsupportedSchemeObserved: scheme='{scheme}'"); } } } diff --git a/src/LanguageServers/PowerPlatformLS/Impl.PullAgent/LoggingHttpHandler.cs b/src/LanguageServers/PowerPlatformLS/Impl.PullAgent/LoggingHttpHandler.cs index 62e47117..00960f7b 100644 --- a/src/LanguageServers/PowerPlatformLS/Impl.PullAgent/LoggingHttpHandler.cs +++ b/src/LanguageServers/PowerPlatformLS/Impl.PullAgent/LoggingHttpHandler.cs @@ -4,6 +4,7 @@ using Microsoft.PowerPlatformLS.Contracts.Internal.Common; using System; using System.Diagnostics; + using System.Text.RegularExpressions; using System.Threading.Tasks; /// @@ -22,6 +23,11 @@ public LoggingHttpHandler(ILogger logger) private static int _requestId; + // Environment/bot identifiers (GUIDs) in request paths are PII; redact them from telemetry. + private static readonly Regex GuidPattern = new( + "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}", + RegexOptions.Compiled); + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { int httpId = Interlocked.Increment(ref _requestId); @@ -54,7 +60,7 @@ protected override async Task SendAsync(HttpRequestMessage private static string GetPathAndQuery(Uri? uri) { if (uri == null) return string.Empty; - return uri.PathAndQuery; + return GuidPattern.Replace(uri.PathAndQuery, "{id}"); } } } diff --git a/src/LanguageServers/PowerPlatformLS/Impl.PullAgent/LspExceptionHandler.cs b/src/LanguageServers/PowerPlatformLS/Impl.PullAgent/LspExceptionHandler.cs index 0d59b4d3..bdb2ad20 100644 --- a/src/LanguageServers/PowerPlatformLS/Impl.PullAgent/LspExceptionHandler.cs +++ b/src/LanguageServers/PowerPlatformLS/Impl.PullAgent/LspExceptionHandler.cs @@ -29,10 +29,10 @@ public static (int Code, string Message) Handle(Exception ex, ILspLogger logger, // User-recoverable: the user can fix this by performing an action (resync, reclone, etc.). FileNotFoundException fnf => - LogTypedError(logger, 400, fnf), + LogTypedSensitiveError(logger, 400, fnf, "File not found."), DirectoryNotFoundException dnf => - LogTypedError(logger, 400, dnf), + LogTypedSensitiveError(logger, 400, dnf, "Directory not found."), // User validation: caller explicitly threw to signal bad input. InvalidOperationException ioe => diff --git a/src/LanguageServers/PowerPlatformLS/UnitTests/PowerPlatformLS.UnitTests/Impl.Core/ChangeMethodTests.cs b/src/LanguageServers/PowerPlatformLS/UnitTests/PowerPlatformLS.UnitTests/Impl.Core/ChangeMethodTests.cs index 773b79e4..1cbf6dc3 100644 --- a/src/LanguageServers/PowerPlatformLS/UnitTests/PowerPlatformLS.UnitTests/Impl.Core/ChangeMethodTests.cs +++ b/src/LanguageServers/PowerPlatformLS/UnitTests/PowerPlatformLS.UnitTests/Impl.Core/ChangeMethodTests.cs @@ -70,7 +70,9 @@ public async Task FileFilter_OnDidChangeWatchedFiles_Async() } else { - Assert.Single(logs.Warning.Where(x => x == $"Can't process 'Changed' event for '{entry.filepath.Split('/')[^1]}': The file does not exist.")); + // The full message (with file name) is routed through LogSensitiveInformation, + // which surfaces at Info level in Debug/test builds; the Warning is now file-name-free. + Assert.Single(logs.Info.Where(x => x == $"Can't process 'Changed' event for '{entry.filepath.Split('/')[^1]}': The file does not exist.")); } } } diff --git a/src/LanguageServers/PowerPlatformLS/UnitTests/PowerPlatformLS.UnitTests/Impl.PullAgent/LoggingHttpHandlerTests.cs b/src/LanguageServers/PowerPlatformLS/UnitTests/PowerPlatformLS.UnitTests/Impl.PullAgent/LoggingHttpHandlerTests.cs index 69855f28..4bfd1406 100644 --- a/src/LanguageServers/PowerPlatformLS/UnitTests/PowerPlatformLS.UnitTests/Impl.PullAgent/LoggingHttpHandlerTests.cs +++ b/src/LanguageServers/PowerPlatformLS/UnitTests/PowerPlatformLS.UnitTests/Impl.PullAgent/LoggingHttpHandlerTests.cs @@ -109,6 +109,20 @@ public void GetPathAndQuery_Returns_Empty_For_Null_Uri() Assert.Equal(string.Empty, result); } + [Fact] + public void GetPathAndQuery_Redacts_Guids_In_Path() + { + var method = typeof(LoggingHttpHandler).GetMethod("GetPathAndQuery", BindingFlags.NonPublic | BindingFlags.Static); + + Assert.NotNull(method); + var uri = new Uri("https://contoso.api.crm.dynamics.com/api/botmanagement/v1/environments/00b0eda3-d061-ea9f-b86f-0fd3f5628590/bots/09031e3d-436d-f111-ab0e-7ced8dfef2b5/content/botcomponents"); + var result = (string)method!.Invoke(null, [uri])!; + + Assert.Equal("/api/botmanagement/v1/environments/{id}/bots/{id}/content/botcomponents", result); + Assert.DoesNotContain("00b0eda3", result); + Assert.DoesNotContain("09031e3d", result); + } + [Fact] public async Task SendAsync_Includes_RequestId_From_LspRequestContext() {