diff --git a/example/Helpers/RealisticContentSecurityPolicyGenerators.cs b/example/Helpers/RealisticContentSecurityPolicyGenerators.cs
new file mode 100644
index 0000000..de1ef6b
--- /dev/null
+++ b/example/Helpers/RealisticContentSecurityPolicyGenerators.cs
@@ -0,0 +1,151 @@
+using OwaspHeaders.Core.Enums;
+using OwaspHeaders.Core.Extensions;
+using OwaspHeaders.Core.Models;
+
+namespace example.Helpers;
+
+public static class RealisticContentSecurityPolicyGenerators
+{
+ ///
+ /// Represents an instance of the with
+ /// the Content-Security Policy from the OWASP homepage.
+ ///
+ ///
+ /// The instance of hte that this method
+ /// returns, DOES NOT contain any other header values. The return value from this method is
+ /// provided as a way of testing the CSP generation code. And should NOT be used in a live
+ /// environment (unless you are replacing the OWASP website with ASP .NET Core 😛)
+ ///
+ ///
+ /// An instance of the with headers which
+ /// represent the Content-Security Policy taken from the OWASP website homepage on May 15th, 2023
+ ///
+ public static SecureHeadersMiddlewareConfiguration GenerateOwaspHomePageCsp() =>
+ SecureHeadersMiddlewareExtensions.BuildDefaultConfiguration()
+ .UseContentSecurityPolicy()
+ .SetCspUris(
+ new List
+ {
+ new() { CommandType = CspCommandType.Directive, DirectiveOrUri = "self" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://api.github.com" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.githubusercontent.com" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.google-analytics.com" },
+ new()
+ {
+ CommandType = CspCommandType.Uri,
+ DirectiveOrUri = "https://owaspadmin.azurewebsites.net"
+ },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.twimg.com" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://platform.twitter.com" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://www.youtube.com" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.doubleclick.net" }
+ }, CspUriType.DefaultUri)
+ .SetCspUris(
+ new List
+ {
+ new() { CommandType = CspCommandType.Directive, DirectiveOrUri = "self" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://api.github.com" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.githubusercontent.com" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.google-analytics.com" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://owaspadmin.azurewebsites.net" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.twimg.com" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://platform.twitter.com" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://www.youtube.com" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.doubleclick.net" },
+ }, CspUriType.FrameAncestors)
+ .SetCspUris(
+ new List
+ {
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.vuejs.org" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.stripe.com" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.wufoo.com" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.sched.com" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.google.com" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.twitter.com" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://www.youtube.com" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://w.soundcloud.com" },
+ }, CspUriType.Frame)
+ .SetCspUris(
+ new List
+ {
+ new() { CommandType = CspCommandType.Directive, DirectiveOrUri = "self" },
+ new() { CommandType = CspCommandType.Directive, DirectiveOrUri = "unsafe-inline" },
+ new() { CommandType = CspCommandType.Directive, DirectiveOrUri = "unsafe-eval" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://fonts.googleapis.com" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://app.diagrams.net" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://cdnjs.cloudflare.com" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://cse.google.com" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.vuejs.org" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.stripe.com" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.wufoo.com" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.youtube.com" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.meetup.com" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.sched.com" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.google-analytics.com" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://unpkg.com" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://buttons.github.io" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://www.google.com" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.gstatic.com" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.twitter.com" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.twimg.com" },
+ }, CspUriType.Script)
+ .SetCspUris(
+ new List
+ {
+ new() { CommandType = CspCommandType.Directive, DirectiveOrUri = "self" },
+ new() { CommandType = CspCommandType.Directive, DirectiveOrUri = "unsafe-inline" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.gstatic.com" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://cdnjs.cloudflare.com" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://www.google.com" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://fonts.googleapis.com" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://platform.twitter.com" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.twimg.com" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "data:" },
+ }, CspUriType.Style)
+ .SetCspUris(
+ new List
+ {
+ new() { CommandType = CspCommandType.Directive, DirectiveOrUri = "self" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "fonts.gstatic.com" }
+ }, CspUriType.Font)
+ .SetCspUris(
+ new List
+ {
+ new() { CommandType = CspCommandType.Directive, DirectiveOrUri = "self" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://pay.google.com" }
+ }, CspUriType.Manifest)
+ .SetCspUris(
+ new List
+ {
+ new() { CommandType = CspCommandType.Directive, DirectiveOrUri = "self" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.globalappsec.org" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "data:" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "www.w3.org" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://licensebuttons.net" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://img.shields.io" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.twitter.com" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://github.githubassets.com" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.twimg.com" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://platform.twitter.com" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.githubusercontent.com" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.vercel.app" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.cloudfront.net" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.coreinfrastructure.org" },
+ new()
+ {
+ CommandType = CspCommandType.Uri,
+ DirectiveOrUri = "https://*.securityknowledgeframework.org"
+ },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://badges.gitter.im" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://travis-ci.org" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://api.travis-ci.org" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://s3.amazonaws.com" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://snyk.io" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://coveralls.io" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://requires.io" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://github.com" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.googleapis.com" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.google.com" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "https://*.gstatic.com" },
+ }, CspUriType.Img);
+}
diff --git a/example/Program.cs b/example/Program.cs
index 8cbf298..91874db 100644
--- a/example/Program.cs
+++ b/example/Program.cs
@@ -1,4 +1,5 @@
-using OwaspHeaders.Core.Extensions;
+using example.Helpers;
+using OwaspHeaders.Core.Extensions;
var builder = WebApplication.CreateBuilder(args);
@@ -22,7 +23,7 @@
app.UseAuthorization();
-app.UseSecureHeadersMiddleware(SecureHeadersMiddlewareExtensions.BuildDefaultConfiguration());
+app.UseSecureHeadersMiddleware(RealisticContentSecurityPolicyGenerators.GenerateOwaspHomePageCsp());
app.MapControllers();
diff --git a/src/Extensions/StringBuilderExtensions.cs b/src/Extensions/StringBuilderExtensions.cs
index 2c18e17..82839b7 100644
--- a/src/Extensions/StringBuilderExtensions.cs
+++ b/src/Extensions/StringBuilderExtensions.cs
@@ -17,7 +17,7 @@ public static class StringBuilderExtensions
///
///
///
- private static StringBuilder TrimEnd(this StringBuilder sb)
+ public static StringBuilder TrimEnd(this StringBuilder sb)
{
if (sb == null || sb.Length == 0) return sb;
@@ -52,8 +52,8 @@ public static StringBuilder BuildValuesForDirective(this StringBuilder stringBui
if (directives.Any())
{
- stringBuilder.Append(string.Join(EmptySpace, directives.Select(directive => $"'{directive.DirectiveOrUri}'")));
- stringBuilder.Append(EmptySpace);
+ stringBuilder.Append(string.Join(EmptySpace,
+ directives.Select(directive => $"'{directive.DirectiveOrUri}'")));
}
}
diff --git a/src/OwaspHeaders.Core.csproj b/src/OwaspHeaders.Core.csproj
index 2a80cdd..7ae864a 100755
--- a/src/OwaspHeaders.Core.csproj
+++ b/src/OwaspHeaders.Core.csproj
@@ -1,7 +1,7 @@
An ASP.NET Core Middleware which adds the OWASP recommended HTTP headers for enhanced security.
- 6.0.5
+ 6.1.0
Jamie Taylor
OwaspHeaders.Core
netstandard2.0
diff --git a/src/OwaspHeadersCore.nuspec b/src/OwaspHeadersCore.nuspec
index 286fdf0..ec288ce 100644
--- a/src/OwaspHeadersCore.nuspec
+++ b/src/OwaspHeadersCore.nuspec
@@ -2,7 +2,7 @@
OwaspHeaders.Core
- 6.0.5
+ 6.1.0
GaProgMan
GaProgMan
docs\README-NuGet.md
diff --git a/src/SecureHeadersMiddleware.cs b/src/SecureHeadersMiddleware.cs
index de6adbc..b4b3af3 100755
--- a/src/SecureHeadersMiddleware.cs
+++ b/src/SecureHeadersMiddleware.cs
@@ -14,6 +14,7 @@ public class SecureHeadersMiddleware
{
private readonly RequestDelegate _next;
private readonly SecureHeadersMiddlewareConfiguration _config;
+ private string _calculatedContentSecurityPolicy;
public SecureHeadersMiddleware(RequestDelegate next, SecureHeadersMiddlewareConfiguration config)
{
@@ -59,20 +60,27 @@ public async Task InvokeAsync(HttpContext httpContext)
if (_config.UseContentSecurityPolicyReportOnly)
{
-
+ if (string.IsNullOrWhiteSpace(_calculatedContentSecurityPolicy))
+ {
+ _calculatedContentSecurityPolicy = _config.ContentSecurityPolicyReportOnlyConfiguration.BuildHeaderValue();
+ }
httpContext.TryAddHeader(Constants.ContentSecurityPolicyReportOnlyHeaderName,
- _config.ContentSecurityPolicyReportOnlyConfiguration.BuildHeaderValue());
+ _calculatedContentSecurityPolicy);
}
else if (_config.UseContentSecurityPolicy)
{
+ if (string.IsNullOrWhiteSpace(_calculatedContentSecurityPolicy))
+ {
+ _calculatedContentSecurityPolicy = _config.ContentSecurityPolicyConfiguration.BuildHeaderValue();
+ }
httpContext.TryAddHeader(Constants.ContentSecurityPolicyHeaderName,
- _config.ContentSecurityPolicyConfiguration.BuildHeaderValue());
+ _calculatedContentSecurityPolicy);
}
if (_config.UseXContentSecurityPolicy)
{
httpContext.TryAddHeader(Constants.XContentSecurityPolicyHeaderName,
- _config.ContentSecurityPolicyConfiguration.BuildHeaderValue());
+ _config.ContentSecurityPolicyConfiguration.BuildHeaderValue());
}
if (_config.UsePermittedCrossDomainPolicy)
diff --git a/tests/RegressionTests.cs b/tests/RegressionTests.cs
deleted file mode 100644
index 0da1569..0000000
--- a/tests/RegressionTests.cs
+++ /dev/null
@@ -1,74 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-using Microsoft.AspNetCore.Http;
-using OwaspHeaders.Core;
-using OwaspHeaders.Core.Enums;
-using OwaspHeaders.Core.Extensions;
-using OwaspHeaders.Core.Models;
-using Xunit;
-
-namespace tests;
-
-///
-/// This class contains a number of regression tests against bugs which were reported
-/// using GitHub issues. Each test will link to the issue in question. Please make sure
-/// that these tests still pass whenever making changes to this codebase
-///
-[ExcludeFromCodeCoverage]
-public class RegressionTests
-{
- private int _onNextCalledTimes;
- private readonly Task _onNextResult = Task.FromResult(0);
- private readonly RequestDelegate _onNext;
- private readonly DefaultHttpContext _context;
-
- public RegressionTests()
- {
- _onNext = _ =>
- {
- Interlocked.Increment(ref _onNextCalledTimes);
- return _onNextResult;
- };
- _context = new DefaultHttpContext();
- }
-
- ///
- /// This test exercises and provides regression against https://github.com/GaProgMan/OwaspHeaders.Core/issues/61
- ///
- [Fact]
- public async Task ContentSecurityPolicy_Adds_MultipleSameValue()
- {
- // arrange
- var headerPresentConfig = SecureHeadersMiddlewareBuilder.CreateBuilder().UseContentSecurityPolicy()
- .SetCspUris(
- new List
- {
- new()
- {
- CommandType = CspCommandType.Directive, DirectiveOrUri = "self"
- },
- new()
- {
- CommandType = CspCommandType.Uri, DirectiveOrUri = "cdnjs.cloudflare.com"
- }
- }, CspUriType.Style).Build();
- var secureHeadersMiddleware = new SecureHeadersMiddleware(_onNext, headerPresentConfig);
-
- // act
- await secureHeadersMiddleware.InvokeAsync(_context);
-
- // assert
- Assert.True(_context.Response.Headers.ContainsKey(Constants.ContentSecurityPolicyHeaderName));
-
- var headerValue = _context.Response.Headers[Constants.ContentSecurityPolicyHeaderName].ToList();
- Assert.Equal(1,
- headerValue.First()
- .Split(" ")
- .Count(hv => hv.Contains("cdnjs.cloudflare.com", StringComparison.InvariantCultureIgnoreCase)));
- }
-}
-
diff --git a/tests/RegressionTests/CspRegressionTests.cs b/tests/RegressionTests/CspRegressionTests.cs
new file mode 100644
index 0000000..944ca33
--- /dev/null
+++ b/tests/RegressionTests/CspRegressionTests.cs
@@ -0,0 +1,183 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using OwaspHeaders.Core;
+using OwaspHeaders.Core.Enums;
+using OwaspHeaders.Core.Extensions;
+using OwaspHeaders.Core.Models;
+using Xunit;
+
+namespace tests.RegressionTests;
+
+///
+/// This class contains a number of regression tests against bugs which were reported
+/// using GitHub issues. Each test will link to the issue in question. Please make sure
+/// that these tests still pass whenever making changes to this codebase
+///
+[ExcludeFromCodeCoverage]
+public class CspRegressionTests
+{
+ private int _onNextCalledTimes;
+ private readonly Task _onNextResult = Task.FromResult(0);
+ private readonly RequestDelegate _onNext;
+ private readonly DefaultHttpContext _context;
+
+ public CspRegressionTests()
+ {
+ _onNext = _ =>
+ {
+ Interlocked.Increment(ref _onNextCalledTimes);
+ return _onNextResult;
+ };
+ _context = new DefaultHttpContext();
+ }
+
+ ///
+ /// This test exercises and provides regression against https://github.com/GaProgMan/OwaspHeaders.Core/issues/61
+ ///
+ [Fact]
+ public async Task ContentSecurityPolicy_Adds_MultipleSameValue()
+ {
+ // arrange
+ var headerPresentConfig = SecureHeadersMiddlewareBuilder.CreateBuilder().UseContentSecurityPolicy()
+ .SetCspUris(
+ new List
+ {
+ new()
+ {
+ CommandType = CspCommandType.Directive, DirectiveOrUri = "self"
+ },
+ new()
+ {
+ CommandType = CspCommandType.Uri, DirectiveOrUri = "cdnjs.cloudflare.com"
+ }
+ }, CspUriType.Style).Build();
+ var secureHeadersMiddleware = new SecureHeadersMiddleware(_onNext, headerPresentConfig);
+
+ // act
+ await secureHeadersMiddleware.InvokeAsync(_context);
+
+ // assert
+ Assert.True(_context.Response.Headers.ContainsKey(Constants.ContentSecurityPolicyHeaderName));
+
+ var headerValue = _context.Response.Headers[Constants.ContentSecurityPolicyHeaderName].ToList();
+ Assert.Equal(1,
+ headerValue.First()
+ .Split(" ")
+ .Count(hv => hv.Contains("cdnjs.cloudflare.com", StringComparison.InvariantCultureIgnoreCase)));
+ }
+
+ ///
+ /// This test exercises and proves regression against unnecessary spaces being added between the final
+ /// directive and first URI in a CSP
+ ///
+ [Fact]
+ public async Task ContentSecurityPolicy_Adds_UnnecessarySpace_Between_FinalDirective_And_First_Uri()
+ {
+ // arrange
+ const string targetCsp = "style-src 'self' cdnjs.cloudflare.com;";
+ var headerPresentConfig = SecureHeadersMiddlewareBuilder.CreateBuilder()
+ .UseContentSecurityPolicy(blockAllMixedContent: false, upgradeInsecureRequests: false)
+ .SetCspUris(
+ // originally PRODUCES: style-src 'self' cdnjs.cloudflare.com;
+ new List
+ {
+ new() { CommandType = CspCommandType.Directive, DirectiveOrUri = "self" },
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "cdnjs.cloudflare.com" }
+ }, CspUriType.Style).Build();
+ var secureHeadersMiddleware = new SecureHeadersMiddleware(_onNext, headerPresentConfig);
+
+ // act
+ await secureHeadersMiddleware.InvokeAsync(_context);
+
+ // assert
+ Assert.True(_context.Response.Headers.ContainsKey(Constants.ContentSecurityPolicyHeaderName));
+
+ var headerStrings = _context.Response.Headers[Constants.ContentSecurityPolicyHeaderName].ToList();
+ Assert.NotNull(headerStrings);
+
+ var actualCsp = headerStrings.First();
+ var actualCharCount = actualCsp.Length;
+ var targetCharCount = targetCsp.Length;
+
+ Assert.True(string.Equals(targetCsp, actualCsp, StringComparison.InvariantCultureIgnoreCase));
+ Assert.Equal(targetCharCount, actualCharCount);
+ }
+
+ ///
+ /// This test exercises and proves regression against unnecessary spaces being added after a
+ /// directive in a generated CSP, when no URIs are provided
+ ///
+ [Fact]
+ public async Task ContentSecurityPolicy_Adds_UnnecessarySpace_After_FinalDirective_When_OnlyDirectivesProvided()
+ {
+ // arrange
+ const string targetCsp = "style-src 'self';";
+ var headerPresentConfig = SecureHeadersMiddlewareBuilder.CreateBuilder()
+ .UseContentSecurityPolicy(blockAllMixedContent: false, upgradeInsecureRequests: false)
+ .SetCspUris(
+ // originally PRODUCES: style-src 'self' ;
+ new List
+ {
+ new() { CommandType = CspCommandType.Directive, DirectiveOrUri = "self" }
+ }, CspUriType.Style).Build();
+ var secureHeadersMiddleware = new SecureHeadersMiddleware(_onNext, headerPresentConfig);
+
+ // act
+ await secureHeadersMiddleware.InvokeAsync(_context);
+
+ // assert
+ Assert.True(_context.Response.Headers.ContainsKey(Constants.ContentSecurityPolicyHeaderName));
+
+ var headerStrings = _context.Response.Headers[Constants.ContentSecurityPolicyHeaderName].ToList();
+ Assert.NotNull(headerStrings);
+
+ var actualCsp = headerStrings.First();
+ var actualCharCount = actualCsp.Length;
+ var targetCharCount = targetCsp.Length;
+
+ Assert.True(string.Equals(targetCsp, actualCsp, StringComparison.InvariantCultureIgnoreCase));
+ Assert.Equal(targetCharCount, actualCharCount);
+ }
+
+ ///
+ /// This test exercises and proves regression against unnecessary spaces being added before the first
+ /// URI in a CSP, when no Directives are provided
+ ///
+ [Fact]
+ public async Task ContentSecurityPolicy_Adds_UnnecessarySpace_Before_First_Uri_When_NoDirectivesProvided()
+ {
+ // arrange
+ const string targetCsp = "style-src cdnjs.cloudflare.com;";
+ var headerPresentConfig = SecureHeadersMiddlewareBuilder.CreateBuilder()
+ .UseContentSecurityPolicy(blockAllMixedContent: false, upgradeInsecureRequests: false)
+ .SetCspUris(
+ // originally PRODUCES: style-src cdnjs.cloudflare.com;
+ new List
+ {
+ new() { CommandType = CspCommandType.Uri, DirectiveOrUri = "cdnjs.cloudflare.com" }
+ }, CspUriType.Style).Build();
+ var secureHeadersMiddleware = new SecureHeadersMiddleware(_onNext, headerPresentConfig);
+
+ // act
+ await secureHeadersMiddleware.InvokeAsync(_context);
+
+ // assert
+ Assert.True(_context.Response.Headers.ContainsKey(Constants.ContentSecurityPolicyHeaderName));
+
+ var headerStrings = _context.Response.Headers[Constants.ContentSecurityPolicyHeaderName].ToList();
+ Assert.NotNull(headerStrings);
+
+ var actualCsp = headerStrings.First();
+ var actualCharCount = actualCsp.Length;
+ var targetCharCount = targetCsp.Length;
+
+ Assert.True(string.Equals(targetCsp, actualCsp, StringComparison.InvariantCultureIgnoreCase));
+ Assert.Equal(targetCharCount, actualCharCount);
+ }
+}
+
diff --git a/tests/SecureHeadersInjectedTest.cs b/tests/SecureHeadersInjectedTest.cs
index c8ba9f5..f34d13f 100755
--- a/tests/SecureHeadersInjectedTest.cs
+++ b/tests/SecureHeadersInjectedTest.cs
@@ -456,16 +456,16 @@ public async Task Invoke_CacheControl_HeaderIsPresent()
Assert.True(_context.Response.Headers.ContainsKey(Constants.CacheControlHeaderName));
Assert.Equal(headerPresentConfig.CacheControl.BuildHeaderValue(),
_context.Response.Headers[Constants.CacheControlHeaderName]);
- }
-
+ }
+
[Fact]
public async Task Invoke_CacheControl_HeaderIsNotPresent()
{
// arrange
var headerNotPresentConfig = SecureHeadersMiddlewareBuilder.CreateBuilder();
headerNotPresentConfig.UseCacheControl = false;
- headerNotPresentConfig.Build();
-
+ headerNotPresentConfig.Build();
+
var secureHeadersMiddleware = new SecureHeadersMiddleware(_onNext, headerNotPresentConfig);
// act
diff --git a/tests/StringBuilderExtensionTests.cs b/tests/StringBuilderExtensionTests.cs
new file mode 100644
index 0000000..c770e2d
--- /dev/null
+++ b/tests/StringBuilderExtensionTests.cs
@@ -0,0 +1,34 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Text;
+using OwaspHeaders.Core.Extensions;
+using Xunit;
+
+namespace tests;
+
+[ExcludeFromCodeCoverage]
+public class StringBuilderExtensionTests
+{
+ [Fact]
+ public void TrimEnd_StringBuilder_Is_Null_Return_StringBuilder()
+ {
+ // arrange & act
+ var builder = ((StringBuilder)null).TrimEnd();
+
+ // assert
+ Assert.Null(builder);
+ }
+
+ [Fact]
+ public void TrimEnd_StringBuilder_Is_Empty_Return_StringBuilder()
+ {
+ // arrange
+ StringBuilder builder = new StringBuilder();
+
+ // act
+ builder = builder.TrimEnd();
+
+ // assert
+ Assert.NotNull(builder);
+ Assert.True(builder.Length == 0);
+ }
+}