From 11d06d4f769213d100906ae66937d8fa3fab4c54 Mon Sep 17 00:00:00 2001 From: DevSars24 Date: Sun, 28 Jun 2026 14:21:11 +0530 Subject: [PATCH] fix: gate environment and json endpoints behind ShouldMapUiEndpoints in production (#117) --- .../DebugProbeProductionEndpointTests.cs | 17 ++- .../Extensions/DebugProbeExtensions.cs | 142 +++++++++--------- 2 files changed, 85 insertions(+), 74 deletions(-) diff --git a/DebugProbe.AspNetCore.Tests/Extensions/DebugProbeProductionEndpointTests.cs b/DebugProbe.AspNetCore.Tests/Extensions/DebugProbeProductionEndpointTests.cs index 093ca38..4f2fe84 100644 --- a/DebugProbe.AspNetCore.Tests/Extensions/DebugProbeProductionEndpointTests.cs +++ b/DebugProbe.AspNetCore.Tests/Extensions/DebugProbeProductionEndpointTests.cs @@ -18,6 +18,8 @@ public async Task Production_does_not_map_ui_endpoints_by_default() var comparePageResponse = await app.Client.GetAsync($"/compare?localTraceId={app.SingleEntry.Id}"); var scriptResponse = await app.Client.GetAsync("/debug/js/debugprobe-ui.js"); var logoResponse = await app.Client.GetAsync("/debug/logo.png"); + var environmentResponse = await app.Client.GetAsync("/debug/environment"); + var jsonResponse = await app.Client.GetAsync($"/debug/json/{app.SingleEntry.Id}"); var clearResponse = await app.Client.PostAsync("/debug/clear", null); Assert.Equal(HttpStatusCode.OK, capturedResponse.StatusCode); @@ -26,6 +28,8 @@ public async Task Production_does_not_map_ui_endpoints_by_default() Assert.Equal(HttpStatusCode.NotFound, scriptResponse.StatusCode); Assert.Equal(HttpStatusCode.NotFound, logoResponse.StatusCode); Assert.Equal(HttpStatusCode.NotFound, clearResponse.StatusCode); + Assert.Equal(HttpStatusCode.NotFound, environmentResponse.StatusCode); + Assert.Equal(HttpStatusCode.NotFound, jsonResponse.StatusCode); } [Fact] @@ -43,6 +47,8 @@ public async Task Production_maps_ui_endpoints_when_explicitly_allowed() var comparePageResponse = await app.Client.GetAsync($"/compare?localTraceId={app.SingleEntry.Id}"); var scriptResponse = await app.Client.GetAsync("/debug/js/debugprobe-ui.js"); var logoResponse = await app.Client.GetAsync("/debug/logo.png"); + var environmentResponse = await app.Client.GetAsync("/debug/environment"); + var jsonResponse = await app.Client.GetAsync($"/debug/json/{app.SingleEntry.Id}"); var clearResponse = await app.Client.PostAsync("/debug/clear", null); Assert.Equal(HttpStatusCode.OK, debugResponse.StatusCode); @@ -51,10 +57,12 @@ public async Task Production_maps_ui_endpoints_when_explicitly_allowed() Assert.Equal(HttpStatusCode.OK, scriptResponse.StatusCode); Assert.Equal(HttpStatusCode.OK, logoResponse.StatusCode); Assert.Equal(HttpStatusCode.OK, clearResponse.StatusCode); + Assert.Equal(HttpStatusCode.OK, environmentResponse.StatusCode); + Assert.Equal(HttpStatusCode.OK, jsonResponse.StatusCode); } [Fact] - public async Task Production_keeps_machine_readable_debug_endpoints_available_by_default() + public async Task Production_blocks_machine_readable_endpoints_by_default() { await using var app = await DebugProbeWebApplication.CreateAsync( Environments.Production, @@ -64,8 +72,11 @@ public async Task Production_keeps_machine_readable_debug_endpoints_available_by var environmentResponse = await app.Client.GetAsync("/debug/environment"); var jsonResponse = await app.Client.GetAsync($"/debug/json/{app.SingleEntry.Id}"); + var compareResponse = await app.Client.GetAsync( + $"/debug/compare/{app.SingleEntry.Id}?baseUrl=http://localhost&remoteTraceId={Guid.NewGuid()}"); - Assert.Equal(HttpStatusCode.OK, environmentResponse.StatusCode); - Assert.Equal(HttpStatusCode.OK, jsonResponse.StatusCode); + Assert.Equal(HttpStatusCode.NotFound, environmentResponse.StatusCode); + Assert.Equal(HttpStatusCode.NotFound, jsonResponse.StatusCode); + Assert.Equal(HttpStatusCode.NotFound, compareResponse.StatusCode); } } diff --git a/DebugProbe.AspNetCore/Extensions/DebugProbeExtensions.cs b/DebugProbe.AspNetCore/Extensions/DebugProbeExtensions.cs index 549c4a4..0fcc2d1 100644 --- a/DebugProbe.AspNetCore/Extensions/DebugProbeExtensions.cs +++ b/DebugProbe.AspNetCore/Extensions/DebugProbeExtensions.cs @@ -167,100 +167,100 @@ public static IApplicationBuilder UseDebugProbe(this IApplicationBuilder app, Ac RequireDebugAuthorization(webApp.Map($"{prefix}/favicon.ico", ctx => EmbeddedAssetWriter.WriteEmbeddedAsset(ctx, "DebugProbe.AspNetCore.Assets.images.debugprobe_favicon.ico", "image/x-icon") ).ExcludeFromDescription(), options); - } - - RequireDebugAuthorization(webApp.MapGet($"{prefix}/compare/{{id}}", async (string id, string baseUrl, string remoteTraceId, - DebugEntryStore store, - DebugProbeOptions options) => - { - var localEnvironment = store.Environment; - var localEntry = store.Get(id); - if (localEntry is null) + RequireDebugAuthorization(webApp.MapGet($"{prefix}/compare/{{id}}", async (string id, string baseUrl, string remoteTraceId, + DebugEntryStore store, + DebugProbeOptions options) => { - return Results.NotFound("Local trace not found"); - } + var localEnvironment = store.Environment; + var localEntry = store.Get(id); - if (!Guid.TryParse(remoteTraceId, out _)) - { - return Results.BadRequest("Invalid remote trace id"); - } + if (localEntry is null) + { + return Results.NotFound("Local trace not found"); + } - var validation = await CompareUrlValidator.ValidateCompareBaseUrlAsync(baseUrl, options); + if (!Guid.TryParse(remoteTraceId, out _)) + { + return Results.BadRequest("Invalid remote trace id"); + } - if (!validation.IsValid) - { - return Results.BadRequest(validation.Error); - } + var validation = await CompareUrlValidator.ValidateCompareBaseUrlAsync(baseUrl, options); - var remoteEnvironmentUrl = new Uri(validation.BaseUri!, $"{prefix}/environment"); + if (!validation.IsValid) + { + return Results.BadRequest(validation.Error); + } - var remoteEntryUrl = new Uri(validation.BaseUri!, $"{prefix}/json/{remoteTraceId}"); + var remoteEnvironmentUrl = new Uri(validation.BaseUri!, $"{prefix}/environment"); - DebugEntry? remoteEntry; - DebugEnvironment? remoteEnvironment; + var remoteEntryUrl = new Uri(validation.BaseUri!, $"{prefix}/json/{remoteTraceId}"); - try - { - remoteEnvironment = await Http.GetFromJsonAsync(remoteEnvironmentUrl); + DebugEntry? remoteEntry; + DebugEnvironment? remoteEnvironment; - if (remoteEnvironment is null) + try { - return Results.BadRequest("Failed to load remote environment"); - } + remoteEnvironment = await Http.GetFromJsonAsync(remoteEnvironmentUrl); + + if (remoteEnvironment is null) + { + return Results.BadRequest("Failed to load remote environment"); + } - remoteEntry = await Http.GetFromJsonAsync(remoteEntryUrl); + remoteEntry = await Http.GetFromJsonAsync(remoteEntryUrl); - if (remoteEntry is null) + if (remoteEntry is null) + { + return Results.NotFound("Remote trace not found"); + } + } + catch { - return Results.NotFound("Remote trace not found"); + return Results.BadRequest("Failed to reach remote server"); } - } - catch - { - return Results.BadRequest("Failed to reach remote server"); - } - - var diff = DebugEntryComparer.Compare(localEntry, remoteEntry); + + var diff = DebugEntryComparer.Compare(localEntry, remoteEntry); - return Results.Ok(new - { - localTrace = localEntry, - remoteTrace = remoteEntry, - localEnvironment, - remoteEnvironment, - method = new { local = localEntry.Method, remote = remoteEntry.Method }, - path = new { local = localEntry.Path, remote = remoteEntry.Path }, - status = new { local = localEntry.StatusCode, remote = remoteEntry.StatusCode }, - - requestTime = new + return Results.Ok(new { - local = localEntry.RequestTimeUtc.ToLocalTime().ToString("HH:mm:ss"), - remote = remoteEntry.RequestTimeUtc.ToLocalTime().ToString("HH:mm:ss"), - }, - - environment = new { local = localEnvironment.Environment, remote = remoteEnvironment?.Environment ?? "" }, - culture = new { local = localEnvironment.Culture, remote = remoteEnvironment?.Culture ?? "" }, - requestBody = new { local = localEntry.RequestBody ?? "", remote = remoteEntry.RequestBody ?? "" }, - responseBody = new { local = localEntry.ResponseBody ?? "", remote = remoteEntry.ResponseBody ?? "" }, - diffs = diff - }); + localTrace = localEntry, + remoteTrace = remoteEntry, + localEnvironment, + remoteEnvironment, + method = new { local = localEntry.Method, remote = remoteEntry.Method }, + path = new { local = localEntry.Path, remote = remoteEntry.Path }, + status = new { local = localEntry.StatusCode, remote = remoteEntry.StatusCode }, + + requestTime = new + { + local = localEntry.RequestTimeUtc.ToLocalTime().ToString("HH:mm:ss"), + remote = remoteEntry.RequestTimeUtc.ToLocalTime().ToString("HH:mm:ss"), + }, + + environment = new { local = localEnvironment.Environment, remote = remoteEnvironment?.Environment ?? "" }, + culture = new { local = localEnvironment.Culture, remote = remoteEnvironment?.Culture ?? "" }, + requestBody = new { local = localEntry.RequestBody ?? "", remote = remoteEntry.RequestBody ?? "" }, + responseBody = new { local = localEntry.ResponseBody ?? "", remote = remoteEntry.ResponseBody ?? "" }, + diffs = diff + }); - }).ExcludeFromDescription(), options); + }).ExcludeFromDescription(), options); - RequireDebugAuthorization(webApp.MapGet($"{prefix}/environment", (DebugEntryStore store) => - { - return Results.Ok(store.Environment); + RequireDebugAuthorization(webApp.MapGet($"{prefix}/environment", (DebugEntryStore store) => + { + return Results.Ok(store.Environment); - }).ExcludeFromDescription(), options); + }).ExcludeFromDescription(), options); - RequireDebugAuthorization(webApp.MapGet($"{prefix}/json/{{id}}", (string id, DebugEntryStore store) => - { - var item = store.Get(id); + RequireDebugAuthorization(webApp.MapGet($"{prefix}/json/{{id}}", (string id, DebugEntryStore store) => + { + var item = store.Get(id); - return item is null ? Results.NotFound() : Results.Json(item); + return item is null ? Results.NotFound() : Results.Json(item); - }).ExcludeFromDescription(), options); + }).ExcludeFromDescription(), options); + } }