diff --git a/DNN Platform/DotNetNuke.ContentSecurityPolicy/BaseCspContributor.cs b/DNN Platform/DotNetNuke.ContentSecurityPolicy/BaseCspContributor.cs
new file mode 100644
index 00000000000..44b12f1738c
--- /dev/null
+++ b/DNN Platform/DotNetNuke.ContentSecurityPolicy/BaseCspContributor.cs
@@ -0,0 +1,33 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information
+
+namespace DotNetNuke.ContentSecurityPolicy
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text.RegularExpressions;
+
+ ///
+ /// Base class for all CSP directive contributors.
+ ///
+ public abstract class BaseCspContributor
+ {
+ ///
+ /// Gets unique identifier for the contributor.
+ ///
+ public Guid Id { get; } = Guid.NewGuid();
+
+ ///
+ /// Gets or sets type of the CSP directive.
+ ///
+ public CspDirectiveType DirectiveType { get; protected set; }
+
+ ///
+ /// Generates the directive string.
+ ///
+ /// The directive string.
+ public abstract string GenerateDirective();
+ }
+}
diff --git a/DNN Platform/DotNetNuke.ContentSecurityPolicy/ContentSecurityPolicy.cs b/DNN Platform/DotNetNuke.ContentSecurityPolicy/ContentSecurityPolicy.cs
new file mode 100644
index 00000000000..c0290111388
--- /dev/null
+++ b/DNN Platform/DotNetNuke.ContentSecurityPolicy/ContentSecurityPolicy.cs
@@ -0,0 +1,460 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information
+
+namespace DotNetNuke.ContentSecurityPolicy
+{
+ using System.Collections.Generic;
+ using System.Linq;
+
+ ///
+ /// Manages the entire Content Security Policy.
+ ///
+ public class ContentSecurityPolicy : IContentSecurityPolicy
+ {
+ private string nonce;
+
+ /// Initializes a new instance of the class.
+ public ContentSecurityPolicy()
+ {
+ }
+
+ ///
+ /// Gets a cryptographically secure random nonce value for use in CSP policies.
+ ///
+ public string Nonce
+ {
+ get
+ {
+ if (this.nonce == null)
+ {
+ var nonceBytes = new byte[32];
+ var generator = System.Security.Cryptography.RandomNumberGenerator.Create();
+ generator.GetBytes(nonceBytes);
+ this.nonce = System.Convert.ToBase64String(nonceBytes);
+ }
+
+ return this.nonce;
+ }
+ }
+
+ ///
+ /// Gets the default source contributor for managing default-src directives.
+ ///
+ public SourceCspContributor DefaultSource
+ {
+ get
+ {
+ return this.GetOrCreateDirective(CspDirectiveType.DefaultSrc);
+ }
+ }
+
+ ///
+ /// Gets the script source contributor for managing script-src directives.
+ ///
+ public SourceCspContributor ScriptSource
+ {
+ get
+ {
+ return this.GetOrCreateDirective(CspDirectiveType.ScriptSrc);
+ }
+ }
+
+ ///
+ /// Gets the style source contributor for managing style-src directives.
+ ///
+ public SourceCspContributor StyleSource
+ {
+ get
+ {
+ return this.GetOrCreateDirective(CspDirectiveType.StyleSrc);
+ }
+ }
+
+ ///
+ /// Gets the image source contributor for managing img-src directives.
+ ///
+ public SourceCspContributor ImgSource
+ {
+ get
+ {
+ return this.GetOrCreateDirective(CspDirectiveType.ImgSrc);
+ }
+ }
+
+ ///
+ /// Gets the connect source contributor for managing connect-src directives.
+ ///
+ public SourceCspContributor ConnectSource
+ {
+ get
+ {
+ return this.GetOrCreateDirective(CspDirectiveType.ConnectSrc);
+ }
+ }
+
+ ///
+ /// Gets the frame ancestors contributor for managing frame-ancestors directives.
+ ///
+ public SourceCspContributor FrameAncestors
+ {
+ get
+ {
+ return this.GetOrCreateDirective(CspDirectiveType.FrameAncestors);
+ }
+ }
+
+ ///
+ /// Gets the font source contributor for managing font-src directives.
+ ///
+ public SourceCspContributor FontSource
+ {
+ get
+ {
+ return this.GetOrCreateDirective(CspDirectiveType.FontSrc);
+ }
+ }
+
+ ///
+ /// Gets the object source contributor for managing object-src directives.
+ ///
+ public SourceCspContributor ObjectSource
+ {
+ get
+ {
+ return this.GetOrCreateDirective(CspDirectiveType.ObjectSrc);
+ }
+ }
+
+ ///
+ /// Gets the media source contributor for managing media-src directives.
+ ///
+ public SourceCspContributor MediaSource
+ {
+ get
+ {
+ return this.GetOrCreateDirective(CspDirectiveType.MediaSrc);
+ }
+ }
+
+ ///
+ /// Gets the frame source contributor for managing frame-src directives.
+ ///
+ public SourceCspContributor FrameSource
+ {
+ get
+ {
+ return this.GetOrCreateDirective(CspDirectiveType.FrameSrc);
+ }
+ }
+
+ ///
+ /// Gets the form action source contributor for managing form-action directives.
+ ///
+ public SourceCspContributor FormAction
+ {
+ get
+ {
+ return this.GetOrCreateDirective(CspDirectiveType.FormAction);
+ }
+ }
+
+ ///
+ /// Gets the base URI source contributor for managing base-uri directives.
+ ///
+ public SourceCspContributor BaseUriSource
+ {
+ get
+ {
+ return this.GetOrCreateDirective(CspDirectiveType.BaseUri);
+ }
+ }
+
+ ///
+ /// Gets collection of CSP contributors for content security policy directives.
+ ///
+ private List ContentSecurityPolicyContributors { get; } = new List();
+
+ ///
+ /// Gets collection of CSP contributors for reporting endpoints directives.
+ ///
+ private List ReportingEndpointsContributors { get; } = new List();
+
+ ///
+ /// Parses a CSP header string into a ContentSecurityPolicy object.
+ ///
+ /// The CSP header string to parse.
+ /// A ContentSecurityPolicy object representing the parsed header.
+ /// Thrown when the CSP header is invalid or cannot be parsed.
+ public IContentSecurityPolicy AddHeader(string cspHeader)
+ {
+ var parser = new ContentSecurityPolicyParser(this);
+ parser.Parse(cspHeader);
+ return this;
+ }
+
+ ///
+ /// Adds a reporting directive to the policy.
+ ///
+ /// The reporting directive to add.
+ /// A ContentSecurityPolicy object representing the parsed header.
+ public IContentSecurityPolicy AddReportEndpointHeader(string header)
+ {
+ if (!string.IsNullOrEmpty(header))
+ {
+ var parser = new ContentSecurityPolicyParser(this);
+ parser.ParseReportingEndpoints(header);
+ }
+
+ return this;
+ }
+
+ ///
+ /// Removes script sources of the specified type from the CSP policy.
+ ///
+ /// The CSP source type to remove.
+ public void RemoveScriptSources(CspSourceType cspSourceType)
+ {
+ this.RemoveSources(CspDirectiveType.ScriptSrc, cspSourceType);
+ }
+
+ ///
+ /// Adds allowed plugin types to the CSP policy.
+ ///
+ /// The plugin type to allow.
+ public void AddPluginTypes(string value)
+ {
+ this.AddDocumentDirective(CspDirectiveType.PluginTypes, value);
+ }
+
+ ///
+ /// Adds a sandbox directive to the CSP policy.
+ ///
+ /// The sandbox directive value.
+ public void AddSandbox(string value)
+ {
+ this.SetDocumentDirective(CspDirectiveType.SandboxDirective, value);
+ }
+
+ ///
+ /// Adds a form-action directive to the CSP policy.
+ ///
+ /// The CSP source type to add.
+ /// The value associated with the source.
+ public void AddFormAction(CspSourceType sourceType, string value)
+ {
+ this.AddSource(CspDirectiveType.FormAction, sourceType, value);
+ }
+
+ ///
+ /// Adds a frame-ancestors directive to the CSP policy.
+ ///
+ /// The CSP source type to add.
+ /// The value associated with the source.
+ public void AddFrameAncestors(CspSourceType sourceType, string value)
+ {
+ this.AddSource(CspDirectiveType.FrameAncestors, sourceType, value);
+ }
+
+ ///
+ /// Adds a report URI to the CSP policy.
+ ///
+ /// The name where violation reports will be sent.
+ /// The URI where violation reports will be sent.
+ public void AddReportEndpoint(string name, string value)
+ {
+ // this.AddReportingDirective(CspDirectiveType.ReportUri, value);
+ this.AddReportingEndpointsDirective(name, value);
+ }
+
+ ///
+ /// Adds a report endpoint to the CSP policy.
+ ///
+ /// The endpoint where reports will be sent.
+ public void AddReportTo(string value)
+ {
+ this.AddReportingDirective(CspDirectiveType.ReportTo, value);
+ }
+
+ ///
+ /// Adds the upgrade-insecure-requests directive to upgrade HTTP requests to HTTPS.
+ ///
+ public void UpgradeInsecureRequests()
+ {
+ this.SetDocumentDirective(CspDirectiveType.UpgradeInsecureRequests, string.Empty);
+ }
+
+ ///
+ /// Generates the complete Content Security Policy.
+ ///
+ /// The complete Content Security Policy.
+ public string GeneratePolicy()
+ {
+ return string.Join(
+ "; ",
+ this.ContentSecurityPolicyContributors
+ .Select(c => c.GenerateDirective())
+ .Where(d => !string.IsNullOrEmpty(d)));
+ }
+
+ ///
+ /// Generates the complete security policy.
+ ///
+ /// Reporting Endpoints as a string.
+ public string GenerateReportingEndpoints()
+ {
+ return string.Join(
+ "; ",
+ this.ReportingEndpointsContributors
+ .Select(c => c.GenerateDirective())
+ .Where(d => !string.IsNullOrEmpty(d)));
+ }
+
+ ///
+ /// Gets an existing directive contributor or creates a new one if it doesn't exist.
+ ///
+ /// The type of directive to get or create.
+ /// A SourceCspContributor for the specified directive type.
+ private SourceCspContributor GetOrCreateDirective(CspDirectiveType directiveType)
+ {
+ var directive = this.ContentSecurityPolicyContributors.FirstOrDefault(c => c.DirectiveType == directiveType) as SourceCspContributor;
+ if (directive == null)
+ {
+ directive = new SourceCspContributor(directiveType);
+ this.AddContributor(directive);
+ }
+
+ return directive;
+ }
+
+ ///
+ /// Adds a contributor to the content security policy.
+ ///
+ /// The contributor to add to the policy.
+ private void AddContributor(BaseCspContributor contributor)
+ {
+ // Remove any existing contributor of the same directive type
+ this.ContentSecurityPolicyContributors.RemoveAll(c => c.DirectiveType == contributor.DirectiveType);
+ this.ContentSecurityPolicyContributors.Add(contributor);
+ }
+
+ ///
+ /// Adds a contributor to the reporting endpoints collection.
+ ///
+ /// The contributor to add to the reporting endpoints.
+ private void AddReportingEndpointsContributors(BaseCspContributor contributor)
+ {
+ // Remove any existing contributor of the same directive type
+ this.ReportingEndpointsContributors.RemoveAll(c => c.DirectiveType == contributor.DirectiveType);
+ this.ReportingEndpointsContributors.Add(contributor);
+ }
+
+ ///
+ /// Adds a source to the specified directive type.
+ ///
+ /// The directive type to add the source to.
+ /// The type of source to add.
+ /// The value associated with the source. If null and sourceType is Nonce, uses the generated nonce.
+ private void AddSource(CspDirectiveType directiveType, CspSourceType sourceType, string value = null)
+ {
+ var contributor = this.ContentSecurityPolicyContributors.FirstOrDefault(c => c.DirectiveType == directiveType) as SourceCspContributor;
+ if (contributor == null)
+ {
+ contributor = new SourceCspContributor(directiveType);
+ this.AddContributor(contributor);
+ }
+
+ if (sourceType == CspSourceType.Nonce && string.IsNullOrEmpty(value))
+ {
+ value = this.Nonce;
+ }
+
+ contributor.AddSource(new CspSource(sourceType, value));
+ }
+
+ ///
+ /// Removes sources of the specified type from the directive.
+ ///
+ /// The directive type to remove sources from.
+ /// The type of sources to remove.
+ private void RemoveSources(CspDirectiveType directiveType, CspSourceType sourceType)
+ {
+ var contributor = this.ContentSecurityPolicyContributors.FirstOrDefault(c => c.DirectiveType == directiveType) as SourceCspContributor;
+ if (contributor == null)
+ {
+ contributor = new SourceCspContributor(directiveType);
+ this.AddContributor(contributor);
+ }
+
+ contributor.RemoveSources(sourceType);
+ }
+
+ ///
+ /// Sets a document directive value, replacing any existing value.
+ ///
+ /// The directive type to set.
+ /// The value to set for the directive.
+ private void SetDocumentDirective(CspDirectiveType directiveType, string value)
+ {
+ var contributor = this.ContentSecurityPolicyContributors.FirstOrDefault(c => c.DirectiveType == directiveType) as DocumentCspContributor;
+ if (contributor == null)
+ {
+ contributor = new DocumentCspContributor(directiveType, value);
+ this.AddContributor(contributor);
+ }
+
+ contributor.SetDirectiveValue(value);
+ }
+
+ ///
+ /// Adds a document directive with the specified value.
+ ///
+ /// The directive type to add.
+ /// The value for the directive.
+ private void AddDocumentDirective(CspDirectiveType directiveType, string value)
+ {
+ var contributor = this.ContentSecurityPolicyContributors.FirstOrDefault(c => c.DirectiveType == directiveType) as DocumentCspContributor;
+ if (contributor == null)
+ {
+ contributor = new DocumentCspContributor(directiveType, value);
+ this.AddContributor(contributor);
+ }
+
+ contributor.SetDirectiveValue(value);
+ }
+
+ ///
+ /// Adds a reporting directive with the specified value.
+ ///
+ /// The directive type to add.
+ /// The value for the reporting directive.
+ private void AddReportingDirective(CspDirectiveType directiveType, string value)
+ {
+ var contributor = this.ContentSecurityPolicyContributors.FirstOrDefault(c => c.DirectiveType == directiveType) as ReportingCspContributor;
+ if (contributor == null)
+ {
+ contributor = new ReportingCspContributor(directiveType);
+ this.AddContributor(contributor);
+ }
+
+ contributor.AddReportingEndpoint(value);
+ }
+
+ ///
+ /// Adds a reporting endpoints directive with the specified name and value.
+ ///
+ /// The name of the reporting endpoint.
+ /// The URI value for the reporting endpoint.
+ private void AddReportingEndpointsDirective(string name, string value)
+ {
+ var contributor = this.ReportingEndpointsContributors.FirstOrDefault(c => c.DirectiveType == CspDirectiveType.ReportUri) as ReportingEndpointContributor;
+ if (contributor == null)
+ {
+ contributor = new ReportingEndpointContributor(CspDirectiveType.ReportUri);
+ this.AddReportingEndpointsContributors(contributor);
+ }
+
+ contributor.AddReportingEndpoint(name, value);
+ }
+ }
+}
diff --git a/DNN Platform/DotNetNuke.ContentSecurityPolicy/ContentSecurityPolicyParser.cs b/DNN Platform/DotNetNuke.ContentSecurityPolicy/ContentSecurityPolicyParser.cs
new file mode 100644
index 00000000000..50ca8a2a482
--- /dev/null
+++ b/DNN Platform/DotNetNuke.ContentSecurityPolicy/ContentSecurityPolicyParser.cs
@@ -0,0 +1,302 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information
+
+namespace DotNetNuke.ContentSecurityPolicy
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text.RegularExpressions;
+
+ ///
+ /// Utility class for parsing Content Security Policy headers into ContentSecurityPolicy objects.
+ ///
+ public class ContentSecurityPolicyParser
+ {
+ private readonly IContentSecurityPolicy policy;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The ContentSecurityPolicy instance to populate with parsed directives.
+ public ContentSecurityPolicyParser(IContentSecurityPolicy policy)
+ {
+ this.policy = policy ?? throw new ArgumentNullException(nameof(policy));
+ }
+
+ ///
+ /// Parses a CSP header string into the provided ContentSecurityPolicy object.
+ ///
+ /// The CSP header string to parse.
+ /// Thrown when the CSP header is invalid or cannot be parsed.
+ public void Parse(string cspHeader)
+ {
+ if (string.IsNullOrWhiteSpace(cspHeader))
+ {
+ throw new ArgumentException("CSP header cannot be null or empty", nameof(cspHeader));
+ }
+
+ // Split the header into individual directives
+ var directives = SplitDirectives(cspHeader);
+
+ foreach (var directive in directives)
+ {
+ this.ParseDirective(directive);
+ }
+ }
+
+ ///
+ /// Tries to parse a CSP header string into the provided ContentSecurityPolicy object.
+ ///
+ /// The CSP header string to parse.
+ /// True if parsing was successful, false otherwise.
+ public bool TryParse(string cspHeader)
+ {
+ try
+ {
+ this.Parse(cspHeader);
+ return true;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+ ///
+ /// Parses a reporting endpoints header string into the provided ContentSecurityPolicy object.
+ ///
+ /// The CSP header string to parse.
+ public void ParseReportingEndpoints(string cspHeader)
+ {
+ if (string.IsNullOrWhiteSpace(cspHeader))
+ {
+ return;
+ }
+
+ // Split the header into individual directives
+ var directives = cspHeader.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
+ .Select(d => d.Trim())
+ .Where(d => !string.IsNullOrEmpty(d));
+
+ foreach (var directive in directives)
+ {
+ this.ParseReportingEndpointsDirective(directive);
+ }
+ }
+
+ ///
+ /// Splits the CSP header into individual directive strings.
+ ///
+ private static IEnumerable SplitDirectives(string cspHeader)
+ {
+ // CSP directives are separated by semicolons
+ return cspHeader.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)
+ .Select(d => d.Trim())
+ .Where(d => !string.IsNullOrEmpty(d));
+ }
+
+ ///
+ /// Parses a source string and adds it to the contributor.
+ ///
+ private static void ParseAndAddSource(SourceCspContributor contributor, string source)
+ {
+ var trimmedSource = source.Trim();
+
+ // Check for quoted keywords first
+ if (CspSourceTypeNameMapper.IsQuotedKeyword(trimmedSource))
+ {
+ if (CspSourceTypeNameMapper.TryGetSourceType(trimmedSource, out var sourceType))
+ {
+ switch (sourceType)
+ {
+ case CspSourceType.Self:
+ contributor.AddSelf();
+ break;
+
+ case CspSourceType.Inline:
+ contributor.AddInline();
+ break;
+
+ case CspSourceType.Eval:
+ contributor.AddEval();
+ break;
+
+ case CspSourceType.None:
+ contributor.AddNone();
+ break;
+
+ case CspSourceType.StrictDynamic:
+ contributor.AddStrictDynamic();
+ break;
+ }
+ }
+ else if (CspSourceTypeNameMapper.IsNonceSource(trimmedSource))
+ {
+ var quotedValue = trimmedSource.Substring(1, trimmedSource.Length - 2);
+ var nonce = quotedValue.Substring(6); // Remove "nonce-" prefix
+ contributor.AddNonce(nonce);
+ }
+ else if (CspSourceTypeNameMapper.IsHashSource(trimmedSource))
+ {
+ var quotedValue = trimmedSource.Substring(1, trimmedSource.Length - 2);
+ contributor.AddHash(quotedValue);
+ }
+ }
+ else if (trimmedSource.Contains(":"))
+ {
+ // Check if it's a scheme
+ if (IsScheme(trimmedSource))
+ {
+ contributor.AddScheme(trimmedSource);
+ }
+ else
+ {
+ // Treat as host
+ contributor.AddHost(trimmedSource);
+ }
+ }
+ else
+ {
+ // Treat as host (domain without protocol)
+ contributor.AddHost(trimmedSource);
+ }
+ }
+
+ ///
+ /// Checks if a string represents a scheme.
+ ///
+ private static bool IsScheme(string source)
+ {
+ string[] knownSchemes = { "http:", "https:", "data:", "blob:", "filesystem:", "wss:", "ws:" };
+ return knownSchemes.Contains(source.ToLowerInvariant());
+ }
+
+ ///
+ /// Parses a single directive and applies it to the policy.
+ ///
+ private void ParseDirective(string directive)
+ {
+ var parts = directive.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
+ if (parts.Length == 0)
+ {
+ return;
+ }
+
+ var directiveName = parts[0].ToLowerInvariant();
+ var sources = parts.Skip(1).ToArray();
+
+ // Try to get the directive type from the name
+ if (!CspDirectiveNameMapper.TryGetDirectiveType(directiveName, out var directiveType))
+ {
+ // Unknown directive - ignore for now
+ throw new ($"Unknown directive: {directiveName}");
+ }
+
+ this.ApplyDirectiveToPolicy(directiveType, sources);
+ }
+
+ ///
+ /// Parses a reporting endpoints directive string into the provided ContentSecurityPolicy object.
+ ///
+ /// The reporting endpoints directive string to parse.
+ private void ParseReportingEndpointsDirective(string directive)
+ {
+ if (string.IsNullOrWhiteSpace(directive))
+ {
+ return;
+ }
+
+ // Split directive into name=value pairs
+ var parts = directive.Split(new[] { '=' }, StringSplitOptions.RemoveEmptyEntries);
+ if (parts.Length != 2)
+ {
+ throw new ArgumentException("Invalid reporting endpoint format. Expected format: name=\"value\"");
+ }
+
+ var name = parts[0].Trim();
+ var value = parts[1].Trim().Trim('"');
+
+ // Add the reporting endpoint to the policy
+ this.policy.AddReportEndpoint(name, value);
+ }
+
+ ///
+ /// Applies a parsed directive to the policy object.
+ ///
+ private void ApplyDirectiveToPolicy(CspDirectiveType directiveType, string[] sources)
+ {
+ switch (directiveType)
+ {
+ case CspDirectiveType.SandboxDirective:
+ this.policy.AddSandbox(string.Join(" ", sources));
+ break;
+
+ case CspDirectiveType.PluginTypes:
+ foreach (var source in sources)
+ {
+ this.policy.AddPluginTypes(source);
+ }
+
+ break;
+
+ case CspDirectiveType.UpgradeInsecureRequests:
+ this.policy.UpgradeInsecureRequests();
+ break;
+
+ case CspDirectiveType.ReportUri:
+ foreach (var source in sources)
+ {
+ this.policy.AddReportEndpoint("default", source);
+ }
+
+ break;
+
+ case CspDirectiveType.ReportTo:
+ foreach (var source in sources)
+ {
+ this.policy.AddReportTo(source);
+ }
+
+ break;
+
+ // Source-based directives
+ default:
+ var contributor = this.GetSourceContributor(directiveType);
+ if (contributor != null)
+ {
+ foreach (var source in sources)
+ {
+ ParseAndAddSource(contributor, source);
+ }
+ }
+
+ break;
+ }
+ }
+
+ ///
+ /// Gets the appropriate source contributor for a directive type.
+ ///
+ private SourceCspContributor GetSourceContributor(CspDirectiveType directiveType)
+ {
+ return directiveType switch
+ {
+ CspDirectiveType.DefaultSrc => this.policy.DefaultSource,
+ CspDirectiveType.ScriptSrc => this.policy.ScriptSource,
+ CspDirectiveType.StyleSrc => this.policy.StyleSource,
+ CspDirectiveType.ImgSrc => this.policy.ImgSource,
+ CspDirectiveType.ConnectSrc => this.policy.ConnectSource,
+ CspDirectiveType.FontSrc => this.policy.FontSource,
+ CspDirectiveType.ObjectSrc => this.policy.ObjectSource,
+ CspDirectiveType.MediaSrc => this.policy.MediaSource,
+ CspDirectiveType.FrameSrc => this.policy.FrameSource,
+ CspDirectiveType.FrameAncestors => this.policy.FrameAncestors,
+ CspDirectiveType.FormAction => this.policy.FormAction,
+ CspDirectiveType.BaseUri => this.policy.BaseUriSource,
+ _ => null
+ };
+ }
+ }
+}
diff --git a/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspContributor.cs b/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspContributor.cs
new file mode 100644
index 00000000000..27279e58df3
--- /dev/null
+++ b/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspContributor.cs
@@ -0,0 +1,81 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information
+
+namespace DotNetNuke.ContentSecurityPolicy
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text.RegularExpressions;
+
+ ///
+ /// Manages Content Security Policy contributors for a specific directive.
+ ///
+ public class CspContributor
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The directive to create the contributor for.
+ public CspContributor(string directive)
+ {
+ this.Directive = directive ?? throw new ArgumentNullException(nameof(directive));
+ }
+
+ ///
+ /// Gets name of the directive (e.g., 'script-src', 'style-src').
+ ///
+ public string Directive { get; }
+
+ ///
+ /// Gets collection of sources for this directive.
+ ///
+ private List Sources { get; } = new List();
+
+ ///
+ /// Adds a source to the directive.
+ ///
+ /// The source to add.
+ public void AddSource(CspSource source)
+ {
+ if (!this.Sources.Any(s => s.Type == source.Type && s.Value == source.Value))
+ {
+ this.Sources.Add(source);
+ }
+ }
+
+ ///
+ /// Removes a source from the directive.
+ ///
+ /// The source to remove.
+ public void RemoveSource(CspSource source)
+ {
+ this.Sources.RemoveAll(s => s.Type == source.Type && s.Value == source.Value);
+ }
+
+ ///
+ /// Generates the complete directive string.
+ ///
+ /// The directive string.
+ public string GenerateDirective()
+ {
+ if (!this.Sources.Any())
+ {
+ return string.Empty;
+ }
+
+ return $"{this.Directive} {string.Join(" ", this.Sources.Select(s => s.ToString()))}";
+ }
+
+ ///
+ /// Gets all sources of a specific type.
+ ///
+ /// The type of sources to get.
+ /// The sources of the specified type.
+ public IEnumerable GetSourcesByType(CspSourceType type)
+ {
+ return this.Sources.Where(s => s.Type == type);
+ }
+ }
+}
diff --git a/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspDirectiveNameMapper.cs b/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspDirectiveNameMapper.cs
new file mode 100644
index 00000000000..2f45a6be448
--- /dev/null
+++ b/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspDirectiveNameMapper.cs
@@ -0,0 +1,101 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information
+
+namespace DotNetNuke.ContentSecurityPolicy
+{
+ using System;
+
+ ///
+ /// Utility class for converting directive types to their string representations.
+ ///
+ public static class CspDirectiveNameMapper
+ {
+ ///
+ /// Gets the directive name string.
+ ///
+ /// The directive type to get the name for.
+ /// The directive name string.
+ public static string GetDirectiveName(CspDirectiveType directiveType)
+ {
+ return directiveType switch
+ {
+ CspDirectiveType.DefaultSrc => "default-src",
+ CspDirectiveType.ScriptSrc => "script-src",
+ CspDirectiveType.StyleSrc => "style-src",
+ CspDirectiveType.ImgSrc => "img-src",
+ CspDirectiveType.ConnectSrc => "connect-src",
+ CspDirectiveType.FontSrc => "font-src",
+ CspDirectiveType.ObjectSrc => "object-src",
+ CspDirectiveType.MediaSrc => "media-src",
+ CspDirectiveType.FrameSrc => "frame-src",
+ CspDirectiveType.BaseUri => "base-uri",
+ CspDirectiveType.PluginTypes => "plugin-types",
+ CspDirectiveType.SandboxDirective => "sandbox",
+ CspDirectiveType.FormAction => "form-action",
+ CspDirectiveType.FrameAncestors => "frame-ancestors",
+ CspDirectiveType.ReportUri => "report-uri",
+ CspDirectiveType.ReportTo => "report-to",
+ CspDirectiveType.UpgradeInsecureRequests => "upgrade-insecure-requests",
+ _ => throw new ArgumentException("Unknown directive type")
+ };
+ }
+
+ ///
+ /// Gets the directive type from a directive name string.
+ ///
+ /// The directive name to get the type for.
+ /// The directive type.
+ /// Thrown when the directive name is unknown.
+ public static CspDirectiveType GetDirectiveType(string directiveName)
+ {
+ if (string.IsNullOrWhiteSpace(directiveName))
+ {
+ throw new ArgumentException("Directive name cannot be null or empty", nameof(directiveName));
+ }
+
+ return directiveName.ToLowerInvariant() switch
+ {
+ "default-src" => CspDirectiveType.DefaultSrc,
+ "script-src" => CspDirectiveType.ScriptSrc,
+ "style-src" => CspDirectiveType.StyleSrc,
+ "img-src" => CspDirectiveType.ImgSrc,
+ "connect-src" => CspDirectiveType.ConnectSrc,
+ "font-src" => CspDirectiveType.FontSrc,
+ "object-src" => CspDirectiveType.ObjectSrc,
+ "media-src" => CspDirectiveType.MediaSrc,
+ "frame-src" => CspDirectiveType.FrameSrc,
+ "base-uri" => CspDirectiveType.BaseUri,
+ "plugin-types" => CspDirectiveType.PluginTypes,
+ "sandbox" => CspDirectiveType.SandboxDirective,
+ "form-action" => CspDirectiveType.FormAction,
+ "frame-ancestors" => CspDirectiveType.FrameAncestors,
+ "report-uri" => CspDirectiveType.ReportUri,
+ "report-to" => CspDirectiveType.ReportTo,
+ "upgrade-insecure-requests" => CspDirectiveType.UpgradeInsecureRequests,
+ _ => throw new ArgumentException($"Unknown directive name: {directiveName}")
+ };
+ }
+
+ ///
+ /// Tries to get the directive type from a directive name string.
+ ///
+ /// The directive name to get the type for.
+ /// The directive type, or default if parsing failed.
+ /// True if parsing was successful, false otherwise.
+ public static bool TryGetDirectiveType(string directiveName, out CspDirectiveType directiveType)
+ {
+ directiveType = default;
+
+ try
+ {
+ directiveType = GetDirectiveType(directiveName);
+ return true;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+ }
+}
diff --git a/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspDirectiveType.cs b/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspDirectiveType.cs
new file mode 100644
index 00000000000..889db63ea4e
--- /dev/null
+++ b/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspDirectiveType.cs
@@ -0,0 +1,97 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information
+
+namespace DotNetNuke.ContentSecurityPolicy
+{
+ ///
+ /// Represents different types of Content Security Policy directives.
+ ///
+ public enum CspDirectiveType
+ {
+ ///
+ /// Directive qui définit la politique par défaut pour les types de ressources non spécifiés.
+ ///
+ DefaultSrc,
+
+ ///
+ /// Directive qui contrôle les sources de scripts autorisées.
+ ///
+ ScriptSrc,
+
+ ///
+ /// Directive qui contrôle les sources de styles autorisées.
+ ///
+ StyleSrc,
+
+ ///
+ /// Directive qui contrôle les sources d'images autorisées.
+ ///
+ ImgSrc,
+
+ ///
+ /// Directive qui contrôle les destinations de connexion autorisées.
+ ///
+ ConnectSrc,
+
+ ///
+ /// Directive qui contrôle les sources de polices autorisées.
+ ///
+ FontSrc,
+
+ ///
+ /// Directive qui contrôle les sources d'objets autorisées.
+ ///
+ ObjectSrc,
+
+ ///
+ /// Directive qui contrôle les sources de médias autorisées.
+ ///
+ MediaSrc,
+
+ ///
+ /// Directive qui contrôle les sources de frames autorisées.
+ ///
+ FrameSrc,
+
+ ///
+ /// Directive qui restreint les URLs pouvant être utilisées dans la base URI du document.
+ ///
+ BaseUri,
+
+ ///
+ /// Directive qui restreint les types de plugins pouvant être chargés.
+ ///
+ PluginTypes,
+
+ ///
+ /// Directive qui active un bac à sable pour la ressource demandée.
+ ///
+ SandboxDirective,
+
+ ///
+ /// Directive qui restreint les URLs pouvant être utilisées comme cible de formulaire.
+ ///
+ FormAction,
+
+ ///
+ /// Directive qui spécifie les parents autorisés à intégrer une page dans un frame.
+ ///
+ FrameAncestors,
+
+ ///
+ /// Directive qui spécifie l'URI où envoyer les rapports de violation.
+ ///
+ ReportUri,
+
+ ///
+ /// Directive qui spécifie où envoyer les rapports de violation au format JSON.
+ ///
+ ReportTo,
+
+ ///
+ /// Directive qui spécifie UpgradeInsecureRequests.
+ ///
+ UpgradeInsecureRequests,
+}
+}
diff --git a/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspParsingExample.cs b/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspParsingExample.cs
new file mode 100644
index 00000000000..b10d2311eb4
--- /dev/null
+++ b/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspParsingExample.cs
@@ -0,0 +1,104 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information
+
+namespace DotNetNuke.ContentSecurityPolicy
+{
+ using System;
+
+ ///
+ /// Example class demonstrating how to parse Content Security Policy headers.
+ ///
+ public static class CspParsingExample
+ {
+ ///
+ /// Demonstrates how to parse a CSP header string.
+ ///
+ public static void ParseExample()
+ {
+ // Example CSP header string
+ var cspHeader = "default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.example.com 'nonce-abc123'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self'; font-src 'self' https://fonts.googleapis.com; frame-ancestors 'none'; report-uri /csp-report";
+ var policy = new ContentSecurityPolicy();
+ try
+ {
+ // Parse the CSP header
+ policy.AddHeader(cspHeader);
+
+ // Access parsed directives
+ Console.WriteLine("Parsed CSP Policy:");
+ Console.WriteLine($"Generated Policy: {policy.GeneratePolicy()}");
+ Console.WriteLine($"Nonce: {policy.Nonce}");
+
+ // You can now modify the parsed policy
+ policy.ScriptSource.AddHost("newcdn.example.com");
+ policy.StyleSource.AddHash("sha256-abc123def456");
+
+ Console.WriteLine($"Modified Policy: {policy.GeneratePolicy()}");
+ }
+ catch (ArgumentException ex)
+ {
+ Console.WriteLine($"Failed to parse CSP header: {ex.Message}");
+ }
+
+ // Example using TryParse
+ var invalidCspHeader = "invalid-directive something";
+ try
+ {
+ policy.AddHeader(invalidCspHeader);
+ Console.WriteLine("Successfully parsed invalid header");
+ }
+ catch (Exception)
+ {
+ Console.WriteLine("Failed to parse invalid header (as expected)");
+ }
+ }
+
+ ///
+ /// Demonstrates various CSP header formats that can be parsed.
+ ///
+ public static void ParseVariousFormats()
+ {
+ var examples = new[]
+ {
+ // Basic policy
+ "default-src 'self'",
+
+ // Policy with multiple sources
+ "script-src 'self' 'unsafe-inline' https://cdn.example.com",
+
+ // Policy with nonce
+ "script-src 'self' 'nonce-abc123def456'",
+
+ // Policy with hash
+ "style-src 'self' 'sha256-abc123def456789'",
+
+ // Complex policy
+ "default-src 'self'; script-src 'self' 'strict-dynamic'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; connect-src 'self' wss:; font-src 'self' https://fonts.googleapis.com; frame-ancestors 'none'; upgrade-insecure-requests; report-uri /csp-report",
+
+ // Policy with sandbox
+ "sandbox allow-forms allow-scripts; script-src 'self'",
+
+ // Policy with form-action
+ "form-action 'self' https://secure.example.com",
+
+ // Policy with report-uri
+ "default-src 'self'; img-src 'self' https://front.satrabel.be https://www.googletagmanager.com https://region1.google-analytics.com; font-src 'self' https://fonts.gstatic.com; style-src 'self' https://fonts.googleapis.com https://www.googletagmanager.com; frame-ancestors 'self'; frame-src 'self'; form-action 'self'; object-src 'none'; base-uri 'self'; script-src 'nonce-hq9CE6VltPZiiySID0F9914GvPObOnIAN3Qs/0R+AmQ=' 'strict-dynamic'; report-to csp-endpoint; report-uri https://dnncore.satrabel.be/DesktopModules/Csp/Report; connect-src https://www.googletagmanager.com https://region1.google-analytics.com https://www.google-analytics.com; upgrade-insecure-requests",
+ };
+
+ foreach (var example in examples)
+ {
+ Console.WriteLine($"\nParsing: {example}");
+ var policy = new ContentSecurityPolicy();
+ try
+ {
+ policy.AddHeader(example);
+ Console.WriteLine($"Success: {policy.GeneratePolicy()}");
+ }
+ catch (Exception)
+ {
+ Console.WriteLine("Failed to parse");
+ }
+ }
+ }
+ }
+}
diff --git a/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspPolicyExample.cs b/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspPolicyExample.cs
new file mode 100644
index 00000000000..c82f8bdde82
--- /dev/null
+++ b/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspPolicyExample.cs
@@ -0,0 +1,39 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information
+
+namespace DotNetNuke.ContentSecurityPolicy
+{
+ using System;
+
+ ///
+ /// Démontre l'utilisation de la Content Security Policy en configurant différentes directives.
+ ///
+ public class CspPolicyExample
+ {
+ ///
+ /// Démontre l'utilisation de la Content Security Policy en configurant différentes directives.
+ ///
+ public static void Example()
+ {
+ // Create a Content Security Policy
+ var csp = new ContentSecurityPolicy();
+
+ // Add a source-based contributor for script sources
+ csp.ScriptSource
+ .AddSelf()
+ .AddHost("https://trusted-cdn.com");
+
+ // Add a document-based contributor for sandbox
+ csp.AddSandbox("allow-scripts allow-same-origin");
+
+ // Add a reporting contributor
+ csp.AddReportEndpoint("name", "https://example.com/csp-report");
+
+ // Generate the complete policy
+ string policy = csp.GeneratePolicy();
+
+ Console.WriteLine(policy);
+ }
+ }
+}
diff --git a/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspSource.cs b/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspSource.cs
new file mode 100644
index 00000000000..93b04cf7565
--- /dev/null
+++ b/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspSource.cs
@@ -0,0 +1,161 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information
+
+namespace DotNetNuke.ContentSecurityPolicy
+{
+ using System;
+ using System.Linq;
+ using System.Text.RegularExpressions;
+
+ ///
+ /// Represents a single source in a Content Security Policy.
+ ///
+ public class CspSource
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Type of the source.
+ /// Value of the source.
+ public CspSource(CspSourceType type, string value = null)
+ {
+ this.Type = type;
+ this.Value = this.ValidateSource(type, value);
+ }
+
+ ///
+ /// Gets type of the CSP source.
+ ///
+ public CspSourceType Type { get; }
+
+ ///
+ /// Gets the actual source value.
+ ///
+ public string Value { get; }
+
+ ///
+ /// Returns the string representation of the source.
+ ///
+ /// The string representation of the source.
+ public override string ToString() => this.Value ?? CspSourceTypeNameMapper.GetSourceTypeName(this.Type);
+
+ ///
+ /// Validates the source based on its type.
+ ///
+ private string ValidateSource(CspSourceType type, string value)
+ {
+ switch (type)
+ {
+ case CspSourceType.Host:
+ return this.ValidateHostSource(value);
+ case CspSourceType.Scheme:
+ return this.ValidateSchemeSource(value);
+ case CspSourceType.Nonce:
+ return this.ValidateNonceSource(value);
+ case CspSourceType.Hash:
+ return this.ValidateHashSource(value);
+ case CspSourceType.Self:
+ return "'self'";
+ case CspSourceType.Inline:
+ case CspSourceType.Eval:
+ return "'unsafe-" + type.ToString().ToLowerInvariant() + "'";
+ case CspSourceType.None:
+ return "'none'";
+ case CspSourceType.StrictDynamic:
+ return "'strict-dynamic'";
+ default:
+ throw new ArgumentException("Invalid source type");
+ }
+ }
+
+ ///
+ /// Validates host source (domain or IP).
+ ///
+ private string ValidateHostSource(string value)
+ {
+ if (string.IsNullOrWhiteSpace(value))
+ {
+ throw new ArgumentException("Host source cannot be empty");
+ }
+
+ // Basic domain validation
+ var domainRegex = new Regex(@"^(https?://)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}(:\d+)?(/.*)?$");
+ if (!domainRegex.IsMatch(value))
+ {
+ throw new ArgumentException($"Invalid host source: {value}");
+ }
+
+ return value.StartsWith("http") ? value : $"https://{value}";
+ }
+
+ ///
+ /// Validates scheme source (protocol).
+ ///
+ private string ValidateSchemeSource(string value)
+ {
+ string[] validSchemes = { "http:", "https:", "data:", "blob:", "filesystem:", "wss:", "ws:" };
+ if (!validSchemes.Contains(value))
+ {
+ throw new ArgumentException($"Invalid scheme: {value}");
+ }
+
+ return value;
+ }
+
+ ///
+ /// Validates nonce source.
+ ///
+ private string ValidateNonceSource(string value)
+ {
+ if (string.IsNullOrWhiteSpace(value))
+ {
+ throw new ArgumentException("Nonce cannot be empty");
+ }
+
+ // Basic nonce validation - allow any non-empty string for flexibility
+ // In real-world scenarios, nonces might not always be strict base64
+ return $"'nonce-{value}'";
+ }
+
+ ///
+ /// Validates hash source.
+ ///
+ private string ValidateHashSource(string value)
+ {
+ string[] hashPrefixes = { "sha256-", "sha384-", "sha512-" };
+
+ if (string.IsNullOrWhiteSpace(value))
+ {
+ throw new ArgumentException("Hash cannot be empty");
+ }
+
+ // Check if the value starts with a valid hash prefix
+ // Allow any string after the prefix for flexibility in parsing scenarios
+ bool hasValidPrefix = hashPrefixes.Any(prefix => value.StartsWith(prefix));
+
+ if (!hasValidPrefix)
+ {
+ throw new ArgumentException($"Invalid hash format: {value}");
+ }
+
+ return $"'{value}'";
+ }
+
+ ///
+ /// Checks if a string is a valid Base64 string.
+ ///
+ private bool IsBase64String(string value)
+ {
+ try
+ {
+ Convert.FromBase64String(value);
+ return true;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+ }
+}
diff --git a/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspSourceType.cs b/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspSourceType.cs
new file mode 100644
index 00000000000..496ab88dbb0
--- /dev/null
+++ b/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspSourceType.cs
@@ -0,0 +1,57 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information
+
+namespace DotNetNuke.ContentSecurityPolicy
+{
+ ///
+ /// Represents different types of Content Security Policy source types.
+ ///
+ public enum CspSourceType
+ {
+ ///
+ /// Permet de spécifier des domaines spécifiques comme source.
+ ///
+ Host,
+
+ ///
+ /// Permet de spécifier des protocoles (ex: https:, data:) comme source.
+ ///
+ Scheme,
+
+ ///
+ /// Autorise les ressources de la même origine ('self').
+ ///
+ Self,
+
+ ///
+ /// Autorise l'utilisation de code inline ('unsafe-inline').
+ ///
+ Inline,
+
+ ///
+ /// Autorise l'utilisation de eval() ('unsafe-eval').
+ ///
+ Eval,
+
+ ///
+ /// Utilise un nonce cryptographique pour valider les ressources.
+ ///
+ Nonce,
+
+ ///
+ /// Utilise un hash cryptographique pour valider les ressources.
+ ///
+ Hash,
+
+ ///
+ /// N'autorise aucune source ('none').
+ ///
+ None,
+
+ ///
+ /// Active le mode strict-dynamic pour le chargement des scripts.
+ ///
+ StrictDynamic,
+ }
+}
diff --git a/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspSourceTypeNameMapper.cs b/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspSourceTypeNameMapper.cs
new file mode 100644
index 00000000000..60c8f655192
--- /dev/null
+++ b/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspSourceTypeNameMapper.cs
@@ -0,0 +1,127 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information
+
+namespace DotNetNuke.ContentSecurityPolicy
+{
+ using System;
+
+ ///
+ /// Utility class for converting source types to their string representations.
+ ///
+ public static class CspSourceTypeNameMapper
+ {
+ ///
+ /// Gets the source type name string.
+ ///
+ /// The source type to get the name for.
+ /// The source type name string.
+ public static string GetSourceTypeName(CspSourceType sourceType)
+ {
+ return sourceType switch
+ {
+ CspSourceType.Host => "host",
+ CspSourceType.Scheme => "scheme",
+ CspSourceType.Self => "'self'",
+ CspSourceType.Inline => "'unsafe-inline'",
+ CspSourceType.Eval => "'unsafe-eval'",
+ CspSourceType.Nonce => "nonce",
+ CspSourceType.Hash => "hash",
+ CspSourceType.None => "'none'",
+ CspSourceType.StrictDynamic => "'strict-dynamic'",
+ _ => throw new ArgumentException("Unknown source type")
+ };
+ }
+
+ ///
+ /// Gets the source type from a source name string.
+ ///
+ /// The source name to get the type for.
+ /// The source type.
+ /// Thrown when the source name is unknown.
+ public static CspSourceType GetSourceType(string sourceName)
+ {
+ if (string.IsNullOrWhiteSpace(sourceName))
+ {
+ throw new ArgumentException("Source name cannot be null or empty", nameof(sourceName));
+ }
+
+ return sourceName.ToLowerInvariant() switch
+ {
+ "'self'" => CspSourceType.Self,
+ "'unsafe-inline'" => CspSourceType.Inline,
+ "'unsafe-eval'" => CspSourceType.Eval,
+ "'none'" => CspSourceType.None,
+ "'strict-dynamic'" => CspSourceType.StrictDynamic,
+ _ => throw new ArgumentException($"Unknown source name: {sourceName}")
+ };
+ }
+
+ ///
+ /// Tries to get the source type from a source name string.
+ ///
+ /// The source name to get the type for.
+ /// The source type, or default if parsing failed.
+ /// True if parsing was successful, false otherwise.
+ public static bool TryGetSourceType(string sourceName, out CspSourceType sourceType)
+ {
+ sourceType = default;
+
+ try
+ {
+ sourceType = GetSourceType(sourceName);
+ return true;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+ ///
+ /// Checks if a source string represents a quoted keyword.
+ ///
+ /// The source string to check.
+ /// True if the source is a quoted keyword, false otherwise.
+ public static bool IsQuotedKeyword(string source)
+ {
+ if (string.IsNullOrWhiteSpace(source))
+ {
+ return false;
+ }
+
+ return source.StartsWith("'") && source.EndsWith("'");
+ }
+
+ ///
+ /// Checks if a source string represents a nonce value.
+ ///
+ /// The source string to check.
+ /// True if the source is a nonce value, false otherwise.
+ public static bool IsNonceSource(string source)
+ {
+ if (string.IsNullOrWhiteSpace(source))
+ {
+ return false;
+ }
+
+ return source.StartsWith("'nonce-") && source.EndsWith("'");
+ }
+
+ ///
+ /// Checks if a source string represents a hash value.
+ ///
+ /// The source string to check.
+ /// True if the source is a hash value, false otherwise.
+ public static bool IsHashSource(string source)
+ {
+ if (string.IsNullOrWhiteSpace(source))
+ {
+ return false;
+ }
+
+ return source.StartsWith("'") && source.EndsWith("'") &&
+ (source.Contains("sha256-") || source.Contains("sha384-") || source.Contains("sha512-"));
+ }
+ }
+}
diff --git a/DNN Platform/DotNetNuke.ContentSecurityPolicy/DocumentCspContributor.cs b/DNN Platform/DotNetNuke.ContentSecurityPolicy/DocumentCspContributor.cs
new file mode 100644
index 00000000000..3457dfe3ce9
--- /dev/null
+++ b/DNN Platform/DotNetNuke.ContentSecurityPolicy/DocumentCspContributor.cs
@@ -0,0 +1,114 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information
+
+namespace DotNetNuke.ContentSecurityPolicy
+{
+ using System;
+ using System.Linq;
+
+ ///
+ /// Contributor for document-level directives.
+ ///
+ public class DocumentCspContributor : BaseCspContributor
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The directive type to create the contributor for.
+ /// The value of the directive.
+ public DocumentCspContributor(CspDirectiveType directiveType, string value)
+ {
+ this.DirectiveType = directiveType;
+ this.SetDirectiveValue(value);
+ }
+
+ ///
+ /// Gets value of the document directive.
+ ///
+ public string DirectiveValue { get; private set; }
+
+ ///
+ /// Sets the directive value with validation.
+ ///
+ /// The value to set for the directive.
+ public void SetDirectiveValue(string value)
+ {
+ this.ValidateDirectiveValue(this.DirectiveType, value);
+ this.DirectiveValue = value;
+ }
+
+ ///
+ /// Generates the directive string.
+ ///
+ /// The directive string.
+ public override string GenerateDirective()
+ {
+ if (this.DirectiveType == CspDirectiveType.UpgradeInsecureRequests)
+ {
+ return $"{CspDirectiveNameMapper.GetDirectiveName(this.DirectiveType)}";
+ }
+
+ if (string.IsNullOrWhiteSpace(this.DirectiveValue))
+ {
+ return string.Empty;
+ }
+
+ return $"{CspDirectiveNameMapper.GetDirectiveName(this.DirectiveType)} {this.DirectiveValue}";
+ }
+
+ ///
+ /// Validates directive value based on directive type.
+ ///
+ private void ValidateDirectiveValue(CspDirectiveType type, string value)
+ {
+ switch (type)
+ {
+ case CspDirectiveType.PluginTypes:
+ this.ValidatePluginTypes(value);
+ break;
+ case CspDirectiveType.SandboxDirective:
+ this.ValidateSandboxDirective(value);
+ break;
+
+ // Add more specific validations as needed
+ }
+ }
+
+ ///
+ /// Validates plugin types.
+ ///
+ private void ValidatePluginTypes(string value)
+ {
+ string[] validPluginTypes = { "application/pdf", "image/svg+xml" };
+ var types = value.Split(' ');
+
+ if (types.Any(t => !validPluginTypes.Contains(t)))
+ {
+ throw new ArgumentException("Invalid plugin type");
+ }
+ }
+
+ ///
+ /// Validates sandbox directive values.
+ ///
+ private void ValidateSandboxDirective(string value)
+ {
+ string[] validSandboxValues =
+ {
+ "allow-forms",
+ "allow-scripts",
+ "allow-same-origin",
+ "allow-top-navigation",
+ "allow-popups",
+ };
+
+ var values = value.Split(' ');
+
+ if (values.Any(v => !validSandboxValues.Contains(v)))
+ {
+ throw new ArgumentException("Invalid sandbox directive value");
+ }
+ }
+ }
+}
diff --git a/DNN Platform/DotNetNuke.ContentSecurityPolicy/DotNetNuke.ContentSecurityPolicy.csproj b/DNN Platform/DotNetNuke.ContentSecurityPolicy/DotNetNuke.ContentSecurityPolicy.csproj
new file mode 100644
index 00000000000..738aa41a77d
--- /dev/null
+++ b/DNN Platform/DotNetNuke.ContentSecurityPolicy/DotNetNuke.ContentSecurityPolicy.csproj
@@ -0,0 +1,45 @@
+
+
+
+ netstandard2.0
+ false
+ true
+ $(MSBuildProjectDirectory)\..\..
+ bin/$(Configuration)/$(TargetFramework)/DotNetNuke.ContentSecurityPolicy.xml
+
+
+ true
+ latest
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+ SolutionInfo.cs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/DNN Platform/DotNetNuke.ContentSecurityPolicy/IContentSecurityPolicy.cs b/DNN Platform/DotNetNuke.ContentSecurityPolicy/IContentSecurityPolicy.cs
new file mode 100644
index 00000000000..4f507829e81
--- /dev/null
+++ b/DNN Platform/DotNetNuke.ContentSecurityPolicy/IContentSecurityPolicy.cs
@@ -0,0 +1,156 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information
+
+namespace DotNetNuke.ContentSecurityPolicy
+{
+ using System;
+
+ ///
+ /// Interface définissant les opérations de gestion de la Content Security Policy.
+ ///
+ public interface IContentSecurityPolicy
+ {
+ ///
+ /// Gets a cryptographically secure nonce value for the CSP policy.
+ ///
+ string Nonce { get; }
+
+ ///
+ /// Gets the default source contributor.
+ ///
+ SourceCspContributor DefaultSource { get; }
+
+ ///
+ /// Gets the script source contributor.
+ ///
+ SourceCspContributor ScriptSource { get; }
+
+ ///
+ /// Gets the style source contributor.
+ ///
+ SourceCspContributor StyleSource { get; }
+
+ ///
+ /// Gets the image source contributor.
+ ///
+ SourceCspContributor ImgSource { get; }
+
+ ///
+ /// Gets the connect source contributor.
+ ///
+ SourceCspContributor ConnectSource { get; }
+
+ ///
+ /// Gets the font source contributor.
+ ///
+ SourceCspContributor FontSource { get; }
+
+ ///
+ /// Gets the object source contributor.
+ ///
+ SourceCspContributor ObjectSource { get; }
+
+ ///
+ /// Gets the media source contributor.
+ ///
+ SourceCspContributor MediaSource { get; }
+
+ ///
+ /// Gets the frame source contributor.
+ ///
+ SourceCspContributor FrameSource { get; }
+
+ ///
+ /// Gets the frame ancestors contributor.
+ ///
+ SourceCspContributor FrameAncestors { get; }
+
+ ///
+ /// Gets the Form action source contributor.
+ ///
+ SourceCspContributor FormAction { get; }
+
+ ///
+ /// Gets the base URI source contributor.
+ ///
+ SourceCspContributor BaseUriSource { get; }
+
+ ///
+ /// Supprimer une source de script à la politique.
+ ///
+ /// Le type de source CSP à supprimer.
+ void RemoveScriptSources(CspSourceType cspSourceType);
+
+ ///
+ /// Ajoute des types de plugins à la politique.
+ ///
+ /// Le type de plugin à autoriser.
+ void AddPluginTypes(string value);
+
+ ///
+ /// Ajoute une directive sandbox à la politique.
+ ///
+ /// Les options de la directive sandbox.
+ void AddSandbox(string value);
+
+ ///
+ /// Ajoute une action de formulaire à la politique.
+ ///
+ /// Le type de source CSP à ajouter.
+ /// L'URL autorisée pour la soumission du formulaire.
+ void AddFormAction(CspSourceType sourceType, string value);
+
+ ///
+ /// Ajoute des ancêtres de frame à la politique.
+ ///
+ /// Le type de source CSP à ajouter.
+ /// L'URL autorisée comme ancêtre de frame.
+ void AddFrameAncestors(CspSourceType sourceType, string value);
+
+ ///
+ /// Ajoute une URI de rapport à la politique.
+ ///
+ /// Le nom où les rapports de violation seront envoyés.
+ /// L'URI où les rapports de violation seront envoyés.
+ public void AddReportEndpoint(string name, string value);
+
+ ///
+ /// Ajoute une destination de rapport à la politique.
+ ///
+ /// L'endpoint où envoyer les rapports.
+ void AddReportTo(string value);
+
+ ///
+ /// Parses a CSP header string into a ContentSecurityPolicy object.
+ ///
+ /// The CSP header string to parse.
+ /// A ContentSecurityPolicy object representing the parsed header.
+ /// Thrown when the CSP header is invalid or cannot be parsed.
+ IContentSecurityPolicy AddHeader(string cspHeader);
+
+ ///
+ /// Ajoute une directive de rapport à la politique.
+ ///
+ /// La directive de rapport à ajouter.
+ /// A ContentSecurityPolicy object representing the parsed header.
+ IContentSecurityPolicy AddReportEndpointHeader(string header);
+
+ ///
+ /// Génère la politique de sécurité complète.
+ ///
+ /// La politique de sécurité complète sous forme de chaîne.
+ string GeneratePolicy();
+
+ ///
+ /// Génère la politique de sécurité complète.
+ ///
+ /// Reporting Endpoints sous forme de chaîne.
+ string GenerateReportingEndpoints();
+
+ ///
+ /// Upgrade Insecure Requests.
+ ///
+ void UpgradeInsecureRequests();
+ }
+}
diff --git a/DNN Platform/DotNetNuke.ContentSecurityPolicy/README.md b/DNN Platform/DotNetNuke.ContentSecurityPolicy/README.md
new file mode 100644
index 00000000000..33845255c6a
--- /dev/null
+++ b/DNN Platform/DotNetNuke.ContentSecurityPolicy/README.md
@@ -0,0 +1,119 @@
+# DotNetNuke.ContentSecurityPolicy
+
+The `DotNetNuke.ContentSecurityPolicy` library provides a fluent API for building and emitting Content Security Policy (CSP) headers in DNN. The `IContentSecurityPolicy` interface is the main entry point to compose directives, manage sources, configure reporting, and generate final header strings.
+
+## Interface: `IContentSecurityPolicy`
+Namespace: `DotNetNuke.ContentSecurityPolicy`
+
+### Properties
+- **Nonce**: Cryptographically secure nonce value to use with inline script/style tags.
+- **DefaultSource**: `SourceCspContributor` for `default-src`.
+- **ScriptSource**: `SourceCspContributor` for `script-src`.
+- **StyleSource**: `SourceCspContributor` for `style-src`.
+- **ImgSource**: `SourceCspContributor` for `img-src`.
+- **ConnectSource**: `SourceCspContributor` for `connect-src`.
+- **FontSource**: `SourceCspContributor` for `font-src`.
+- **ObjectSource**: `SourceCspContributor` for `object-src`.
+- **MediaSource**: `SourceCspContributor` for `media-src`.
+- **FrameSource**: `SourceCspContributor` for `frame-src`.
+- **FrameAncestors**: `SourceCspContributor` for `frame-ancestors`.
+- **FormAction**: `SourceCspContributor` for `form-action`.
+- **BaseUriSource**: `SourceCspContributor` for `base-uri`.
+
+### Methods
+- **RemoveScriptSources(CspSourceType cspSourceType)**: Remove script sources of the specified type (e.g., `Inline`, `Self`, `Nonce`).
+- **AddPluginTypes(string value)**: Add values for `plugin-types` (e.g., `application/pdf`).
+- **AddSandboxDirective(string value)**: Add `sandbox` options (e.g., `allow-scripts allow-same-origin`).
+- **AddFormAction(CspSourceType sourceType, string value)**: Add a `form-action` source.
+- **AddFrameAncestors(CspSourceType sourceType, string value)**: Add a `frame-ancestors` source.
+- **AddReportEndpoint(string name, string value)**: Add a named reporting endpoint.
+- **AddReportTo(string value)**: Add a `report-to` group name to the policy.
+- **AddHeaders(string cspHeader)**: Parse and merge a CSP header string; returns the same `IContentSecurityPolicy` for chaining.
+- **GeneratePolicy()**: Build the `Content-Security-Policy` header value.
+- **GenerateReportingEndpoints()**: Build the reporting header value(s).
+- **UpgradeInsecureRequests()**: Add the `upgrade-insecure-requests` directive.
+
+## Working with sources
+Directive properties expose a `SourceCspContributor`, which supports adding/removing sources such as:
+- `AddSelf()` → `'self'`
+- `AddNone()` → `'none'`
+- `AddInline()` → `'unsafe-inline'`
+- `AddEval()` → `'unsafe-eval'`
+- `AddStrictDynamic()` → `'strict-dynamic'`
+- `AddNonce(string)` → `'nonce-'`
+- `AddHash(string)` → `'sha256-...'`, `'sha384-...'`, `'sha512-...'`
+- `AddHost(string)` → `example.com`, `https://cdn.example.com`
+- `AddScheme(string)` → `https:`, `data:`, `blob:`
+- `RemoveSources(CspSourceType)` to remove by type
+
+See: `CspSourceType.cs`, `CspSource.cs`, `SourceCspContributor.cs`.
+
+## Usage examples
+
+### Configure a baseline policy with a nonce
+```csharp
+using DotNetNuke.ContentSecurityPolicy;
+
+public class CspExample
+{
+ private readonly IContentSecurityPolicy _csp;
+
+ public CspExample(IContentSecurityPolicy csp)
+ {
+ _csp = csp;
+ }
+
+ public void Configure()
+ {
+ // Default baseline
+ _csp.DefaultSource.AddSelf();
+ _csp.ScriptSource.AddSelf().AddNonce(_csp.Nonce);
+ _csp.StyleSource.AddSelf().AddNonce(_csp.Nonce);
+ _csp.ImgSource.AddSelf().AddScheme("data:");
+
+ // Lock down frames and forms
+ _csp.FrameAncestors.AddNone();
+ _csp.FormAction.AddSelf();
+
+ // Reporting
+ _csp.AddReportEndpoint("csp-endpoint", "/api/csp/report");
+ _csp.AddReportTo("csp-endpoint");
+
+ // Optionally upgrade insecure requests
+ _csp.UpgradeInsecureRequests();
+
+ // Generate header values
+ var cspHeader = _csp.GeneratePolicy();
+ var reportingHeader = _csp.GenerateReportingEndpoints();
+ // Emit headers via your pipeline/middleware/module
+ }
+}
+```
+
+### Parse and merge an existing CSP header
+```csharp
+_csp.AddHeaders("default-src 'self'; img-src 'self' data:")
+ .ScriptSource.AddNonce(_csp.Nonce);
+
+var headerValue = _csp.GeneratePolicy();
+```
+
+### Remove an unsafe source
+```csharp
+_csp.RemoveScriptSources(CspSourceType.Inline);
+```
+
+## Notes
+- Nonce: use `Nonce` in your inline tags: `