Skip to content

Commit 89d4815

Browse files
committed
feat(CSP): add 'trusted-types' CSP directive support
add possibility to specify 'trusted-types' and 'require-trusted-types-for' CSP directives re juunas11#57
1 parent 8249413 commit 89d4815

File tree

6 files changed

+186
-2
lines changed

6 files changed

+186
-2
lines changed

src/Joonasw.AspNetCore.SecurityHeaders/Csp/Builder/CspBuilder.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,11 @@ public class CspBuilder
8181
/// Setups up rules for controlling when subresource integrity must be used
8282
/// </summary>
8383
public CspRequireSriBuilder RequireSri { get; } = new CspRequireSriBuilder();
84+
85+
/// <summary>
86+
/// Setups up rules for controlling when to restrict the creation of Trusted Types policies
87+
/// </summary>
88+
public CspTrustedTypesBuilder RequireTrustedTypes { get; } = new CspTrustedTypesBuilder();
8489

8590
public Func<CspSendingHeaderContext, Task> OnSendingHeader { get; set; } = context => Task.CompletedTask;
8691

@@ -150,6 +155,7 @@ public CspOptions BuildCspOptions()
150155
_options.Prefetch = AllowPrefetch.BuildOptions();
151156
_options.BaseUri = AllowBaseUri.BuildOptions();
152157
_options.RequireSri = RequireSri.BuildOptions();
158+
_options.TrustedTypes = RequireTrustedTypes.BuildOptions();
153159
_options.OnSendingHeader = OnSendingHeader;
154160
return _options;
155161
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
using System;
2+
using Joonasw.AspNetCore.SecurityHeaders.Csp.Options;
3+
4+
namespace Joonasw.AspNetCore.SecurityHeaders.Csp.Builder
5+
{
6+
/// <summary>
7+
/// Builder for Content Security Policy
8+
/// rules related to Trusted Types.
9+
/// </summary>
10+
public class CspTrustedTypesBuilder
11+
{
12+
private readonly CspTrustedTypesOptions _options = new CspTrustedTypesOptions();
13+
14+
/// <summary>
15+
/// Disallows creating any Trusted Type policy (same as not specifying any <c>policyName</c>).
16+
/// </summary>
17+
public void DisallowAll()
18+
{
19+
_options.AllowNone = true;
20+
}
21+
22+
/// <summary>
23+
/// Allow any unique policy name ('allow-duplicates' may relax that further)
24+
/// </summary>
25+
/// <returns>The builder for call chaining</returns>
26+
public CspTrustedTypesBuilder WithAnyUniquePolicy()
27+
{
28+
_options.AllowAny = true;
29+
return this;
30+
}
31+
32+
/// <summary>
33+
/// Allow CSS from the given
34+
/// <paramref name="policyName"/>.
35+
/// </summary>
36+
/// <param name="policyName">A valid policy name consists only of alphanumeric characters, or one of "-#=_/@.%". </param>
37+
/// <returns>The builder for call chaining</returns>
38+
public CspTrustedTypesBuilder WithPolicyName(string policyName)
39+
{
40+
if (policyName == null) throw new ArgumentNullException(nameof(policyName));
41+
if (policyName.Length == 0) throw new ArgumentException("Policy Name can't be empty", nameof(policyName));
42+
43+
_options.TrustedPolicies.Add(policyName);
44+
return this;
45+
}
46+
47+
public CspTrustedTypesBuilder AllowDuplicates()
48+
{
49+
_options.AllowDuplicates = true;
50+
return this;
51+
}
52+
53+
public CspTrustedTypesBuilder RequireTrustedTypesForScript()
54+
{
55+
_options.RequireTrustedTypesForScript = true;
56+
return this;
57+
}
58+
59+
public CspTrustedTypesOptions BuildOptions()
60+
{
61+
return _options;
62+
}
63+
}
64+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
using System.Collections.Generic;
2+
3+
namespace Joonasw.AspNetCore.SecurityHeaders.Csp.Options
4+
{
5+
/// <summary>
6+
/// This directive declares an allow-list of trusted type policy names created with
7+
/// <c>TrustedTypes.createPolicy</c> from Trusted Types API
8+
/// </summary>
9+
public class CspTrustedTypesOptions
10+
{
11+
/// <summary>
12+
/// Collection of sources from where these resources can be loaded.
13+
/// </summary>
14+
/// <remark>
15+
/// A valid policy name consists only of alphanumeric characters, or one of "-#=_/@.%".
16+
/// </remark>
17+
public ICollection<string> TrustedPolicies { get; set; }
18+
19+
/// <summary>
20+
/// If <c>true</c> allows for creating policies with a name that was already used.
21+
/// </summary>
22+
public bool AllowDuplicates { get; set; }
23+
24+
/// <summary>
25+
/// Disallows creating any Trusted Type policy (same as not specifying any <c>policyName</c>).
26+
/// </summary>
27+
public bool AllowNone { get; set; }
28+
29+
/// <summary>
30+
/// A star (*) as a policy name instructs the user agent to allow any unique policy name
31+
/// (<c>'allow-duplicates'</c> may relax that further).
32+
/// </summary>
33+
public bool AllowAny { get; set; }
34+
35+
/// <summary>
36+
/// Instructs user agents to control the data passed to DOM XSS sink functions, like Element.innerHTML setter.
37+
/// Those functions only accept non-spoofable, typed values created by Trusted Type policies, and reject strings.
38+
/// </summary>
39+
public bool RequireTrustedTypesForScript { get; set; }
40+
41+
public CspTrustedTypesOptions()
42+
{
43+
TrustedPolicies = new List<string>();
44+
}
45+
46+
private ICollection<string> GetParts()
47+
{
48+
ICollection<string> parts = new List<string>();
49+
50+
if (AllowNone)
51+
{
52+
parts.Add("'none'");
53+
}
54+
else
55+
{
56+
if (AllowAny)
57+
{
58+
parts.Add("*");
59+
}
60+
61+
foreach (string allowedSource in TrustedPolicies)
62+
{
63+
parts.Add(allowedSource);
64+
}
65+
66+
if (AllowDuplicates)
67+
{
68+
parts.Add("'allow-duplicates'");
69+
}
70+
}
71+
return parts;
72+
}
73+
74+
/// <inheritdoc />
75+
public override string ToString()
76+
{
77+
ICollection<string> parts = GetParts();
78+
79+
if (parts.Count == 0)
80+
{
81+
return string.Empty;
82+
}
83+
84+
var result = "trusted-types " + string.Join(" ", parts);
85+
86+
if (RequireTrustedTypesForScript)
87+
{
88+
result += "; require-trusted-types-for 'script'";
89+
}
90+
91+
return result;
92+
}
93+
}
94+
}

src/Joonasw.AspNetCore.SecurityHeaders/CspOptions.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,11 @@ public class CspOptions
109109
/// Options for controlling what subresource integrity attributes should be required on
110110
/// </summary>
111111
public CspRequireSriOptions RequireSri { get; set; }
112+
113+
/// <summary>
114+
/// Options for controlling how user agents restricts the creation of Trusted Types policies
115+
/// </summary>
116+
public CspTrustedTypesOptions TrustedTypes { get; set; }
112117

113118
/// <summary>
114119
/// The URL where violation reports should be sent.
@@ -144,6 +149,7 @@ public class CspOptions
144149

145150
public CspOptions()
146151
{
152+
TrustedTypes = new CspTrustedTypesOptions();
147153
Script = new CspScriptSrcOptions();
148154
Style = new CspStyleSrcOptions();
149155
Default = new CspDefaultSrcOptions();
@@ -200,7 +206,8 @@ public CspOptions()
200206
Worker.ToString(nonceService),
201207
Prefetch.ToString(nonceService),
202208
BaseUri.ToString(nonceService),
203-
RequireSri.ToString()
209+
RequireSri.ToString(),
210+
TrustedTypes.ToString()
204211
};
205212
if (BlockAllMixedContent)
206213
{

src/Joonasw.AspNetCore.SecurityHeaders/Joonasw.AspNetCore.SecurityHeaders.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
<!-- /Source Link support-->
2121
</PropertyGroup>
2222
<ItemGroup>
23-
<None Include="..\..\LICENSE.txt" Pack="true" PackagePath="LICENSE.txt"/>
23+
<None Include="..\..\LICENSE.txt" Pack="true" PackagePath="LICENSE.txt" />
2424
</ItemGroup>
2525
<ItemGroup>
2626
<PackageReference Include="System.ValueTuple" Version="4.4.0" />

test/Joonasw.AspNetCore.SecurityHeaders.Tests/CspBuilderTests.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,19 @@ public void RequireSriFor_ReturnsCorrectHeader()
127127

128128
Assert.Equal("require-sri-for script", headerValue);
129129
}
130+
131+
132+
[Fact]
133+
public void RequireTrustedTypes_ReturnsCorrectHeader()
134+
{
135+
var builder = new CspBuilder();
136+
137+
builder.RequireTrustedTypes.DisallowAll();
138+
139+
var headerValue = builder.BuildCspOptions().ToString(null).headerValue;
140+
141+
Assert.Equal("trusted-types 'none'", headerValue);
142+
}
130143

131144
[Fact]
132145
public async Task OnSendingHeader_ShouldNotSendTest()

0 commit comments

Comments
 (0)