Sends the security headers from the application instead of the proxy
U3. These headers normally come from nginx, but the deployment target does
not allow server configuration, so the application emits them itself. That
changes the failure mode: a bad nginx config fails loudly at reload, while a
middleware that never runs sends nothing and says nothing.
Two policies, defined in code. Strict for /admin, /api/v1 and /health;
relaxed for the public website, which is authored elsewhere by someone who
has never seen this policy. Configuration decides where a policy applies and
which external origins are permitted; it cannot invent a policy that is
subtly permissive.
script-src 'self' under Strict has no 'unsafe-inline' and no 'unsafe-eval',
asserted by a test so that loosening it means deleting a test that says why.
style-src does carry 'unsafe-inline' and cannot not: Radix positions its
overlays with inline style attributes, which nonces cannot reach at all.
Two traps handled explicitly. StartsWithSegments rather than string
StartsWith, because "/administrator".StartsWith("/admin") is true and a
public page would silently lose its inline scripts with no server-side trace.
And all decision logic sits in a static writer rather than in the middleware,
because DefaultHttpContext.Response.OnStarting is a no-op — the obvious
middleware test observes nothing and an assertion that nothing was written
passes for entirely the wrong reason.
An unknown policy name fails ValidateOnStart, so the process exits rather
than quietly serving /admin under the relaxed policy. Origin format is
validated too, beyond what the design asked: a CSP source list silently
ignores a malformed source, so a URL with a path would look configured and
block the script anyway.
BR-U3-22's Umami startup warning is withdrawn (REF-U3-01) — the backend
never sees VITE_UMAMI_WEBSITE_ID. It becomes a blocking CI gate in U5.
Build 0 errors; 315 tests pass, up from 253.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHoJpxYXzHACSQguHrC5fw
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
using SlpModularCms.Api.Extensions;
|
||||
using SlpModularCms.Core.Hosting;
|
||||
using SlpModularCms.Core.Hosting.Health;
|
||||
using SlpModularCms.Core.Hosting.Security;
|
||||
using Scalar.AspNetCore;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
@@ -18,6 +19,7 @@ builder.Services.AddCoreInfrastructure(builder.Configuration);
|
||||
builder.Services.AddCmsCors(builder.Configuration);
|
||||
builder.Services.AddCmsRateLimiting(builder.Configuration);
|
||||
builder.Services.AddCmsHealthChecks();
|
||||
builder.Services.AddCmsSecurityHeaders(builder.Configuration);
|
||||
|
||||
// Registered BEFORE module services: modules must not configure Data Protection themselves,
|
||||
// because a later registration would override this persistent key store (see
|
||||
@@ -50,6 +52,13 @@ app.MigrateCoreDatabase();
|
||||
// 5. Global Exception Handling
|
||||
app.UseExceptionHandler();
|
||||
|
||||
// First thing INSIDE the exception handler, and before the static-file middleware below.
|
||||
// Both directions matter: the exception handler re-executes the pipeline from within itself,
|
||||
// so anything registered outside it never sees the ProblemDetails response; and static files
|
||||
// short-circuit the pipeline, so anything after them is invisible to the public website —
|
||||
// which is almost all of the HTML this host serves.
|
||||
app.UseCmsSecurityHeaders();
|
||||
|
||||
app.UseRateLimiter();
|
||||
|
||||
// 6. Configure Pipeline
|
||||
|
||||
@@ -42,5 +42,16 @@
|
||||
"PermitLimit": 20,
|
||||
"WindowSeconds": 60
|
||||
}
|
||||
},
|
||||
"SecurityHeaders": {
|
||||
"Enabled": true,
|
||||
"DefaultPolicy": "Relaxed",
|
||||
"PathPolicies": [
|
||||
{ "PathPrefix": "/admin", "Policy": "Strict" },
|
||||
{ "PathPrefix": "/api/v1", "Policy": "Strict" },
|
||||
{ "PathPrefix": "/health", "Policy": "Strict" }
|
||||
],
|
||||
"AllowedScriptOrigins": [],
|
||||
"AllowedConnectOrigins": []
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user