IM.ContentSecurityPolicy
2.0.4
dotnet add package IM.ContentSecurityPolicy --version 2.0.4
NuGet\Install-Package IM.ContentSecurityPolicy -Version 2.0.4
<PackageReference Include="IM.ContentSecurityPolicy" Version="2.0.4" />
<PackageVersion Include="IM.ContentSecurityPolicy" Version="2.0.4" />
<PackageReference Include="IM.ContentSecurityPolicy" />
paket add IM.ContentSecurityPolicy --version 2.0.4
#r "nuget: IM.ContentSecurityPolicy, 2.0.4"
#:package IM.ContentSecurityPolicy@2.0.4
#addin nuget:?package=IM.ContentSecurityPolicy&version=2.0.4
#tool nuget:?package=IM.ContentSecurityPolicy&version=2.0.4
IM.ContentSecurityPolicy
Content Security Policy (CSP) headers and per-request nonces for ASP.NET Core and Umbraco CMS 13 to 18.
The package:
- generates a random nonce for each request;
- writes a
Content-Security-PolicyorContent-Security-Policy-Report-Onlyheader built fromCspOptions; - adds the nonce to
<script>,<style>and<link>tags through a tag helper, HTML helpers or view components.
It targets net8.0 and runs on .NET 8, 9 and 10. It references Umbraco.Cms.Core 13 to 18 so that the Umbraco Marketplace lists it. The code does not call Umbraco, so it also works in plain ASP.NET Core apps.
Source availability: the source repository is private. The package is distributed through NuGet.org. Report bugs and request features in the issue tracker and ask questions in Discussions.
| Umbraco | .NET |
|---|---|
| 13, 14 | 8 |
| 15, 16 | 9 |
| 17, 18 | 10 |
For Umbraco 10 to 12 use version 1.0.2.
Installation
dotnet add package IM.ContentSecurityPolicy
Setup
using IM.ContentSecurityPolicy.Extensions;
using IM.ContentSecurityPolicy.Middleware;
builder.Services.AddNonceServices();
var app = builder.Build();
app.UseNonceGeneration();
app.UseContentSecurityPolicy();
Call UseNonceGeneration before UseContentSecurityPolicy. In the other order the header and the page get different nonces.
Register the tag helpers and the HTML helper namespace in Views/_ViewImports.cshtml:
@addTagHelper *, IM.ContentSecurityPolicy
@using IM.ContentSecurityPolicy.Extensions
Adding the nonce to tags
Tag helper
Add asp-nonce="true" to a <script>, <style> or <link> element:
<script src="~/js/site.js" asp-nonce="true"></script>
<script asp-nonce="true">
console.log('inline script');
</script>
<link rel="stylesheet" href="~/css/site.css" asp-nonce="true" />
The nonce attribute is written only when the request already has a nonce, so the nonce middleware must run first.
HTML helpers
| Method | Output |
|---|---|
Html.ScriptWithNonce(src, type = "text/javascript", attributes = null) |
<script src="..." type="..." nonce="..."></script> |
Html.InlineScriptWithNonce(content, type = "text/javascript") |
<script type="..." nonce="...">content</script> |
Html.StyleWithNonce(href, attributes = null) |
<link rel="stylesheet" href="..." nonce="..." /> |
Html.InlineStyleWithNonce(content) |
<style nonce="...">content</style> |
Html.GetNonceAttribute() |
nonce="...", or an empty string when the request has no nonce |
attributes is an anonymous object such as new { defer = "defer" }. Property names are written in lower case.
The helpers do not HTML-encode their arguments. Pass only trusted values.
View components
The view components put the nonce into a view model and render a view. The package does not include the views, so add one per component you use, for example Views/Shared/Components/NonceScript/Default.cshtml:
@model IM.ContentSecurityPolicy.ViewComponents.NonceScriptViewModel
<script src="@Url.Content(Model.Src)" type="@Model.Type" nonce="@Model.Nonce"></script>
@await Component.InvokeAsync("NonceScript", new { src = "~/js/site.js" })
| Component | Arguments | Model properties |
|---|---|---|
NonceScript |
src, type = "text/javascript", attributes = null |
Src, Type, Nonce, Attributes |
NonceInlineScript |
content, type = "text/javascript" |
Content, Type, Nonce |
NonceStyle |
href, rel = "stylesheet", attributes = null |
Href, Rel, Nonce, Attributes |
NonceInlineStyle |
content |
Content, Nonce |
The models are in the IM.ContentSecurityPolicy.ViewComponents namespace and are named after the component, for example NonceInlineStyleViewModel.
Nonce service
Inject INonceService from IM.ContentSecurityPolicy.Services to read the nonce in your own code.
| Method | Description |
|---|---|
GenerateNonce() |
Creates a new nonce and stores it in HttpContext.Items. |
GetNonce() |
Returns the stored nonce, or creates one if there is none. |
HasNonce() |
Returns true when the request has a nonce. |
A nonce is 32 bytes from RandomNumberGenerator, Base64-encoded.
Configuration
AddNonceServices takes an optional Action<CspOptions>. The source 'nonce-{nonce}' in any list is replaced with the request nonce.
builder.Services.AddNonceServices(options =>
{
options.ScriptSrc = new[] { "'self'", "'nonce-{nonce}'", "https://cdn.example.com" };
options.ImgSrc = new[] { "'self'", "data:", "https:" };
options.ReportOnly = true;
options.ReportUri = "https://example.com/csp-report";
});
| Property | Directive | Default |
|---|---|---|
DefaultSrc |
default-src |
'self' |
ScriptSrc |
script-src |
'self' 'nonce-{nonce}' |
StyleSrc |
style-src |
'self' 'nonce-{nonce}' |
FontSrc |
font-src |
'self' |
ImgSrc |
img-src |
'self' data: |
ConnectSrc |
connect-src |
'self' |
FrameSrc |
frame-src |
'self' |
ObjectSrc |
object-src |
'none' |
BaseUri |
base-uri |
'self' |
FormAction |
form-action |
'self' |
FrameAncestors |
frame-ancestors |
'self' |
CustomDirectives |
any other directive | empty |
ReportUri |
report-uri |
not set |
UpgradeInsecureRequests |
upgrade-insecure-requests |
true |
ReportOnly |
sends Content-Security-Policy-Report-Only |
false |
An empty array leaves the directive out of the header.
Builder
CspOptionsBuilder sets the same properties with With... methods. Pass it the options instance to change it in place:
using IM.ContentSecurityPolicy.Options;
builder.Services.AddNonceServices(options => new CspOptionsBuilder(options)
.WithScriptSrc("'self'", "'nonce-{nonce}'", "https://cdn.example.com")
.WithConnectSrc("'self'", "https://api.example.com")
.WithCustomDirective("worker-src", "'self'"));
There are two presets:
CspOptionsBuilder.CreateStrict()is the default policy withframe-src 'none'.CspOptionsBuilder.CreateDevelopment()adds'unsafe-inline'and'unsafe-eval'to scripts,'unsafe-inline'to styles,data:to fonts,https:to images andws:wss:to connections, and turns offupgrade-insecure-requests.
A preset returns a new builder, so copy the values you need:
builder.Services.AddNonceServices(options =>
{
if (builder.Environment.IsDevelopment())
{
var dev = CspOptionsBuilder.CreateDevelopment().Build();
options.ScriptSrc = dev.ScriptSrc;
options.StyleSrc = dev.StyleSrc;
options.ConnectSrc = dev.ConnectSrc;
options.UpgradeInsecureRequests = false;
}
});
Options on the middleware
app.UseContentSecurityPolicy(options => { ... }) starts from the defaults and ignores the options registered with AddNonceServices.
Umbraco
The default policy blocks the inline scripts of the Umbraco backoffice. Apply the middleware to front-end requests only, before app.UseUmbraco():
app.UseWhen(
context => !context.Request.Path.StartsWithSegments("/umbraco"),
branch => branch.UseNonceGeneration().UseContentSecurityPolicy());
Security notes
- Start with
ReportOnly = trueand aReportUri. Enforce the policy once the reports are clean. - Nonces cover
<script>,<style>and stylesheet<link>elements. They do not cover inline event handlers such asonclick, orjavascript:URLs. - Do not put a nonce on a script that contains user input.
- Cached markup keeps the nonce it was rendered with. Do not use output caching or Umbraco cached partials for markup that contains a nonce.
License
Free for private and commercial use under the IM.ContentSecurityPolicy License: no modification, distribution only as part of your website or application, and a commercial website or application shows "Uses IM.ContentSecurityPolicy by Idea Makers" with a link to https://ideamakers.pl.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. 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. |
-
net8.0
- Umbraco.Cms.Core (>= 13.1.1 && < 19.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Release process only: published to NuGet with trusted publishing. Shorter license section in the README. No code changes.