SafeWebCore is a lightweight, high-performance .NET 10 middleware library that adds security headers to your ASP.NET Core applications. It targets an A+ rating on securityheaders.com out of the box — zero configuration required.
Current version: 1.8.1
-
🔒 A+ in one line —
AddNetSecureHeadersStrictAPlus()configures the strictest security headers instantly -
🧭 App-profile presets — ready-made profiles for API, MVC, Blazor, and SPA reverse-proxy apps
-
🛠️ Fully custom —
AddNetSecureHeaders(opts => { ... })gives you complete control over every header -
⚙️ Configuration binding —
AddNetSecureHeadersFromConfiguration(...)bindsNetSecureHeadersOptionsdirectly from configuration -
🌦️ Environment-aware rollout — opt-in helpers can default CSP to report-only outside production for safer rollout
-
🧩 Nonce-based CSP — per-request cryptographic nonces for
script-srcandstyle-src -
🧷 Razor nonce TagHelpers — auto-inject nonce attributes on
<script>and<style>when available -
🛣️ Path-based policies — apply different security profiles per route prefix with longest-prefix matching
-
🎯 Endpoint metadata overrides — skip headers or force CSP report-only per endpoint
-
🧪 Startup configuration validation — invalid combinations fail fast during startup
-
📝 CSP Report-Only support — ship policies safely before enforcing
-
🧱 Typed policy builders — strongly typed builders for
Referrer-Policy,Permissions-Policy, and COEP/COOP/CORP values -
🧰 Optional additional headers — opt-in support for
Origin-Agent-Cluster,X-Robots-Tag, andClear-Site-Data -
📋 Full CSP Level 3 (W3C Recommendation) — all directives including
worker-src,manifest-src,frame-src,script-src-elem/attr,style-src-elem/attr,report-to, nonce/hash support,strict-dynamic -
🔮 CSP Level 4 ready — Trusted Types (
require-trusted-types-for,trusted-types),fenced-frame-src(Privacy Sandbox) -
🎯 Fluent CSP Builder — type-safe, chainable API with full XML documentation for every directive
-
⚡ Zero-allocation nonce generation —
stackalloc+RandomNumberGeneratoron the hot path, plusTryWriteNonce(Span<char>)for fully heap-free scenarios -
🔍
HttpContext.GetCspNonce()— discoverable extension method to retrieve the per-request nonce -
🛑 Server header removal — hides server technology from attackers
-
🔌 Extensible — add custom
IHeaderPolicyimplementations for any header -
📊 CSP violation reporting — built-in middleware for
/csp-reportendpoint using Reporting API v1 -
🔍 Diagnostics preview —
MapSafeWebCoreDiagnostics(...)for an opt-in JSON preview of effective headers, path-policy resolution, and CSP mode -
🔐 JWT authority validation — opt-in
SafeWebCore.JwtBearercompanion module turns a broken JWT authority into a fail-fast startup error or a loud Error log (solves dotnet/aspnetcore#67991, reported by Stephan van Rooij), plus optional token hardening -
📈 Opt-in metrics —
System.Diagnostics.Metricscounters for core middleware and fraud detection -
🚨 Fraud action pipeline —
IFraudEventSink/FraudEventfor reacting to fraud analysis results (logging, webhooks, custom actions) -
📦 Companion packages —
SafeWebCore.JwtBearer,SafeWebCore.FraudDetection,SafeWebCore.Analyzers(preview), andSafeWebCore.Testing(preview) -
📖 Recipe docs — practical integration guides under
docs/recipes/ -
✅ Actionable startup validation — remediation guidance for CSP mode, path prefixes, additional headers, and reporting endpoints
using SafeWebCore.Builder;
builder.Services.AddNetSecureHeaders(opts =>
{
opts.ReferrerPolicyValue = new ReferrerPolicyBuilder()
.StrictOriginWhenCrossOrigin()
.Build();
opts.PermissionsPolicyValue = new PermissionsPolicyBuilder()
.Disable(PermissionsFeature.Camera)
.Disable(PermissionsFeature.Microphone)
.AllowSelf(PermissionsFeature.Geolocation)
.Build();
var crossOrigin = new CrossOriginPolicyBuilder()
.CoepRequireCorp()
.CoopSameOrigin()
.CorpSameOrigin()
.Build();
opts.CoepValue = crossOrigin.Coep;
opts.CoopValue = crossOrigin.Coop;
opts.CorpValue = crossOrigin.Corp;
});builder.Services.AddNetSecureHeaders(opts =>
{
opts.EnableOriginAgentCluster = true;
opts.OriginAgentClusterValue = "?1";
opts.EnableXRobotsTag = true;
opts.XRobotsTagValue = "noindex, nofollow";
opts.EnableClearSiteData = true;
opts.ClearSiteDataValue = "\"cache\", \"cookies\", \"storage\"";
});| Standard | Status | Coverage |
|---|---|---|
| CSP Level 3 (W3C Recommendation) | ✅ Full | All 22 directives, nonce/hash, strict-dynamic, report-to |
| CSP Level 4 (Emerging) | ✅ Ready | Trusted Types, fenced-frame-src (Privacy Sandbox) |
v1.8.1 is a maintenance release — 100% backwards compatible with v1.8.0 and earlier: no public API, default, preset or configuration change, and no behavior change.
| Improvement | Detail |
|---|---|
| Internal deduplication | The pen-test signal scoring, the authorization-check notification flow and the path-policy resolution existed as byte-identical copies in both fraud detectors and in the middleware/diagnostics pair; they now live once in the internal PenTestSignalAnalyzer and PathPolicyResolver helpers, so a score, a throttle rule or a policy-resolution rule can only change in one place |
| Allocation cleanup | The path-policy lookup walks the list by index instead of allocating an enumerator per request, and the repeated CSP source literals in SecurePresets, CspOptions and CrossOriginPolicyBuilder now name one private constant per file — every emitted header value, directive and policy-resolution outcome is identical |
| Dependency maintenance | Development-only packages (test SDK, xunit, coverlet, PublicApiAnalyzers) refreshed to their latest stable releases; SafeWebCore.JwtBearer moves its Microsoft.AspNetCore.Authentication.JwtBearer floor to 10.0.12 |
| Docs | New SonarCloud triage page records how the analysis is scoped and the ten findings that are deliberately accepted |
See the full CHANGELOG for details.
dotnet add package SafeWebCoreusing SafeWebCore.Extensions;
var builder = WebApplication.CreateBuilder(args);
// Adds ALL security headers with the strictest A+ configuration
builder.Services.AddNetSecureHeadersStrictAPlus();
var app = builder.Build();
app.UseNetSecureHeaders();
app.MapGet("/", () => "Hello, secure world!");
app.Run();That's it! Your application now returns these headers on every response:
| Header | Value |
|---|---|
Strict-Transport-Security |
max-age=63072000; includeSubDomains; preload |
X-Frame-Options |
DENY |
X-Content-Type-Options |
nosniff |
Referrer-Policy |
no-referrer |
Permissions-Policy |
All recognized features denied (scanner-safe) |
Cross-Origin-Embedder-Policy |
require-corp |
Cross-Origin-Opener-Policy |
same-origin |
Cross-Origin-Resource-Policy |
same-origin |
X-DNS-Prefetch-Control |
off |
X-Permitted-Cross-Domain-Policies |
none |
Content-Security-Policy |
Nonce-based, strict-dynamic, Trusted Types |
Server |
(removed) |
X-Powered-By |
(removed) |
The preset is intentionally strict. Relax only what your app needs. CSP directives are space-separated — add multiple origins in a single string:
builder.Services.AddNetSecureHeadersStrictAPlus(opts =>
{
// Multiple CDNs — just separate with spaces
opts.Csp = opts.Csp with { ImgSrc = "'self' https://cdn1.example.com https://cdn2.example.com data:" };
// Multiple directives at once using 'with { ... }'
opts.Csp = opts.Csp with
{
ConnectSrc = "'self' https://api.example.com wss://ws.example.com",
FontSrc = "'self' https://fonts.gstatic.com https://cdn.example.com"
};
// Non-CSP headers are simple string properties
opts.ReferrerPolicyValue = "strict-origin-when-cross-origin";
});💡 Tip: Each CSP directive is one string with space-separated sources. Use a single
with { ... }block to change multiple directives at once.
For complete control, use AddNetSecureHeaders with the fluent CSP builder:
using SafeWebCore.Builder;
using SafeWebCore.Extensions;
builder.Services.AddNetSecureHeaders(opts =>
{
opts.EnableHsts = true;
opts.HstsValue = "max-age=31536000; includeSubDomains";
opts.EnableXFrameOptions = true;
opts.XFrameOptionsValue = "SAMEORIGIN";
opts.ReferrerPolicyValue = "strict-origin-when-cross-origin";
// Use the fluent CSP builder
opts.Csp = new CspBuilder()
.DefaultSrc("'none'")
.ScriptSrc("'nonce-{nonce}' 'strict-dynamic' https:")
.StyleSrc("'nonce-{nonce}'")
.ImgSrc("'self' https: data:")
.FontSrc("'self' https://fonts.gstatic.com")
.ConnectSrc("'self' wss://realtime.example.com")
.FrameAncestors("'none'")
.BaseUri("'none'")
.FormAction("'self'")
.UpgradeInsecureRequests()
.Build();
});The current workspace includes the completed v1.5 tooling features (additive, opt-in, 100% backward compatible):
- SWC001: Registration without
UseNetSecureHeaders() - SWC002: Permanent
UseCspReportOnly = true - SWC003:
'unsafe-inline'without nonce - SWC004: Overly broad CSP sources
AssertHasSecurityHeaders()AssertHasCspEnforceMode()/AssertHasCspReportOnlyMode()AssertHasNonceInCsp()/AssertHasNoNonceInCsp()- Bootstrap helpers for
TestServer
See docs/recipes/ for practical examples.
The following features are now implemented from the v1.2 plan.
builder.Services.AddNetSecureHeaders(opts =>
{
opts.UseCspReportOnly = true;
});This emits Content-Security-Policy-Report-Only instead of enforce-mode Content-Security-Policy.
builder.Services.AddNetSecureHeaders(opts =>
{
opts.PathPolicies.Add(new PathPolicyOptions
{
PathPrefix = "/api",
Options = new NetSecureHeadersOptions
{
ReferrerPolicyValue = "no-referrer",
UseCspReportOnly = true
}
});
});Path policies are matched by prefix and the longest matching prefix wins.
SafeWebCore validates options during startup and fails fast for invalid configurations, for example:
UseCspReportOnly = truewhileEnableCsp = false- duplicate path prefixes (normalized)
- empty path policy prefixes
Register the TagHelpers in your Razor _ViewImports.cshtml:
@addTagHelper *, SafeWebCoreThen use normal tags; nonce is added automatically when available:
<script>
console.log("nonce is injected automatically");
</script>
<style>
body { font-family: sans-serif; }
</style>SafeWebCore generates a unique cryptographic nonce per request. Use it in your scripts and styles:
using SafeWebCore.Attributes;
[CspNonce]
public class HomeController : Controller
{
public IActionResult Index() => View();
}<!-- In your Razor view -->
<script nonce="@ViewData["CspNonce"]">
console.log("This script is allowed by CSP");
</script>
<style nonce="@ViewData["CspNonce"]">
body { font-family: sans-serif; }
</style>using SafeWebCore.Extensions;
// In Minimal API
app.MapGet("/page", (HttpContext ctx) =>
{
var nonce = ctx.GetCspNonce();
return Results.Content(
$"<script nonce=\"{nonce}\">console.log('nonce ok');</script>",
"text/html");
});
// In a controller action
public IActionResult Index()
{
ViewData["CspNonce"] = HttpContext.GetCspNonce();
return View();
}SafeWebCore ships a BenchmarkDotNet suite covering nonce generation, CSP header assembly, typed policy builders, preset instantiation, the middleware pipeline, and CSP report parsing.
cd benchmarks/SafeWebCore.Benchmarks
dotnet run -c ReleaseSee docs/benchmarks.md for scenario descriptions, running instructions, and result interpretation.
Three complete, runnable ASP.NET Core applications demonstrating different integration patterns:
| Example | Framework | Key Features |
|---|---|---|
| MinimalApi | Minimal API | One-line A+ setup, inline nonce, CSP reporting, health probes |
| MvcApp | MVC + Razor Views | Typed policy builders, path policies, nonce TagHelpers, controller attributes |
| ApiService | Web API Controllers | Custom CSP report sink, endpoint overrides, API preset |
| JwtBearerDemo | Minimal API + JWT | Fixed the dotnet/aspnetcore#67991 silent-401 bug: broken vs. fail-fast authority startup |
Each example is fully functional out of the box — just dotnet run from the example directory.
# Try each example
cd examples/MinimalApi && dotnet run
cd examples/MvcApp && dotnet run
cd examples/ApiService && dotnet runSee examples/README.md for a detailed overview and feature matrix.
Makes a misconfigured or unreachable JWT authority fail loud before your users ever see a 401, and optionally hardens your token validation. It solves dotnet/aspnetcore#67991 (reported by Stephan van Rooij, milestone ".NET 12 Planning") today, on .NET 10:
- ⚡ Fail fast or fail loud at startup — a broken OpenID Connect authority (HTTP 4xx) stops the
application with a clear error, or logs at
Error, instead of silently returning401for everything with nothing in the logs. - 🧪 Static configuration checks — HTTPS authority, audience/issuer consistency,
alg: nonerejected. - 🛡️ Token hardening — require signed tokens, algorithm allow-list,
typheader checks, audience/issuer enforcement, max clock skew / token lifetime,jti/nbf/iat. - 🔁 Runtime metadata logging with optional periodic re-validation and empty-JWKS detection
(
RequireSigningKeys).
dotnet add package SafeWebCore.JwtBearer// One-liner: AddJwtBearer + hardening + startup authority validation (fail fast by default)
builder.Services.AddSafeWebCoreJwtBearer(
o => o.Authority = "https://login.microsoftonline.com/organizations/v2.0",
hardening => hardening.MaximumTokenLifetime = TimeSpan.FromHours(1));Full API and options: src/SafeWebCore.JwtBearer/README.md ·
Try it live: examples/JwtBearerDemo/
| Guide | Description |
|---|---|
| Getting Started | Installation, minimal setup, and verifying your headers |
| Examples | Three complete sample projects (Minimal API, MVC, Web API) |
| Security Headers | Every security header explained with values and rationale |
| CSP Configuration | CSP builder, nonces, directives, and common scenarios |
| Presets | Strict A+ and app-profile presets, customization examples |
| Advanced Configuration | Custom policies, CSP reporting, endpoint overrides, troubleshooting |
| Benchmarks | Running benchmarks and interpreting results |