Helmet.AspNetCore 0.1.0

dotnet add package Helmet.AspNetCore --version 0.1.0
                    
NuGet\Install-Package Helmet.AspNetCore -Version 0.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Helmet.AspNetCore" Version="0.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Helmet.AspNetCore" Version="0.1.0" />
                    
Directory.Packages.props
<PackageReference Include="Helmet.AspNetCore" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Helmet.AspNetCore --version 0.1.0
                    
#r "nuget: Helmet.AspNetCore, 0.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Helmet.AspNetCore@0.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Helmet.AspNetCore&version=0.1.0
                    
Install as a Cake Addin
#tool nuget:?package=Helmet.AspNetCore&version=0.1.0
                    
Install as a Cake Tool

Helmet.AspNetCore

A security headers library for ASP.NET Core that goes beyond NWebsec: content-type-aware header application, full CSP builder with nonce and report-only support, profile-based defaults, per-endpoint overrides, and server hardening — in a single package.

Features

  • Content-type-aware — HTML responses get the full security header set (CSP, X-Frame-Options, Permissions-Policy, etc.); API/JSON responses get only what's relevant (nosniff, CORP, Cache-Control). Automatic, zero configuration.
  • Profile-based defaults — SecurityProfile.Spa or SecurityProfile.Api gives you a secure baseline in one line. Override individual headers without losing the rest.
  • Full CSP builder — Fluent API for all CSP Level 3 directives. Nonce support, hash support (SHA-256/384/512), strict-dynamic, report-only mode.
  • Per-endpoint overrides — MVC attributes to suppress headers, swap CSP policies, or tweak individual headers on specific actions.
  • Server hardening — Kestrel limits, HSTS, rate limiting, CORS, redirect validation — all from the same builder.

Quick Start

dotnet add package Helmet.AspNetCore
// Program.cs — one line for a secure SPA
var builder = WebApplication.CreateBuilder(args);

builder.AddHelmetWebSecurity(SecurityProfile.Spa);

var app = builder.Build();

app.UseHelmetWebSecurity();

app.MapControllers();
app.Run();

That's it. Your app now has:

  • CSP, X-Frame-Options, Referrer-Policy, Permissions-Policy on HTML responses
  • Nosniff, CORP, Cache-Control on API responses
  • HSTS (1-year, includeSubDomains)
  • Kestrel hardened (no Server header, request size limits)
  • Rate limiting (200 req/min per IP for SPA, 100 for API)
  • CORS deny-all

Profiles

SecurityProfile.Spa

For web applications that serve HTML pages (Angular, React, Razor, Blazor).

CSP: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; form-action 'self'; base-uri 'self'; object-src 'none'; upgrade-insecure-requests; block-all-mixed-content

Headers on HTML responses: CSP, X-Frame-Options (DENY), X-XSS-Protection (0), Referrer-Policy (no-referrer), Permissions-Policy (all features blocked), COOP (same-origin), COEP (require-corp), CORP (same-origin), X-Download-Options (noopen), X-Robots-Tag (none), Cache-Control (no-store).

Headers on non-HTML responses: X-Content-Type-Options (nosniff), CORP (same-origin), X-Permitted-Cross-Domain-Policies (none), Cache-Control (no-store).

Rate limit: 200 requests/minute per IP.

SecurityProfile.Api

For pure API services that never serve HTML.

CSP: default-src 'none'; frame-ancestors 'none'; upgrade-insecure-requests; block-all-mixed-content

Rate limit: 100 requests/minute per IP.

Since API responses are never text/html, the HTML-only headers are never applied — the profile's CSP is only emitted if something unexpectedly returns HTML.

Content-Type-Aware Headers

The middleware inspects Response.ContentType (via OnStarting callback) and applies only the relevant headers:

