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..ac62f34c12e
--- /dev/null
+++ b/DNN Platform/DotNetNuke.ContentSecurityPolicy/ContentSecurityPolicy.cs
@@ -0,0 +1,492 @@
+// 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;
+ private bool checkSyntax;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Check syntax.
+ public ContentSecurityPolicy(bool checkSyntax)
+ {
+ this.checkSyntax = checkSyntax;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ContentSecurityPolicy()
+ : this(false)
+ {
+ }
+
+ ///
+ /// 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];
+ using (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)));
+ }
+
+ ///
+ /// Clear Content Security Policy Contributors.
+ ///
+ public void ClearContentSecurityPolicyContributors()
+ {
+ this.ContentSecurityPolicyContributors.Clear();
+ }
+
+ ///
+ /// Clear Reporting Endpoints Contributors.
+ ///
+ public void ClearReportingEndpointsContributors()
+ {
+ this.ReportingEndpointsContributors.Clear();
+ }
+
+ ///
+ /// 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.checkSyntax);
+ 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.checkSyntax);
+ this.AddContributor(contributor);
+ }
+
+ if (sourceType == CspSourceType.Nonce && string.IsNullOrEmpty(value))
+ {
+ value = this.Nonce;
+ }
+
+ contributor.AddSource(new CspSource(sourceType, value, this.checkSyntax));
+ }
+
+ ///
+ /// 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.checkSyntax);
+ 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.checkSyntax);
+ this.AddContributor(contributor);
+ }
+
+ contributor.DirectiveValue = 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.checkSyntax);
+ this.AddContributor(contributor);
+ }
+
+ contributor.DirectiveValue = 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.checkSyntax);
+ 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.checkSyntax);
+ 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..f6609b0264f
--- /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)
+ {
+ var knownSchemes = new string[] { "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..a22642317b1
--- /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 that defines the default policy for unspecified resource types.
+ ///
+ DefaultSrc,
+
+ ///
+ /// Directive that controls authorized script sources.
+ ///
+ ScriptSrc,
+
+ ///
+ /// Directive that controls authorized style sources.
+ ///
+ StyleSrc,
+
+ ///
+ /// Directive that controls authorized image sources.
+ ///
+ ImgSrc,
+
+ ///
+ /// Directive that controls authorized connection destinations.
+ ///
+ ConnectSrc,
+
+ ///
+ /// Directive that controls authorized font sources.
+ ///
+ FontSrc,
+
+ ///
+ /// Directive that controls authorized object sources.
+ ///
+ ObjectSrc,
+
+ ///
+ /// Directive that controls authorized media sources.
+ ///
+ MediaSrc,
+
+ ///
+ /// Directive that controls authorized frame sources.
+ ///
+ FrameSrc,
+
+ ///
+ /// Directive that restricts URLs that can be used in the document's base URI.
+ ///
+ BaseUri,
+
+ ///
+ /// Directive that restricts the types of plugins that can be loaded.
+ ///
+ PluginTypes,
+
+ ///
+ /// Directive that enables a sandbox for the requested resource.
+ ///
+ SandboxDirective,
+
+ ///
+ /// Directive that restricts URLs that can be used as form targets.
+ ///
+ FormAction,
+
+ ///
+ /// Directive that specifies the parents authorized to embed a page in a frame.
+ ///
+ FrameAncestors,
+
+ ///
+ /// Directive that specifies the URI where violation reports should be sent.
+ ///
+ ReportUri,
+
+ ///
+ /// Directive that specifies where to send violation reports in JSON format.
+ ///
+ ReportTo,
+
+ ///
+ /// Directive that specifies UpgradeInsecureRequests.
+ ///
+ UpgradeInsecureRequests,
+}
+}
diff --git a/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspExtensions.cs b/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspExtensions.cs
new file mode 100644
index 00000000000..91a3e89f5c2
--- /dev/null
+++ b/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspExtensions.cs
@@ -0,0 +1,32 @@
+// 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
+{
+ ///
+ /// Provides extension methods for .
+ ///
+ public static class CspExtensions
+ {
+ ///
+ /// Adds script source directives to the specified instance
+ /// to enable support for WebForms, including 'self', 'unsafe-inline', and 'unsafe-eval'.
+ ///
+ /// The content security policy to modify.
+ public static void AddWebformsSupport(this IContentSecurityPolicy csp)
+ {
+ csp.DefaultSource.AddSelf();
+ csp.ScriptSource.AddSelf();
+ csp.ScriptSource.AddInline();
+ csp.ScriptSource.AddEval();
+ csp.StyleSource.AddSelf();
+ csp.ImgSource.AddSelf();
+ csp.FontSource.AddSelf();
+ csp.FrameSource.AddSelf();
+ csp.FormAction.AddSelf();
+ csp.ConnectSource.AddSelf();
+ csp.BaseUriSource.AddSelf();
+ }
+ }
+}
diff --git a/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspSource.cs b/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspSource.cs
new file mode 100644
index 00000000000..d191f858c7c
--- /dev/null
+++ b/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspSource.cs
@@ -0,0 +1,150 @@
+// 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
+ {
+ ///
+ /// Compiled regex for validating host sources (domain with wildcard, IPv4 and IPv6).
+ ///
+ private static readonly Regex DomainRegex = new Regex(@"^(https?://)?(([a-zA-Z0-9-*]+\.)+[a-zA-Z]{2,}|\d{1,3}(\.\d{1,3}){3}|\[?[0-9a-fA-F:]+\]?)(:\d+)?(/.*)?$", RegexOptions.Compiled);
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Type of the source.
+ /// Value of the source.
+ /// Check syntax of value.
+ public CspSource(CspSourceType type, string value = null, bool checkSyntax = false)
+ {
+ this.Type = type;
+ this.Value = this.ValidateSource(type, value, checkSyntax);
+ }
+
+ ///
+ /// 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, bool checkSyntax)
+ {
+ switch (type)
+ {
+ case CspSourceType.Host:
+ return this.ValidateHostSource(value, checkSyntax);
+ case CspSourceType.Scheme:
+ return this.ValidateSchemeSource(value, checkSyntax);
+ case CspSourceType.Nonce:
+ return this.ValidateNonceSource(value);
+ case CspSourceType.Hash:
+ return this.ValidateHashSource(value, checkSyntax);
+ 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, bool checkSyntax)
+ {
+ if (string.IsNullOrWhiteSpace(value))
+ {
+ throw new ArgumentException("Host source cannot be empty");
+ }
+
+ // domain with wildcard, ip4 and ip6 validation
+ if (checkSyntax && !DomainRegex.IsMatch(value))
+ {
+ throw new ArgumentException($"Invalid host source: {value}");
+ }
+
+ return value;
+ }
+
+ ///
+ /// Validates scheme source (protocol).
+ ///
+ private string ValidateSchemeSource(string value, bool checkSyntax)
+ {
+ var validSchemes = new string[] { "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, bool checkSyntax)
+ {
+ var hashPrefixes = new string[] { "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}'";
+ }
+ }
+}
diff --git a/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspSourceType.cs b/DNN Platform/DotNetNuke.ContentSecurityPolicy/CspSourceType.cs
new file mode 100644
index 00000000000..93bf7049663
--- /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
+ {
+ ///
+ /// Allows specifying specific domains as sources.
+ ///
+ Host,
+
+ ///
+ /// Allows specifying protocols (e.g., https:, data:) as sources.
+ ///
+ Scheme,
+
+ ///
+ /// Allows resources from the same origin ('self').
+ ///
+ Self,
+
+ ///
+ /// Allows the use of inline code ('unsafe-inline').
+ ///
+ Inline,
+
+ ///
+ /// Allows the use of eval() ('unsafe-eval').
+ ///
+ Eval,
+
+ ///
+ /// Uses a cryptographic nonce to validate resources.
+ ///
+ Nonce,
+
+ ///
+ /// Uses a cryptographic hash to validate resources.
+ ///
+ Hash,
+
+ ///
+ /// Allows no sources ('none').
+ ///
+ None,
+
+ ///
+ /// Enables strict-dynamic mode for script loading.
+ ///
+ 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..4530fa9c776
--- /dev/null
+++ b/DNN Platform/DotNetNuke.ContentSecurityPolicy/DocumentCspContributor.cs
@@ -0,0 +1,140 @@
+// 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;
+
+ ///
+ /// Contributor for document-level directives.
+ ///
+ public class DocumentCspContributor : BaseCspContributor
+ {
+ ///
+ /// Compiled regex for validating MIME type format (type/subtype).
+ ///
+ private static readonly Regex MimeTypeRegex = new Regex(@"^[a-zA-Z0-9][a-zA-Z0-9!#$&\-\^_.+]*/[a-zA-Z0-9][a-zA-Z0-9!#$&\-\^_.+]*$", RegexOptions.Compiled);
+
+ private string directiveValue;
+
+ private bool checkSyntax;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The directive type to create the contributor for.
+ /// The value of the directive.
+ /// Check syntax of value.
+ public DocumentCspContributor(CspDirectiveType directiveType, string value, bool checkSyntax)
+ {
+ this.checkSyntax = checkSyntax;
+ this.DirectiveType = directiveType;
+ this.DirectiveValue = value;
+ }
+
+ ///
+ /// Gets or Sets value of the document directive.
+ ///
+ public string DirectiveValue
+ {
+ get
+ {
+ return this.directiveValue;
+ }
+
+ set
+ {
+ if (this.checkSyntax)
+ {
+ 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;
+ }
+ }
+
+ ///
+ /// Validates plugin types (MIME types).
+ ///
+ private void ValidatePluginTypes(string value)
+ {
+ if (string.IsNullOrWhiteSpace(value))
+ {
+ throw new ArgumentException("Plugin types cannot be empty");
+ }
+
+ var types = value.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
+
+ if (types.Any(t => !MimeTypeRegex.IsMatch(t)))
+ {
+ throw new ArgumentException($"Invalid MIME type format: {value}");
+ }
+ }
+
+ ///
+ /// Validates sandbox directive values.
+ ///
+ private void ValidateSandboxDirective(string value)
+ {
+ var validSandboxValues = new string[]
+ {
+ "allow-forms",
+ "allow-modals",
+ "allow-orientation-lock",
+ "allow-pointer-lock",
+ "allow-popups",
+ "allow-popups-to-escape-sandbox",
+ "allow-presentation",
+ "allow-same-origin",
+ "allow-scripts",
+ "allow-top-navigation",
+ "allow-top-navigation-by-user-activation",
+ };
+
+ var values = value.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
+
+ if (values.Any(v => !validSandboxValues.Contains(v)))
+ {
+ throw new ArgumentException($"Invalid sandbox directive value: {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..10238c58941
--- /dev/null
+++ b/DNN Platform/DotNetNuke.ContentSecurityPolicy/IContentSecurityPolicy.cs
@@ -0,0 +1,166 @@
+// 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 defining Content Security Policy management operations.
+ ///
+ 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; }
+
+ ///
+ /// Removes a script source from the policy.
+ ///
+ /// The CSP source type to remove.
+ void RemoveScriptSources(CspSourceType cspSourceType);
+
+ ///
+ /// Adds plugin types to the policy.
+ ///
+ /// The plugin type to allow.
+ void AddPluginTypes(string value);
+
+ ///
+ /// Adds a sandbox directive to the policy.
+ ///
+ /// The sandbox directive options.
+ void AddSandbox(string value);
+
+ ///
+ /// Adds a form action to the policy.
+ ///
+ /// The CSP source type to add.
+ /// The allowed URL for form submission.
+ void AddFormAction(CspSourceType sourceType, string value);
+
+ ///
+ /// Adds frame ancestors to the policy.
+ ///
+ /// The CSP source type to add.
+ /// The allowed URL as a frame ancestor.
+ void AddFrameAncestors(CspSourceType sourceType, string value);
+
+ ///
+ /// Adds a report URI to the policy.
+ ///
+ /// The name where violation reports will be sent.
+ /// The URI where violation reports will be sent.
+ public void AddReportEndpoint(string name, string value);
+
+ ///
+ /// Adds a report destination to the policy.
+ ///
+ /// The endpoint where reports will be sent.
+ 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);
+
+ ///
+ /// Adds a report directive to the policy.
+ ///
+ /// The report directive to add.
+ /// A ContentSecurityPolicy object representing the parsed header.
+ IContentSecurityPolicy AddReportEndpointHeader(string header);
+
+ ///
+ /// Generates the complete security policy.
+ ///
+ /// The complete security policy as a string.
+ string GeneratePolicy();
+
+ ///
+ /// Generates the reporting endpoints.
+ ///
+ /// Reporting Endpoints as a string.
+ string GenerateReportingEndpoints();
+
+ ///
+ /// Upgrade Insecure Requests.
+ ///
+ void UpgradeInsecureRequests();
+
+ ///
+ /// Clear Content Security Policy Contributors.
+ ///
+ void ClearContentSecurityPolicyContributors();
+
+ ///
+ /// Clear Reporting Endpoints Contributors.
+ ///
+ void ClearReportingEndpointsContributors();
+ }
+}
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: `