diff --git a/DeepDrftWeb/Middleware/CmsStealthRoutingHandler.cs b/DeepDrftWeb/Middleware/CmsStealthRoutingHandler.cs new file mode 100644 index 0000000..8e88204 --- /dev/null +++ b/DeepDrftWeb/Middleware/CmsStealthRoutingHandler.cs @@ -0,0 +1,34 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Authorization.Policy; + +namespace DeepDrftWeb.Middleware; + +/// +/// Returns 404 for any /cms/* request that fails authorization. +/// This prevents the CMS from acknowledging its own existence to unauthorized callers +/// (a redirect to /account/login would reveal that the route exists). +/// CMS-PLAN §3.4 stealth-routing constraint. +/// +public class CmsStealthRoutingHandler : IAuthorizationMiddlewareResultHandler +{ + private readonly AuthorizationMiddlewareResultHandler _default = new(); + + public async Task HandleAsync( + RequestDelegate next, + HttpContext context, + AuthorizationPolicy policy, + PolicyAuthorizationResult authorizeResult) + { + // For /cms/* routes (including an exact /cms hit), map any authorization + // failure to 404 regardless of cause (unauthenticated, wrong role, or any + // future policy failure). This prevents the CMS from acknowledging its + // own existence to callers outside the Admin hierarchy. + if (context.Request.Path.StartsWithSegments("/cms") && !authorizeResult.Succeeded) + { + context.Response.StatusCode = StatusCodes.Status404NotFound; + return; + } + + await _default.HandleAsync(next, context, policy, authorizeResult); + } +} diff --git a/DeepDrftWeb/Program.cs b/DeepDrftWeb/Program.cs index 01d0ff1..a4f5ad0 100644 --- a/DeepDrftWeb/Program.cs +++ b/DeepDrftWeb/Program.cs @@ -2,6 +2,8 @@ using AuthBlocksLib; using AuthBlocksLib.Options; using DeepDrftCms; using DeepDrftWeb; +using DeepDrftWeb.Middleware; +using Microsoft.AspNetCore.Authorization; using MudBlazor.Services; using DeepDrftWeb.Components; using Microsoft.AspNetCore.HttpOverrides; @@ -64,6 +66,11 @@ builder.Services.AddAuthBlocks(options => }; }); +// CMS stealth routing: unauthorized /cms/* requests return 404, not a redirect. +// This prevents the CMS from revealing its own existence to unauthenticated callers. +// See CMS-PLAN §3.4. +builder.Services.AddSingleton(); + // AuthBlocksWeb: Blazor JWT client services (auth API is mounted on this same host via MapAuthBlocks). // AuthBlocksWeb.Startup.ConfigureAuthServices registers AddCascadingAuthenticationState server-side. AuthBlocksWeb.Startup.ConfigureAuthServices(builder.Services, baseUrl);