Response type Headers applied
text/html, application/xhtml+xml Full set: CSP, X-Frame-Options, Referrer-Policy, Permissions-Policy, COOP, COEP, CORP, X-XSS-Protection, X-Download-Options, X-Robots-Tag, Reporting-Endpoints, Cache-Control, nosniff
Everything else (JSON, images, CSS, JS, etc.) Minimal set: nosniff, CORP, X-Permitted-Cross-Domain-Policies, Cache-Control
No content type (204, redirects) Minimal set

This is automatic. No path-prefix configuration needed.

CSP Configuration

Override specific directives (keep profile defaults for the rest)

builder.AddHelmetWebSecurity(SecurityProfile.Spa)
    .ConfigureCsp(csp => csp
        .ScriptSrc(s => s.Self().StrictDynamic())
        .ConnectSrc(c => c.Self().Source("https://api.example.com")));

Only script-src and connect-src are overridden. All other Spa profile directives remain.

Add hashes for inline scripts/styles

builder.AddHelmetWebSecurity(SecurityProfile.Spa)
    .AddScriptHash("sha256-MhtPZXr7+LpJUY5qtMutB+qWfQtMaPccfe7QXtCcEYc=")
    .AddStyleHash("sha256-abc123...");

Or via the CSP builder for more control:

builder.AddHelmetWebSecurity(SecurityProfile.Spa)
    .ConfigureCsp(csp => csp
        .ScriptSrc(s => s
            .Self()
            .UnsafeHashes()
            .Sha256("MhtPZXr7+LpJUY5qtMutB+qWfQtMaPccfe7QXtCcEYc="))
        .StyleSrc(s => s
            .Self()
            .Sha256("abc123...")));

Full custom CSP (no profile)

builder.AddHelmetWebSecurity(b =>
{
    b.AddSecurityHeaders(headers =>
    {
        headers.ContentSecurityPolicy(csp => csp
            .DefaultSrc(d => d.None())
            .ScriptSrc(s => s.Self().Nonce().StrictDynamic())
            .StyleSrc(s => s.Self())
            .ImgSrc(i => i.Self().DataScheme())
            .FontSrc(f => f.Self())
            .ConnectSrc(c => c.Self())
            .FrameAncestors(fa => fa.None())
            .BaseUri(b => b.Self())
            .FormAction(f => f.Self())
            .UpgradeInsecureRequests());
    });
    b.HardenKestrel();
    b.AddHsts();
    b.AddRateLimiting();
    b.AddCors();
});

Available CSP directives

Method CSP directive
DefaultSrc default-src
ScriptSrc script-src
ScriptSrcElem script-src-elem
ScriptSrcAttr script-src-attr
StyleSrc style-src
StyleSrcElem style-src-elem
StyleSrcAttr style-src-attr
ImgSrc img-src
FontSrc font-src
ConnectSrc connect-src
MediaSrc media-src
FrameSrc frame-src
ChildSrc child-src
WorkerSrc worker-src
ObjectSrc object-src
ManifestSrc manifest-src
BaseUri base-uri
FormAction form-action
FrameAncestors frame-ancestors
Sandbox sandbox
UpgradeInsecureRequests upgrade-insecure-requests
BlockAllMixedContent block-all-mixed-content
ReportTo report-to
ReportUri report-uri

Available source values

