SecurityHeaders gives ASP.NET Core a one-call header policy
Small package to allow adding security headers to ASP.NET Core websites
At a glance
- What is it?
- NetEscapades.AspNetCore.SecurityHeaders is a small ASP.NET Core middleware package that sets a safe default set of response headers, and its real value is the fluent policy builder for tightening them endpoint by endpoint.
- Who is it for?
- SecurityHeaders suits a team that wants a defensible baseline of response headers on an ASP.NET Core application today, and it suits anyone who needs a stricter Content-Security-Policy on most routes with a relaxed one on a specific endpoint, since named policies cover that case at the cost of more configuration.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 92 days ago.
- What is it written in?
- Mainly C#, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
One middleware call covers every response
The package is described as a small package to allow adding security headers to ASP.NET Core websites, and the smallest possible use is a single call in the pipeline.
public void Configure(IApplicationBuilder app)
{
app.UseSecurityHeaders();
// other middleware e.g. static files, MVC etc
}The README notes that the order of middleware matters, so to apply the headers to all requests it should be configured first in the pipeline. That single call adds a specific set of headers to every response that passes through. The set is X-Content-Type-Options: nosniff, Strict-Transport-Security: max-age=31536000 which is applied only to HTTPS responses, X-Frame-Options: Deny, Referrer-Policy: strict-origin-when-cross-origin, a Content-Security-Policy of object-src 'none'; form-action 'self'; frame-ancestors 'none', Cross-Origin-Opener-Policy: same-origin, Cross-Origin-Embedder-Policy: credentialless, and Cross-Origin-Resource-Policy: same-site. The audience is any .NET developer who knows the headers exist but does not want to hand-write them per response.
The defaults are explicitly labelled as a floor, not a target
The README is unusually honest about what the defaults are worth. It says these policies represent a safe set of minimum defaults that should be valid for most sites, but are not the most secure they could be, and it advises thinking about which features you need and restricting them where possible. It points at two specific things to strengthen: a stronger Content Security Policy, and a Permissions Policy. The default CSP shown above is genuinely minimal, it blocks objects, restricts form targets to self and forbids framing entirely, but it does not constrain script-src or style-src at all, which is where the interesting XSS and data exfiltration paths live. So the default is a floor to start from, and a team that ships it unchanged is shipping a policy that is inert against inline script injection. Understanding that distinction is the difference between using this package well and using it as a checkbox.
The policy builder is where the real work happens
Customisation is done with a HeaderPolicyCollection, which is built with helper methods for each header, and the CustomHeader type lets you add anything else. The example below sets a spread of headers, removes the server header, builds a Content Security Policy through a builder callback, and adds a custom header.
var policyCollection = new HeaderPolicyCollection()
.AddFrameOptionsDeny()
.AddContentTypeOptionsNoSniff()
.AddStrictTransportSecurityMaxAgeIncludeSubDomains(maxAgeInSeconds: 60 * 60 * 24 * 365) // maxage = one year in seconds
.AddReferrerPolicyStrictOriginWhenCrossOrigin()
.RemoveServerHeader()
.AddContentSecurityPolicy(builder =>
{
builder.AddObjectSrc().None();
builder.AddFormAction().Self();
builder.AddFrameAncestors().None();
})
.AddCrossOriginOpenerPolicy(x => x.SameOrigin());
.AddCustomHeader("X-My-Test-Header", "Header value");
app.UseSecurityHeaders(policyCollection);Two details are worth noticing. The strict transport helper takes a max age in seconds and the example converts a year into that number inline. And RemoveServerHeader is available, which suppresses the Server header that IIS and Kestrel otherwise advertise.
Three ways to express the same collection
The API offers three levels of ceremony for the same result, which is good design because most applications need one and a few need the third. The verbose form instantiates a HeaderPolicyCollection and passes it to UseSecurityHeaders. There is a terser form, since the default set is itself encapsulated in a helper, so AddDefaultSecurityHeaders replaces the individual calls.
var policyCollection = new HeaderPolicyCollection()
.AddDefaultSecurityHeaders()
.AddCustomHeader("X-My-Test-Header", "Header value");
app.UseSecurityHeaders(policyCollection);Third, there is a convenience overload of UseSecurityHeaders that takes an Action of HeaderPolicyCollection, so the collection does not have to be built as a separate object.
app.UseSecurityHeaders(policies =>
policies
.AddDefaultSecurityHeaders()
.AddStrictTransportSecurityMaxAgeIncludeSubDomains(maxAgeInSeconds: 63072000)
);That last example also shows the override pattern. Since the default collection already includes a strict transport header, adding a second one with a different max age changes that header rather than adding a duplicate, which is how you keep the defaults and adjust a single value.
Per-endpoint policies need named configuration and early registration
Different routes needing different headers is the case the package handles with more work, and the README is direct that it requires more configuration. The scenario given is a very restrictive Content-Security-Policy by default with a more relaxed one on specific endpoints. The mechanism is named policies registered on the service collection.
var builder = WebApplication.CreateBuilder();
builder.Services.AddSecurityHeaderPolicies()
.SetDefaultPolicy(policy => policy.AddDefaultSecurityHeaders())
.AddPolicy("CustomPolicy", policy => policy.AddCustomHeader("X-Custom", "SomeValue"));The default policy applies to all requests that are not customised for an endpoint, and named policies are then applied where a route opts in. The second requirement is ordering, and the README section for it is titled as calling UseSecurityHeaders early in the middleware pipeline. Both halves matter: the policies have to be registered in the container before the middleware runs, and the middleware has to sit early enough for its headers to be attached before anything short-circuits the pipeline. This is the part of the package where a misconfiguration shows up as missing headers on only some routes, which is annoying to diagnose.
What the package will not do for you
SecurityHeaders writes headers. It does not decide what your Content-Security-Policy should say, and it does not verify that the result is what you intended. The README does not document a report-only mode for Content-Security-Policy, a nonce or hash based script allowlist, a way to report violations to a collector endpoint, or a test helper that asserts the headers are present. The default collection includes Cross-Origin-Embedder-Policy: credentialless, which is what enables cross-origin isolation for the heavy JavaScript APIs, and the isolation is not free: it can break third-party embeds and resources that do not send the right headers, and nothing in the package tells you so before you ship it. The other limit is scope. If your application is served by a CDN or a reverse proxy that already sets headers, you now have two layers able to set the same header, and the effective value is whichever layer wins, which is not something the package resolves for you.
A small, actively moved package with a normal upgrade path
The package is published on NuGet as NetEscapades.AspNetCore.SecurityHeaders and is installed either from the Visual Studio Package Manager Console or with the dotnet CLI.
dotnet add package NetEscapades.AspNetCore.SecurityHeaders --version 1.3.1The README also shows adding the reference straight into the project file, which is what you want in a repository that pins dependencies centrally.
<ItemGroup>
<PackageReference Include="NetEscapades.AspNetCore.SecurityHeaders" Version="1.3.1" />
</ItemGroup>The release history is short and regular, with v1.3.1 on 2025-12-20, v1.3.0 on 2025-11-21 and v1.2.0 on 2025-09-21, and the last push was on 2026-07-22. The licence is MIT, which is what you would expect for a package meant to sit in the pipeline of a commercial application. The repository carries a CHANGELOG.md, a ReleaseNotes.md, a solution file and a test directory, and the build is driven by build.cmd, build.ps1 and build.sh, so the upgrade cost for a consuming application is a version bump and a look at the changelog.
Editorial conclusion
SecurityHeaders suits a team that wants a defensible baseline of response headers on an ASP.NET Core application today, and it suits anyone who needs a stricter Content-Security-Policy on most routes with a relaxed one on a specific endpoint, since named policies cover that case at the cost of more configuration. It is the wrong tool for a non-ASP.NET Core application, for a static site already served with headers by its CDN, and for a team that expects the middleware to produce a working policy on its own, because the default policy is deliberately not the most secure it could be. Before adopting it, decide whether you will replace the default Content-Security-Policy with one that matches your own scripts, and check the middleware order, since headers must be added before the rest of the pipeline or they will not reach every response. The last push was on 2026-07-22 and the current release is v1.3.1 from 2025-12-20.
Frequently asked questions
How do I install NetEscapades.AspNetCore.SecurityHeaders?
Install the NetEscapades.AspNetCore.SecurityHeaders NuGet package with Install-Package from the Visual Studio Package Manager Console, or with dotnet add package NetEscapades.AspNetCore.SecurityHeaders from the command line. The README also shows adding a PackageReference to the project file.
Which security headers does the default policy add?
The default set is X-Content-Type-Options nosniff, Strict-Transport-Security with a max-age of 31536000 on HTTPS responses, X-Frame-Options Deny, Referrer-Policy strict-origin-when-cross-origin, a Content-Security-Policy of object-src none, form-action self and frame-ancestors none, plus Cross-Origin-Opener-Policy same-origin, Cross-Origin-Embedder-Policy credentialless and Cross-Origin-Resource-Policy same-site.
How do I apply different security headers to different endpoints?
Register named policies on the service collection with AddSecurityHeaderPolicies, set the default policy with SetDefaultPolicy, and add named ones with AddPolicy. The middleware then has to be called early in the pipeline so its headers reach every response.
Is the default Content-Security-Policy in this package the most secure one?
No. The README describes the defaults as a safe set of minimum defaults that should be valid for most sites but are not the most secure they could be, and it advises using a stronger Content Security Policy and a Permissions Policy where possible.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/andrewlock-netescapades-aspnetcore-securityheaders)Was this useful?