Method CSP value
None() 'none'
Self() 'self'
UnsafeInline() 'unsafe-inline'
UnsafeEval() 'unsafe-eval'
UnsafeHashes() 'unsafe-hashes'
StrictDynamic() 'strict-dynamic'
Nonce() 'nonce-<per-request>'
Sha256(hash) 'sha256-<hash>'
Sha384(hash) 'sha384-<hash>'
Sha512(hash) 'sha512-<hash>'
Source(uri) Custom origin (e.g. https://cdn.example.com)
DataScheme() data:
BlobScheme() blob:

Nonce Support

Nonces provide per-request script/style authorization. Enable them with UseNonces():

builder.AddHelmetWebSecurity(SecurityProfile.Spa)
    .ConfigureCsp(csp => csp
        .ScriptSrc(s => s.Self().Nonce().StrictDynamic()))
    .UseNonces();

A 128-bit cryptographic nonce is generated per request and automatically included in the CSP header.

Accessing the nonce in code

// In a controller or middleware
var nonce = HttpContext.GetCspNonce();

Razor tag helper

Add the tag helper to _ViewImports.cshtml:

@addTagHelper *, Helmet.AspNetCore

Then use csp-nonce on script/style tags:

<script csp-nonce>
    console.log("This script is nonce-authorized");
</script>

<style csp-nonce>
    body { margin: 0; }
</style>

Renders as:

<script nonce="K7gNU3sdo+OL0wNhqoVWhr3g6s1xYv72ol/pe/Unols=">
    console.log("This script is nonce-authorized");
</script>

Angular / SPA note

For pre-built SPAs, hash-based CSP with SRI (subresourceIntegrity: true in angular.json) is usually a better fit than nonces, since the HTML is static. Use AddScriptHash() / AddStyleHash() for inline event handlers or critical CSS that Angular injects.

CSP Report-Only

Test a stricter CSP without breaking your app. Violations are reported but not blocked.

builder.AddHelmetWebSecurity(SecurityProfile.Spa)
    .ConfigureCsp(csp => csp
        .ScriptSrc(s => s.Self()))                          // enforced
    .ConfigureCspReportOnly(csp => csp
        .ScriptSrc(s => s.Self().StrictDynamic())           // test stricter policy
        .ReportTo("csp-violations"))                         // report violations
    .AddReportingEndpoint("csp-violations", "/api/security/csp-report");

This produces two headers:

Content-Security-Policy: ... script-src 'self' ...
Content-Security-Policy-Report-Only: ... script-src 'self' 'strict-dynamic' ...; report-to csp-violations
Reporting-Endpoints: csp-violations="/api/security/csp-report"

Built-in report endpoint

Optionally map a minimal endpoint that logs CSP violation reports:

app.MapCspReportEndpoint("/api/security/csp-report");

Reports are logged via ILogger at Warning level. For production, wire your own endpoint to forward reports to Sentry, Elastic, or a dedicated CSP reporting service.

Individual Header Configuration

Override any header while keeping profile defaults for the rest:

builder.AddHelmetWebSecurity(SecurityProfile.Spa)
    .ConfigureFrameOptions(FrameOptions.SameOrigin)
    .ConfigureReferrerPolicy(ReferrerPolicy.StrictOriginWhenCrossOrigin)
    .ConfigureXRobotsTag(r => r.NoIndex().NoFollow().NoSnippet())
    .ConfigurePermissionsPolicy(pp => pp.AllowSelf("fullscreen").AllowSelf("clipboard-read"))
    .ConfigureCrossOrigin(co => co
        .OpenerPolicy(CrossOriginOpenerPolicy.SameOriginAllowPopups)
        .EmbedderPolicy(CrossOriginEmbedderPolicy.Credentialless));

FrameOptions

Value Header
FrameOptions.Deny (default) X-Frame-Options: DENY
FrameOptions.SameOrigin X-Frame-Options: SAMEORIGIN

ReferrerPolicy

Value Header
ReferrerPolicy.NoReferrer (default) Referrer-Policy: no-referrer
ReferrerPolicy.StrictOriginWhenCrossOrigin Referrer-Policy: strict-origin-when-cross-origin
ReferrerPolicy.SameOrigin Referrer-Policy: same-origin
... All standard values supported

Permissions-Policy

Profile default blocks all ~30 features. Open specific ones:

.ConfigurePermissionsPolicy(pp => pp
    .AllowSelf("camera")           // camera=(self)
    .AllowSelf("fullscreen")       // fullscreen=(self)
    .AllowAll("autoplay")          // autoplay=*
    .Allow("geolocation", "https://maps.example.com"))  // geolocation=(https://maps.example.com)

Cross-Origin Policies

.ConfigureCrossOrigin(co => co
    .OpenerPolicy(CrossOriginOpenerPolicy.SameOriginAllowPopups)  // for OAuth popups
    .EmbedderPolicy(CrossOriginEmbedderPolicy.Credentialless)     // relaxed COEP
    .ResourcePolicy(CrossOriginResourcePolicy.SameOrigin))        // default

Per-Endpoint Overrides

Suppress all security headers

[SuppressSecurityHeaders]
[HttpGet("/health")]
public IActionResult Health() => Ok("healthy");

Named CSP policies

Register alternative policies at startup:

builder.AddHelmetWebSecurity(SecurityProfile.Spa)
    .AddCspPolicy("legacy", csp => csp
        .ScriptSrc(s => s.Self().UnsafeInline())
        .StyleSrc(s => s.Self().UnsafeInline()));

Apply to specific endpoints:

[CspPolicy("legacy")]
public IActionResult LegacyPage() => View();

Individual header overrides

[FrameOptions(FrameOptions.SameOrigin)]  // allow embedding this page
public IActionResult EmbeddableWidget() => View();

[CspScriptSrc("'self'", "'unsafe-inline'")]  // relax script-src for this page
public IActionResult AdminPage() => View();

Redirect Validation

Prevent open redirect attacks by validating redirect targets. Opt-in, disabled by default.

builder.AddHelmetWebSecurity(SecurityProfile.Spa)
    .ConfigureRedirectValidation(rv => rv
        .AllowSameHost()
        .AllowHost("auth.example.com")
        .AllowHost("*.example.com"));

Inspects 3xx responses. If the Location header points to a host not in the allowlist, logs a warning.

Server Hardening

These features are applied automatically by profiles but can be configured individually.

Kestrel

builder.AddHelmetWebSecurity(SecurityProfile.Spa)
    .HardenKestrel(k =>
    {
        k.MaxRequestBodySize = 50 * 1024 * 1024;   // 50 MB (default: 30 MB)
        k.MaxConcurrentConnections = 200;            // default: 100
        k.MaxRequestHeadersTotalSize = 64 * 1024;   // 64 KB (default: 32 KB)
    });

Removes the Server header and applies request size / connection limits.

HSTS

builder.AddHelmetWebSecurity(SecurityProfile.Spa)
    .AddHsts(hsts =>
    {
        hsts.Preload = true;           // add to browser preload list
        hsts.MaxAge = TimeSpan.FromDays(730);  // 2 years
    });

Default: 1-year max-age, includeSubDomains.

Rate Limiting

builder.AddHelmetWebSecurity(SecurityProfile.Spa)
    .AddRateLimiting(rl =>
    {
        rl.PermitLimit = 300;     // requests per window
        rl.WindowMinutes = 1;     // window duration
    });

Fixed-window rate limiting per remote IP. Returns 429 Too Many Requests when exceeded.

CORS

// Allow specific origins
builder.AddHelmetWebSecurity(SecurityProfile.Api)
    .AddCors(policy => policy
        .WithOrigins("https://app.example.com")
        .AllowCredentials()
        .WithMethods("GET", "POST"));

Default: deny all cross-origin requests.

Complete Examples

Angular SPA with OIDC

builder.AddHelmetWebSecurity(SecurityProfile.Spa)
    .AddScriptHash("sha256-MhtPZXr7+LpJUY5qtMutB+qWfQtMaPccfe7QXtCcEYc=")  // Angular critical CSS onload
    .ConfigureCsp(csp => csp
        .ConnectSrc(c => c.Self().Source("https://auth.example.com")))         // allow OIDC calls
    .ConfigureCrossOrigin(co => co
        .OpenerPolicy(CrossOriginOpenerPolicy.SameOriginAllowPopups));         // OIDC popup login

var app = builder.Build();
app.UseHelmetWebSecurity();
app.UseStaticFiles();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.MapFallbackToFile("/index.html").RequireAuthorization();
app.Run();

Pure REST API

builder.AddHelmetWebSecurity(SecurityProfile.Api)
    .AddCors(policy => policy
        .WithOrigins("https://app.example.com")
        .WithMethods("GET", "POST", "PUT", "DELETE")
        .WithHeaders("Authorization", "Content-Type"));

var app = builder.Build();
app.UseHelmetWebSecurity();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();

Razor Pages with nonces

builder.AddHelmetWebSecurity(SecurityProfile.Spa)
    .ConfigureCsp(csp => csp
        .ScriptSrc(s => s.Self().Nonce().StrictDynamic())
        .StyleSrc(s => s.Self().Nonce()))
    .UseNonces();

var app = builder.Build();
app.UseHelmetWebSecurity();
// ...

<script csp-nonce src="~/js/site.js"></script>
<style csp-nonce>
    /* critical CSS */
</style>

Gradual CSP rollout with Report-Only

// Step 1: Deploy with report-only to discover violations
builder.AddHelmetWebSecurity(SecurityProfile.Spa)
    .ConfigureCspReportOnly(csp => csp
        .ScriptSrc(s => s.Self().StrictDynamic())
        .ReportTo("csp"))
    .AddReportingEndpoint("csp", "/api/security/csp-report");

// Step 2: After fixing violations, enforce
builder.AddHelmetWebSecurity(SecurityProfile.Spa)
    .ConfigureCsp(csp => csp
        .ScriptSrc(s => s.Self().StrictDynamic()));

Comparison with NWebsec

Feature Helmet.AspNetCore NWebsec
Content-type-aware headers Yes (automatic) No (applies to all)
Profile-based defaults Yes (Spa/Api) No (manual setup)
CSP builder Yes (fluent) Yes (fluent)
CSP Report-Only Yes Yes
CSP nonce (tag helper) Yes Yes
CSP hash support Yes (SHA-256/384/512) Yes
strict-dynamic Yes Yes
report-to + Reporting-Endpoints Yes (modern) report-uri only (legacy)
Per-endpoint overrides Yes (attributes) Yes (attributes)
Permissions-Policy Yes (30+ features) No
Cross-Origin isolation (COOP/COEP/CORP) Yes No
Kestrel hardening Yes No
Rate limiting Yes No
Redirect validation Yes Yes
Server header removal Yes (via Kestrel) No
HSTS Yes Yes
X-Robots-Tag Yes (configurable) Yes (configurable)

Migration

Zero breaking changes for existing consumers:

// These continue to work unchanged:
builder.AddHelmetWebSecurity(SecurityProfile.Spa)
    .AddScriptHash("sha256-MhtPZXr7+LpJUY5qtMutB+qWfQtMaPccfe7QXtCcEYc=");

builder.AddHelmetWebSecurity(SecurityProfile.Api);

Removed (no current consumers):

  • SkipBrowserHeaders() — replaced by automatic content-type detection
  • CustomCsp property — replaced by ConfigureCsp()

Implementation Phases

Phase Scope Delivers
1 Content-type-aware middleware + configurable headers + CSP builder Fixes original question; makes every header configurable; full CSP directive control
2 Nonce + report-only + reporting endpoints + sandbox builder Modern CSP best practices; safe rollout via report-only
3 Per-endpoint attributes + redirect validation + tag helper NWebsec feature parity and beyond

NuGet Packaging

The .csproj must include the README for NuGet:

<PropertyGroup>
  <PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>

<ItemGroup>
  <None Include="README.md" Pack="true" PackagePath="\" />
</ItemGroup>

Testing

  • Unit tests: CspBuilder/CspSourceBuilder string output, content-type classification (IsHtmlResponse), policy resolution from profile + overrides, nonce format/uniqueness, redirect validation logic, PermissionsPolicyBuilder output, XRobotsTagBuilder output
  • Integration tests: Full middleware pipeline with various content types verifying correct header subsets; per-endpoint attribute overrides; nonce present in both CSP header and HttpContext; report-only header generation
Product Compatible and additional computed target framework versions.
.NET net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net9.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.1.0 146 4/22/2026