{"id":4027,"date":"2026-09-07T13:28:16","date_gmt":"2026-09-07T13:28:16","guid":{"rendered":"https:\/\/hackagora.com\/?p=4027"},"modified":"2026-09-21T17:30:43","modified_gmt":"2026-09-21T17:30:43","slug":"content-security-policy-csp-how-it-works-bypass-techniques-and-security-best-practices","status":"publish","type":"post","link":"https:\/\/hackagora.com\/en\/content-security-policy-csp-how-it-works-bypass-techniques-and-security-best-practices\/","title":{"rendered":"Content Security Policy (CSP) : How it Works, Bypass Techniques and Security Best Practices"},"content":{"rendered":"\n<figure class=\"wp-block-image alignright size-full is-resized\"><img decoding=\"async\" src=\"https:\/\/hackagora.com\/wp-content\/uploads\/2026\/09\/csp-bypass-techniques.svg\" alt=\"Content Security Policy (CSP) : How it Works, Bypass Techniques and Security Best Practices\" class=\"wp-image-4029\" style=\"width:323px;height:auto\"\/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Content Security Policy (CSP) is one of the main security mechanisms offered by browsers to limit the impact of client-side vulnerabilities. In theory, a well-designed CSP neutralises a large proportion of injection attacks; in practice, it is often weakened by compromises that have accumulated over time: exceptions added to maintain compatibility with legacy code, third-party domain allowlists that expand without ever being pruned, poorly generated nonces, or poorly managed trust relationships. The result is that a policy which appears strict at first glance may, under certain conditions, remain entirely circumventable.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This article examines how CSP works, its main directives and source expressions, the most common configuration errors, and bypass techniques. We will then look at how to audit a CSP in a penetration testing context, followed by how to build a policy that is truly robust against such attacks.<\/p>\n\n\n\n<h2 id=\"aioseo-comprehensive-guide-to-content-security-policy-csp\" class=\"wp-block-heading\">Comprehensive Guide to Content Security Policy (CSP)<\/h2>\n\n\n<div class=\"wp-block-aioseo-table-of-contents\"><ul><li><a class=\"aioseo-toc-item\" href=\"#what-is-content-security-policy\">What is Content Security Policy?<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#what-role-does-csp-play-in-preventing-xss\">What Role Does CSP Play in Preventing XSS?<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#principles-and-how-content-security-policy-works\">Principles and How Content Security Policy Works<\/a><ul><\/ul><\/li><li><a class=\"aioseo-toc-item\" href=\"#understanding-the-key-csp-directives\">Understanding the Key CSP Directives<\/a><ul><li><a class=\"aioseo-toc-item\" href=\"#fetch-directives\">Fetch directives<\/a><ul><li><a class=\"aioseo-toc-item\" href=\"#default-src\">default-src<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#script-src\">script-src<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#script-src-elem-and-script-src-attr\">script-src-elem and script-src-attr<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#style-src-style-src-elem-style-src-attr\">style-src, style-src-elem and style-src-attr<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#img-src\">img-src<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#connect-src\">connect-src<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#frame-src-child-src\">frame-src and child-src<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#worker-src\">worker-src<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#font-src-media-src-manifest-src\">font-src, media-src et manifest-src<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#object-src\">object-src<\/a><\/li><\/ul><\/li><li><a class=\"aioseo-toc-item\" href=\"#directives-related-to-the-document-and-navigation\">Directives related to the document and navigation<\/a><ul><li><a class=\"aioseo-toc-item\" href=\"#base-uri\">base-uri<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#form-action\">form-action<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#frame-ancestors\">frame-ancestors<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#sandbox\">sandbox<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#upgrade-insecure-requests\">upgrade-insecure-requests<\/a><\/li><\/ul><\/li><li><a class=\"aioseo-toc-item\" href=\"#reporting-directives\">Reporting directives<\/a><ul><li><a class=\"aioseo-toc-item\" href=\"#report-to-et-reporting-endpoints\">report-to and Reporting-Endpoints<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#report-uri\">report-uri<\/a><\/li><\/ul><\/li><\/ul><\/li><li><a class=\"aioseo-toc-item\" href=\"#understanding-source-expressions-and-csp-values\">Understanding Source Expressions and CSP Values<\/a><ul><li><a class=\"aioseo-toc-item\" href=\"#self\">\u2018self\u2019<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#none\">\u2018none\u2019<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#host-based-sources\">Host-based sources<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#wildcards\">Wildcards<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#sources-based-on-a-scheme\">Sources based on a scheme<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#data-blob-and-other-special-schemes\">data:, blob: and other special schemes<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#nonces\">Nonces<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#hashes\">Hashes<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#strict-dynamic\">strict-dynamic<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#unsafe-inline\">unsafe-inline<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#unsafe-hashes\">unsafe-hashes<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#unsafe-eval\">unsafe-eval<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#wasm-unsafe-eval\">wasm-unsafe-eval<\/a><\/li><\/ul><\/li><li><a class=\"aioseo-toc-item\" href=\"#trusted-types-and-reducing-dom-xss-attack-surface\">Trusted Types and Reducing DOM XSS Attack Surface<\/a><ul><\/ul><\/li><li><a class=\"aioseo-toc-item\" href=\"#the-limitations-of-content-security-policy\">The Limitations of Content Security Policy<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#what-are-the-most-common-csp-configuration-errors\">What are the Most Common CSP Configuration Errors?<\/a><ul><li><a class=\"aioseo-toc-item\" href=\"#excessive-reliance-on-unsafe-inline\">Excessive reliance on unsafe-inline<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#excessive-reliance-on-unsafe-eval\">Excessive reliance on unsafe-eval<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#wildcards-and-overly-broad-domain-allowlists\">Wildcards and overly broad domain allowlists<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#excessive-confidence-in-self\">Excessive confidence in \u2018self\u2019<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#incomplete-policy\">Incomplete policy<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#explicit-absence-of-object-src-and-base-uri\">Explicit absence of object-src and base-uri<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#static-predictable-or-incorrectly-assigned-nonces\">Static, predictable or incorrectly assigned nonces<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#csp-deployed-in-report-only-mode-only\">CSP deployed in Report-Only mode only<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#inconsistent-deployment-depending-on-the-routes\">Inconsistent deployment depending on the routes<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#compromise-on-compatibility-with-older-browsers\">Compromise on compatibility with older browsers<\/a><\/li><\/ul><\/li><li><a class=\"aioseo-toc-item\" href=\"#common-techniques-for-bypassing-csps\">Common Techniques for Bypassing Content Security Policy<\/a><ul><li><a class=\"aioseo-toc-item\" href=\"#exploiting-a-policy-allowing-unsafe-inline\">Exploiting a policy allowing unsafe-inline<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#trusted-third-party-domain-and-jsonp-endpoint\">Trusted third-party domain and JSONP endpoint<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#bypassing-the-self-script-src-restriction-via-an-upload\">Bypassing the \u2018self\u2019 script-src restriction via an upload<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#bypassing-a-weak-or-misused-nonce\">Bypassing a weak or misused nonce<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#strict-dynamic-and-vulnerable-script-loader\">strict-dynamic and vulnerable script loader<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#gadget-script-trusted-framework-and-dom-clobbering\">Gadget script, trusted framework and DOM clobbering<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#html-injection-without-javascript-execution-but-with-an-impact-via-base-uri-or-form-action\">HTML injection without JavaScript execution, but with an impact via base-uri or form-action<\/a><\/li><\/ul><\/li><li><a class=\"aioseo-toc-item\" href=\"#dom-xss-csp-and-trusted-types-understanding-the-differences-between-these-mechanisms\">DOM XSS, CSP and Trusted Types: Understanding the Differences Between these Mechanisms<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#penetration-testing-methodology-of-a-content-security-policy\">Penetration Testing Methodology of a Content Security Policy<\/a><ul><\/ul><\/li><li><a class=\"aioseo-toc-item\" href=\"#how-to-build-a-robust-content-security-policy-csp\">How to Build a Robust Content Security Policy (CSP)?<\/a><ul><li><a class=\"aioseo-toc-item\" href=\"#start-with-the-actual-functional-requirements\">Start with the actual functional requirements<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#prioritise-nonces-or-hashes-for-javascript\">Prioritise nonces or hashes for JavaScript<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#explicitly-prohibit-inline-event-handlers-wherever-possible\">Explicitly prohibit inline event handlers wherever possible<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#phase-out-unsafe-inline-and-unsafe-eval\">Phase out unsafe-inline and unsafe-eval<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#minimise-third-party-domains\">Minimise third-party domains<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#isolate-user-controlled-content\">Isolate user-controlled content<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#use-base-uri-form-action-frame-ancestors-and-object-src-explicitly\">Use base-uri, form-action, frame-ancestors and object-src explicitly<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#adopt-trusted-types-where-the-architecture-allows\">Adopt Trusted Types where the architecture allows<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#roll-out-the-policy-gradually-using-report-only\">Roll out the policy gradually using Report-Only<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#monitor-breaches-without-turning-reporting-into-noise\">Monitor breaches without turning reporting into noise<\/a><\/li><li><a class=\"aioseo-toc-item\" href=\"#ensuring-the-policy-is-maintained-over-time\">Ensure the policy is maintained over time<\/a><\/li><\/ul><\/li><li><a class=\"aioseo-toc-item\" href=\"#conclusion\">Conclusion<\/a><\/li><\/ul><\/div>\n\n\n<h2 id=\"what-is-content-security-policy\" class=\"wp-block-heading\">What is Content Security Policy?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Content Security Policy is a security mechanism enforced directly by the browser. An application sends the browser a policy that describes the authorised sources and behaviours for the current document. The browser then checks each operation affected by CSP before allowing it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For example, a minimal policy could be sent in the HTTP response:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy: default-src 'self'; script-src 'self'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This configuration specifies that, by default, resources must come from the same origin as the application and that JavaScript scripts must also be loaded from a source corresponding to \u2018<code>self<\/code>\u2019.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If the page then contains:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;script src=\"\/js\/app.js\"&gt;&lt;\/script&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The browser will normally allow the resource to be loaded, as it comes from the current origin. However:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;script src=\"https:\/\/attacker.example\/payload.js\"&gt;&lt;\/script&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">will be blocked if <code>attacker.example<\/code> is not authorised by the directive that actually applies to the script.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">CSP does not attempt to determine whether a JavaScript file is malicious. The browser does not perform a semantic analysis of the code to decide whether it is dangerous. It checks whether the requested loading or execution complies with the rules provided to it. This distinction is essential: CSP is, first and foremost, a system for controlling the capabilities and trust relationships of the document.<\/p>\n\n\n\n<h2 id=\"what-role-does-csp-play-in-preventing-xss\" class=\"wp-block-heading\">What Role Does CSP Play in Preventing XSS?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">CSP is often presented as a safeguard against <a href=\"https:\/\/hackagora.com\/en\/xss-cross-site-scripting-vulnerabilities-types-of-attacks-exploitations-and-security-best-practices\/\" target=\"_blank\" rel=\"noopener\">XSS<\/a>. This statement is correct, provided one understands precisely what it means. A CSP does not fix the vulnerability that allows user-controlled data to be injected into HTML or the DOM. It can, however, render some attack vectors ineffective.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Let us imagine that an application re-injects a user-controlled value without encoding it:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;div&gt;\n    USER_INPUT\n&lt;\/div&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If the user manages to convert this value into arbitrary HTML, the vulnerability exists regardless of CSP. A restrictive policy may, however, prevent certain payloads from being executed. For example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy: script-src 'self'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">An inline JavaScript injection will normally be blocked unless another mechanism within the policy allows it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">However, it would be dangerous to conclude from this that the injection no longer has any impact. An attacker may sometimes find another method: loading a script from an already authorised source, hijacking a trusted JavaScript loader, exploiting a gadget script provided by a framework, altering a form\u2019s destination, manipulating a base URL, or taking advantage of DOM behaviour that does not require the insertion of a new <code>&lt;script&gt;<\/code> block.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">CSP must therefore remain a defence-in-depth measure. Prevention at source still relies on context-aware output encoding, sanitisation where HTML is legitimately accepted, the use of secure DOM APIs, and the removal of pipelines that transform untrusted data into code or interpreted content.<\/p>\n\n\n\n<h2 id=\"principles-and-how-content-security-policy-works\" class=\"wp-block-heading\">Principles and How Content Security Policy Works<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">When a browser receives a CSP-protected document, it analyses the policies associated with the document and constructs the set of rules it must apply whilst using it. When a resource needs to be fetched, when a script needs to be executed, when a form is submitted, or when any other CSP-related operation occurs, the browser determines the applicable directive and then checks whether the action complies with the policy.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Let\u2019s consider the following configuration:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy:\n    default-src 'self';\n    script-src 'self' https:\/\/static.example.com;\n    img-src 'self' https:\/\/images.example.com;\n    connect-src 'self' https:\/\/api.example.com<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">A script from <code>https:\/\/static.example.com\/app.js<\/code> may be allowed, whilst a script from <code>https:\/\/attacker.example\/payload.js<\/code> is blocked. Similarly, a <code>fetch()<\/code> request to <code>https:\/\/api.example.com\/users<\/code> may be allowed by <code>connect-src<\/code>, whilst a request to an undeclared origin is refused.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This mechanism turns the browser into an additional enforcement layer. It does not prevent the application from generating vulnerable HTML, but it can block certain consequences that this HTML might attempt to cause.<\/p>\n\n\n\n<h3 id=\"aioseo-the-http-content-security-policy-header\" class=\"wp-block-heading\">The HTTP Content-Security-Policy header<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The recommended method for implementing a policy is the HTTP header:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy: default-src 'self'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This header is included in the response containing the document to be protected. It enables the policy to be applied as soon as the document is loaded and provides access to all the guidelines specified for this delivery method.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A policy can also be declared in the HTML document:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;meta\n    http-equiv=\"Content-Security-Policy\"\n    content=\"default-src 'self'; script-src 'self'\"&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This method can be useful when it is not possible to modify HTTP headers, but it has several limitations. <code>Content-Security-Policy-Report-Only<\/code> cannot be deployed via a <code>&lt;meta&gt;<\/code><br>tag, and directives such as <code>frame-ancestors<\/code>, <code>report-uri<\/code> or <code>sandbox<\/code> are not supported in this mode. Furthermore, a policy declared using <code>&lt;meta&gt;<\/code> only applies to content located after the element once it has been parsed. Wherever the infrastructure allows, the HTTP header should therefore be used in preference.<\/p>\n\n\n\n<h3 id=\"aioseo-in-browser-validation-process\" class=\"wp-block-heading\">In-browser validation process<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The applicable directive depends on the type of operation. For a <code>&lt;script&gt;<\/code> element, for example, the browser may use `<code>script-src-elem<\/code>`, then fall back to `<code>script-src<\/code>` and finally `<code>default-src<\/code>` if the more specific directives are absent. For an inline event handler such as `<code>onclick<\/code>`, `<code>script-src-attr<\/code>` may apply. For a `<code>fetch()<\/code>` or WebSocket connection, the browser checks `<code>connect-src<\/code>`, then `<code>default-src<\/code>` if `<code>connect-src<\/code>` is not defined.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">However, some directives follow a different logic. `<code>base-uri<\/code>`, `<code>form-action<\/code>` and `<code>frame-ancestors<\/code>` do not derive their values from `<code>default-src<\/code>`. A policy that sets <code>default-src \u2018none\u2019 <\/code>without defining these directives should therefore not be interpreted as automatically blocking the operations they control.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This concept of fallback is one of the most important points to master during an audit. The aim is not to read a string of characters, but to reconstruct the policy actually applied to each behaviour.<\/p>\n\n\n\n<h3 id=\"aioseo-multiple-csp-policies\" class=\"wp-block-heading\">Multiple CSP policies<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">A single response may contain several CSP policies. They do not override one another: the browser must apply them all together.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Let&#8217;s consider:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy: default-src 'self'; connect-src 'none'\nContent-Security-Policy: script-src https:\/\/static.example.com; connect-src https:\/\/api.example.com<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The second policy theoretically allows <code>https:\/\/api.example.com<\/code> for `<code>connect-src<\/code>`, but the first contains <code>connect-src \u2018none\u2019<\/code>. A connection to this API therefore remains blocked, as it must satisfy all policies in enforcement mode.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This behaviour is important in penetration testing. Naively merging two CSPs or reading only the last header can lead to an incorrect conclusion about which resources are actually authorized.<\/p>\n\n\n\n<h3 id=\"aioseo-content-security-policy-and-content-security-policy-report-only\" class=\"wp-block-heading\">Content-Security-Policy and Content-Security-Policy-Report-Only<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">CSP has two main deployment modes. With:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy: default-src 'self'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">violations are effectively blocked. Using:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy-Report-Only: default-src 'self'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">the browser evaluates the policy and may generate reports, but does not block the operation because of this Report-Only policy.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This distinction is fundamental. Report-Only is particularly useful when introducing a CSP, as it allows you to identify legitimate resources that would otherwise be blocked, adjust the policy and then gradually enable enforcement.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The two modes can coexist. An application can maintain a stable policy in enforcement mode whilst simultaneously testing a more restrictive policy in Report-Only mode. During an audit, it is therefore always necessary to identify which policy actually protects the page and which policy merely monitors its behaviour.<\/p>\n\n\n\n<h2 id=\"understanding-the-key-csp-directives\" class=\"wp-block-heading\">Understanding the Key CSP Directives<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A CSP policy consists of directives that control categories of resources or specific behaviours. Some are fetch directives, others protect the document\u2019s structure or navigation, whilst others relate to reporting or modern mechanisms such as Trusted Types.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It is important not to view CSP as a list of independent keywords. The directives form a coherent whole, with fallback mechanisms and interactions that influence the effective policy.<\/p>\n\n\n\n<h3 id=\"fetch-directives\" class=\"wp-block-heading\">Fetch directives<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Fetch directives specify the authorised sources for different types of resources. <code>default-src<\/code> often acts as a fallback value, but a more specific directive overrides this fallback for the category it controls.<\/p>\n\n\n\n<h4 id=\"default-src\" class=\"wp-block-heading\">default-src<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\"><code>default-src<\/code> provides a fallback policy for many categories of resources.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy: default-src 'self'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If no more specific directive exists, the resources in question must match \u2018<code>self<\/code>\u2019. For example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy:\n    default-src 'self';\n    img-src https:\/\/images.example.com<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">images are controlled by <code>img-src<\/code>, whilst other resources covered by the fallback mechanism remain subject to <code>default-src 'self'<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A common hardening strategy is to start with:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy: default-src 'none'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">and then explicitly re-authorise the necessary categories. This approach reduces implicit permissions, but it does not eliminate the need to define directives that do not depend on <code>default-src<\/code>, notably <code>base-uri<\/code>, <code>form-action<\/code> and <code>frame-ancestors<\/code>.<\/p>\n\n\n\n<h4 id=\"script-src\" class=\"wp-block-heading\">script-src<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\"><code>script-src<\/code> is generally the most sensitive directive in a CSP, as it controls a large proportion of the mechanisms that enable JavaScript execution.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy: script-src 'self'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This policy normally allows scripts loaded from the current origin and blocks those from unauthorised origins. It also blocks, in the absence of exceptions, the standard execution of inline scripts and many dynamic evaluation mechanisms.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">However, analysis of <code>script-src<\/code> must never be limited to the list of domains. Nonces, hashes, <code>strict-dynamic<\/code>, <code>'unsafe-inline'<\/code>, <code>'unsafe-eval'<\/code>, <code>'wasm-unsafe-eval'<\/code>, as well as the more specific directives <code>script-src-elem<\/code> and <code>script-src-attr<\/code>, can profoundly alter the trust model.<\/p>\n\n\n\n<h4 id=\"script-src-elem-and-script-src-attr\" class=\"wp-block-heading\">script-src-elem and script-src-attr<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\" id=\"aioseo-script-src-elem-et-script-src-attr\">CSP Level 3 allows for a more granular distinction between scripts contained within <code>&lt;script&gt;<\/code> elements and inline event handlers.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\" id=\"aioseo-script-src-elem-et-script-src-attr\"><code>script-src-elem<\/code> applies to <code>&lt;script&gt;<\/code> elements, whether they are external scripts or inline blocks. For example:<br><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy:\n    script-src 'self';\n    script-src-elem https:\/\/static.example.com<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">In this case, <code>&lt;script&gt;<\/code> elements are evaluated according to `<code>script-src-elem<\/code>`. If this directive is absent, the browser falls back to `<code>script-src<\/code>`, then to `<code>default-src<\/code>` if necessary.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">`<code>script-src-attr<\/code>` applies to JavaScript event handlers declared in HTML attributes, such as:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;button onclick=\"save()\"&gt;Save&lt;\/button&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">A modern application can explicitly disallow these handlers:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy:\n    script-src 'nonce-RANDOM' 'strict-dynamic';\n    script-src-attr 'none'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This distinction is important during an audit, as a policy can be strict for &lt;script&gt; elements whilst maintaining different rules for executable attributes.<\/p>\n\n\n\n<h4 id=\"style-src-style-src-elem-style-src-attr\" class=\"wp-block-heading\">style-src, style-src-elem and style-src-attr<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\"><code>style-src<\/code> controls style sheets and, depending on the configuration, inline CSS. CSP3 also provides <code>style-src-elem<\/code> for elements and style sheets, as well as <code>style-src-attr<\/code> for style attributes.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy: style-src 'self'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Styles generally pose less of a risk than JavaScript when it comes to code execution, but they should not be regarded as having no impact. A CSS injection can alter the user interface in a misleading way and, in certain scenarios, contribute to information leaks or the creation of a side channel. A robust policy therefore restricts styles to only those sources that are genuinely necessary.<\/p>\n\n\n\n<h4 id=\"img-src\" class=\"wp-block-heading\">img-src<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\"><code>img-src<\/code> defines the authorised sources for images.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy:\n    img-src 'self' https:\/\/images.example.com<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Controlling images is useful beyond general security best practice. Loading an image triggers a network request and can be exploited in certain scenarios as a channel for data exfiltration or signalling. Restricting <code>img-src<\/code> therefore limits the capabilities available to injected content, even when that content cannot directly execute JavaScript.<\/p>\n\n\n\n<h4 id=\"connect-src\" class=\"wp-block-heading\">connect-src<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">connect-src controls the destinations to which the document can connect via mechanisms such as <code>fetch()<\/code>, <code>XMLHttpRequest<\/code>, WebSocket, EventSource or certain Beacon-based operations.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy:\n    connect-src 'self' https:\/\/api.example.com<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">A script running on the page will be able to contact the authorised API, but an attempt to connect directly to an unauthorised origin may be blocked. This directive provides a useful defence-in-depth measure against certain forms of data exfiltration, but it should not be interpreted as a blanket ban on all outbound network traffic: other types of resources or browsing are controlled by other directives.<\/p>\n\n\n\n<h4 id=\"frame-src-child-src\" class=\"wp-block-heading\">frame-src and child-src<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\"><code>frame-src<\/code> defines the origins that may be loaded into elements such as <code>&lt;iframe&gt;<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy:\n    frame-src https:\/\/player.example.com<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This directive should not be confused with <code>frame-ancestors<\/code>. frame-src controls what the page can embed; <code>frame-ancestors<\/code> controls which pages are permitted to embed the current page.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>child-src<\/code> is an older directive that can still serve as a fallback in certain contexts, particularly when <code>frame-src<\/code> or <code>worker-src<\/code> are not defined. In a modern policy, it is preferable to explicitly define the directives corresponding to the uses that are actually required, rather than relying on fallback behaviours that are difficult to interpret.<\/p>\n\n\n\n<h4 id=\"worker-src\" class=\"wp-block-heading\">worker-src<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\"><code>worker-src<\/code> controls the sources used to create Workers, SharedWorkers and Service Workers.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy:\n    worker-src 'self'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">When <code>worker-src<\/code> is absent, the search for an applicable directive may proceed via <code>child-src<\/code>, then <code>script-src<\/code>, then <code>default-src<\/code>. This fallback chain explains why a policy that appears strict at first glance may allow or block workers in a way that differs from what developers intended.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In modern applications that use Service Workers, compute workers or complex front-end architectures, this directive should be configured explicitly.<\/p>\n\n\n\n<h4 id=\"font-src-media-src-manifest-src\" class=\"wp-block-heading\">font-src, media-src et manifest-src<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\"><code>font-src<\/code> restricts the origins of web fonts. <code>media-src<\/code> applies to audio and video content, whilst <code>manifest-src<\/code> controls web application manifests.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">These directives are generally less critical than <code>script-src<\/code>, but they contribute to the principle of least privilege. An application should only allow the categories of resources and origins that it actually needs.<\/p>\n\n\n\n<h4 id=\"object-src\" class=\"wp-block-heading\">object-src<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\"><code>object-src<\/code> controls the resources used by the <code>&lt;object&gt;<\/code> and &lt;embed&gt; elements. These mechanisms are less common today and were historically based on technologies with a large attack surface.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A modern policy therefore frequently specifies:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy: object-src 'none'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">However, an important caveat must be noted. <code>object-src<\/code> has a fallback to <code>default-src<\/code>. Its absence therefore does not necessarily imply a complete lack of restriction if <code>default-src<\/code> is already restrictive. Explicitly setting <code>object-src \u2018none\u2019<\/code> remains, however, a best practice when these resources are not required, as it clearly expresses the security intent and prevents any future relaxation of <code>default-src<\/code> from inadvertently reintroducing this capability.<\/p>\n\n\n\n<h3 id=\"directives-related-to-the-document-and-navigation\" class=\"wp-block-heading\">Directives related to the document and navigation<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Some important directives do not directly control a JavaScript file, an image or a stylesheet. They affect the document\u2019s structure, the navigation destinations or the way in which the page can be embedded.<\/p>\n\n\n\n<h4 id=\"base-uri\" class=\"wp-block-heading\">base-uri<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">The HTML tag <code>&lt;base&gt;<\/code> allows the base used by the browser to resolve a document\u2019s relative URLs to be changed. An injection such as:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;base href=\"https:\/\/attacker.example\/\"&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">can therefore alter the resolution of relative references further down the page. To prevent this behaviour when the application does not use <code>&lt;base&gt;<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy: base-uri 'none'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">is a robust choice. Where this functionality is required, <code>base-uri \u2018self\u2019<\/code> may be a more permissive alternative. <code>base-uri<\/code> does not derive its value from <code>default-src<\/code>, which means that its absence must be explicitly checked for.<\/p>\n\n\n\n<h4 id=\"form-action\" class=\"wp-block-heading\">form-action<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\"><code>form-action<\/code> defines the permitted destinations for form submissions.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy: form-action 'self'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This directive is particularly useful when an HTML injection vulnerability remains, even where JavaScript cannot be executed. An attacker may sometimes attempt to inject or modify a form in order to send data to another origin. form-action helps to reduce this attack surface.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Like <code>base-uri<\/code>, this directive has no fallback to <code>default-src<\/code>. A <code>default-src 'none'<\/code> policy therefore does not automatically block all form destinations.<\/p>\n\n\n\n<h4 id=\"frame-ancestors\" class=\"wp-block-heading\">frame-ancestors<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">frame-ancestors controls which origins are permitted to embed the current page within a frame.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy: frame-ancestors 'none'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">prohibits all embedding, whilst:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy: frame-ancestors 'self'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">restricts embedding to origins matching <code>\u2018self\u2019<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This directive provides a modern mechanism for protecting against clickjacking and offers greater flexibility than the older <code>X-Frame-Options<\/code> header. It has no fallback to <code>default-src<\/code> and must therefore be explicitly set when protection against framing is required.<\/p>\n\n\n\n<h4 id=\"sandbox\" class=\"wp-block-heading\">sandbox<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>sandbox<\/code> directive applies restrictions to the document that are comparable to those of an iframe\u2019s <code>sandbox<\/code> attribute. Depending on the tokens that are explicitly reauthorised, it can restrict script execution, forms, navigation, pop-ups or certain features of the origin.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This directive is not suitable for all pages, but it can be useful for isolating content that is inherently less trustworthy. It must be used with caution, as a configuration that is too restrictive may break many features, whilst one that is too permissive may give a false sense of isolation.<\/p>\n\n\n\n<h4 id=\"upgrade-insecure-requests\" class=\"wp-block-heading\">upgrade-insecure-requests<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">The directive:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy: upgrade-insecure-requests<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">instructs the browser to treat certain HTTP URLs as if they had been replaced by their HTTPS equivalents. It can facilitate the migration of a legacy application that still contains insecure references.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">However, it does not replace the correct implementation of HTTPS or HSTS. Its role is to facilitate the transition and reduce certain forms of mixed content, not to constitute a secure transport strategy in its own right.<\/p>\n\n\n\n<h3 id=\"reporting-directives\" class=\"wp-block-heading\">Reporting directives<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">CSP reporting provides visibility into the operations that the policy blocks or would block. It is particularly useful during the phased roll-out of a policy or for monitoring the emergence of new dependencies.<\/p>\n\n\n\n<h4 id=\"report-to-et-reporting-endpoints\" class=\"wp-block-heading\">report-to and Reporting-Endpoints<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">The modern mechanism relies on an endpoint declared separately:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Reporting-Endpoints: csp-endpoint=\"https:\/\/reports.example.com\/csp\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">and then referenced from the CSP:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy:\n    default-src 'self';\n    report-to csp-endpoint<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><code>report-to<\/code> contains the logical name of the endpoint, whilst <code>Reporting-Endpoints<\/code> associates this name with a URL. When a violation is detected, the browser can send a report containing, amongst other things, the applicable directive, the blocked resource and the relevant \u2018enforce\u2019 or \u2018report\u2019 mode.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Reporting must be treated as a data source that may be controlled by an attacker. The collected values must not be displayed in an internal dashboard without being encoded or validated.<\/p>\n\n\n\n<h4 id=\"report-uri\" class=\"wp-block-heading\">report-uri<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\"><code>report-uri<\/code> is the legacy CSP reporting mechanism. It has now been superseded by <code>report-to<\/code> and the Reporting API, but remains useful for certain older browsers or environments.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">During a transition period, an application may therefore declare both mechanisms. It should be borne in mind, however, that a browser supporting <code>report-to<\/code> may prioritise this mechanism and ignore <code>report-uri<\/code> for the policy in question.<\/p>\n\n\n\n<h2 id=\"understanding-source-expressions-and-csp-values\" class=\"wp-block-heading\">Understanding Source Expressions and CSP Values<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A directive alone is not enough to determine whether a policy is restrictive. Actual security depends on how the sources and keywords are specified.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Thus, the following policies all use <code>script-src<\/code> but establish very different trust models:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy: script-src *<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy: script-src 'self'<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy: script-src 'nonce-RANDOM' 'strict-dynamic'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The first relies on an extremely broad authorisation of network sources. The second trusts the origin of the application. The third identifies specific root scripts using a nonce and can extend this trust to the scripts they load dynamically.<\/p>\n\n\n\n<h3 id=\"self\" class=\"wp-block-heading\">\u2018self\u2019<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The value <code>\u2018self\u2019<\/code> refers to the protected origin and takes into account, in particular, the scheme, host and port, along with certain secure upgrade rules specified by CSP. A policy such as:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy: script-src 'self'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">allows, for example, a relative script such as <code>\/js\/app.js<\/code> to be loaded when it belongs to the corresponding origin.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">However, it would be incorrect to automatically treat <code>\u2018self\u2019<\/code> as a secure resource. If the application also serves, from this same origin, user-controlled files, dynamically generated content or resources from storage that is insufficiently compartmentalised, the trust boundary may be too broad. The browser does not know that a file located in <code>\/uploads\/<\/code> is \u2018user-provided\u2019 whilst a file located in <code>\/js\/<\/code> is \u2018application-provided\u2019: if both correspond to the same CSP source, they belong to the same trust boundary.<\/p>\n\n\n\n<h3 id=\"none\" class=\"wp-block-heading\">\u2018none\u2019<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The value <code>\u2018none\u2019<\/code> represents the empty set for the directive in question.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy: object-src 'none'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">thus prohibits resources controlled by <code>object-src<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>\u2018none\u2019<\/code> must be used on its own in a list of sources to express this prohibition. If it is mixed with other source expressions, those other expressions may still match; therefore, one must not write an ambiguous configuration on the assumption that <code>\u2018none\u2019<\/code> will take precedence over everything else.<\/p>\n\n\n\n<h3 id=\"host-based-sources\" class=\"wp-block-heading\">Host-based sources<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">A host source may contain a scheme, a host, a port and, optionally, a path.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>script-src https:\/\/static.example.com<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">trusts resources corresponding to this source. The host may also be restricted to a path:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>script-src https:\/\/static.example.com\/js\/<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Paths ending with <code>\/<\/code> act as prefixes, whilst a path without a trailing <code>\/<\/code> may be matched more strictly. It should be borne in mind, however, that redirects have a specific semantics: following a redirect, the path portion of the expression is not used in the same way for matching. A path restriction should therefore not be regarded as a security boundary equivalent to an origin separation.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Authorising a host also raises a fundamental question: who can serve content from that host? A domain belonging to a reputable provider may still pose a risk in <code>script-src<\/code> if it is a shared platform where any user can publish JavaScript.<\/p>\n\n\n\n<h3 id=\"wildcards\" class=\"wp-block-heading\">Wildcards<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Wildcards allow you to broaden the scope of an expression. For example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>script-src https:\/\/*.example.com<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">allows a set of subdomains matching the expression.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Such a rule should not be analysed solely on the basis of the main domain. Overlooked development environments, subdomains delegated to third parties, storage buckets, customer spaces or integrated SaaS services may all become relevant.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The global wildcard <code>*<\/code> is even more permissive for network sources compatible with the relevant directive. However, it should not be described as \u2018absolutely all possible sources\u2019: special schemas such as <code>data:<\/code> or <code>blob:<\/code> are handled differently and often need to be explicitly allowed. The correct security conclusion remains that <code>*<\/code> massively expands the scope of trust and generally has no place in the script-src directive of a policy aimed at limiting XSS.<\/p>\n\n\n\n<h3 id=\"sources-based-on-a-scheme\" class=\"wp-block-heading\">Sources based on a scheme<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">A source such as:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>img-src https:<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">allows resources that comply with the HTTPS scheme, without restricting the host. This level of trust may be acceptable for certain less sensitive categories, depending on the context, but would be far more dangerous with:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>script-src https:<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">as the browser could then consider scripts from a very large number of HTTPS origins to be permissible.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Modern policies should therefore favour sources that are as specific as possible, particularly for JavaScript.<\/p>\n\n\n\n<h3 id=\"data-blob-and-other-special-schemes\" class=\"wp-block-heading\">data:, blob: and other special schemes<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><code>data:<\/code> and <code>blob:<\/code> schemes are sometimes required by front-end applications that generate images, downloads or other resources locally. However, authorisation for these must be restricted to the directives that actually require them.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For example, authorising:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>img-src 'self' data:<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">may be justified for inline images, whilst applying the same logic to <code>script-src<\/code> would introduce a much more vulnerable attack surface.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>blob:<\/code> also warrants specific attention. Depending on the resource category and the browser, a Blob may become an executable object or load code into a worker. A robust policy therefore does not add <code>data:<\/code> or <code>blob:<\/code> as a matter of course; it documents their necessity for each resource type.<\/p>\n\n\n\n<h3 id=\"nonces\" class=\"wp-block-heading\">Nonces<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">A CSP nonce is a random value associated with a given response and used to identify explicitly authorised elements.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The response may contain:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy:\n    script-src 'nonce-a8Dk3mQ91Yx7...'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">and the document:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;script nonce=\"a8Dk3mQ91Yx7...\"&gt;\n    initialiseApplication();\n&lt;\/script&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The browser compares the element\u2019s nonce with that of the policy. If the values match and the other CSP conditions are met, the script is permitted.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The benefit of this model is significant: trust is no longer granted to an entire domain but directly to specific scripts approved by the application for that response.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">To be effective, a nonce must be generated on the server side using a cryptographically secure random source, possess sufficient entropy and be renewed with every response. A static value, a predictable value or one derived from a timestamp does not provide the required property.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">However, cryptographic quality alone is not sufficient. If the rendering engine automatically adds the nonce to a <code>&lt;script&gt;<\/code> element, part of which is controlled by an attacker, the policy may continue to trust the incorrect content. It is therefore necessary to audit both the generation of the nonce and the locations where it is applied.<\/p>\n\n\n\n<h3 id=\"hashes\" class=\"wp-block-heading\">Hashes<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Hashes allow you to authorise specific content rather than a domain or a value that changes with every response.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy:\n    script-src 'sha256-BASE64_HASH'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The browser calculates the hash of the script in question and checks that it matches the one declared in the policy. This approach is suitable for static inline blocks whose content changes infrequently.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Hashes offer a high level of granularity, but maintaining them can become costly if the application frequently modifies its HTML or generates dynamic inline code. They can also be used as part of a Strict CSP with <code>strict-dynamic<\/code>, where the script corresponding to the hash becomes a trusted root.<\/p>\n\n\n\n<h3 id=\"strict-dynamic\" class=\"wp-block-heading\">strict-dynamic<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><code>strict-dynamic<\/code> fundamentally changes the way in which <code>script-src<\/code> establishes trust.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Consider the following:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy:\n    script-src 'nonce-RANDOM' 'strict-dynamic'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">and a root script:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;script nonce=\"RANDOM\" src=\"\/js\/loader.js\"&gt;&lt;\/script&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The browser trusts this script because it contains the expected nonce. When this script subsequently loads other scripts programmatically, trust can be propagated to these new scripts under the conditions specified by CSP.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const script = document.createElement(\"script\");\nscript.src = \"\/js\/module.js\";\ndocument.head.appendChild(script);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The aim is to avoid having to maintain large domain allowlists for applications that use loaders or dynamic bundles.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In CSP3 browsers, when <code>strict-dynamic<\/code> is enabled with a trust root based on a nonce or hash, the `host sources`, `scheme sources`, <code>`self`<\/code> and <code>`unsafe-inline`<\/code> values present in the relevant directive are ignored for the modern model. This makes it possible, in particular, to create backwards-compatible policies where older browsers use allowlists whilst newer browsers rely on nonce + <code>strict-dynamic<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">However, this property carries a significant responsibility: root scripts become critical security components. If an authorised script creates a new <code>&lt;script&gt;<\/code> element from a user-controlled URL, an attacker may seek to exploit the trust already granted rather than injecting an unauthorised script themselves.<\/p>\n\n\n\n<h3 id=\"unsafe-inline\" class=\"wp-block-heading\">unsafe-inline<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><code>'unsafe-inline'<\/code> re-enables forms of inline content that CSP normally seeks to prevent, notably <code>&lt;script&gt;<\/code> blocks and, depending on the directive actually applied, certain inline event handlers.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy:\n    script-src 'self' 'unsafe-inline'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">can therefore make many HTML injections directly exploitable in JavaScript.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">However, we must avoid drawing overly simplistic conclusions. In a modern policy that uses nonces or hashes, the browser may ignore \u2018unsafe-inline\u2019 for the relevant scripts. With <code>strict-dynamic<\/code>, this value is also ignored by CSP3 browsers in the modern trust model. Furthermore, <code>script-src-attr<\/code> can impose a specific rule on event handlers.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">During a penetration test, the textual presence of <code>\u2018unsafe-inline\u2019<\/code> is therefore an indicator to analyse, not automatic proof of a bypass.<\/p>\n\n\n\n<h3 id=\"unsafe-hashes\" class=\"wp-block-heading\">unsafe-hashes<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><code>'unsafe-hashes'<\/code> facilitates certain migration scenarios where an application wishes to retain specific inline event handlers without re-enabling the entire <code>'unsafe-inline'<\/code> feature. The mechanism allows certain executable attributes to be permitted via their hashes.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Whilst this approach may be useful temporarily, modern code should, wherever possible, replace:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;button onclick=\"save()\"&gt;Save&lt;\/button&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">with a binding implemented in permitted JavaScript:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>button.addEventListener(\"click\", save);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This reduces reliance on executable code directly embedded within HTML and facilitates the adoption of a strict policy.<\/p>\n\n\n\n<h3 id=\"unsafe-eval\" class=\"wp-block-heading\">unsafe-eval<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><code>'unsafe-eval'<\/code> enables various mechanisms that construct or execute code from strings, notably <code>eval()<\/code> and <code>Function()<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy:\n    script-src 'self' 'unsafe-eval'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">does not, on its own, introduce an XSS vulnerability. However, it removes a barrier that might otherwise have blocked a dangerous evaluation primitive.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const expression = getUserControlledValue();\neval(expression);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">is vulnerable because untrusted data reaches an execution mechanism. The presence of <code>\u2018unsafe-eval\u2019<\/code> then allows the exploit to occur despite the CSP.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Dependencies that still require <code>\u2018unsafe-eval\u2019<\/code> in production must be identified and, where possible, reconfigured or replaced.<\/p>\n\n\n\n<h3 id=\"wasm-unsafe-eval\" class=\"wp-block-heading\">wasm-unsafe-eval<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">WebAssembly applications may need to permit certain dynamic compilation mechanisms. <code>\u2018wasm-unsafe-eval\u2019<\/code> provides a more targeted authorisation than <code>\u2018unsafe-eval\u2019<\/code> for this purpose.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Where only dynamic WebAssembly is required, this setting is preferable to the much broader activation of <code>\u2018unsafe-eval\u2019<\/code>. However, if <code>\u2018unsafe-eval\u2019<\/code> is already enabled, it allows more mechanisms and makes this level of granularity less useful.<\/p>\n\n\n\n<h2 id=\"trusted-types-and-reducing-dom-xss-attack-surface\" class=\"wp-block-heading\">Trusted Types and Reducing DOM XSS Attack Surface<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Trusted Types complements CSP by addressing an issue that goes beyond the simple selection of sources. The aim is to reduce the number of places where an arbitrary string can be passed to DOM sinks capable of interpreting HTML, script or script URLs.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Let&#8217;s consider:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>element.innerHTML = userInput;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">A strict CSP may block certain payloads produced by this assignment, but the dangerous sink remains. Trusted Types allows you to instruct the browser that certain APIs should no longer accept strings directly, but should instead receive typed objects produced by policies explicitly created by the application.<\/p>\n\n\n\n<h3 id=\"aioseo-require-trusted-types-for-script\" class=\"wp-block-heading\">require-trusted-types-for \u2018script\u2019<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">An application can enable:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy:\n    require-trusted-types-for 'script'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The browser then applies the Trusted Types requirements to the relevant DOM sinks. Directly assigning a string to a protected sink may result in an error if the value has not been produced by an appropriate Trusted Types policy.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Since February 2026, this directive has been available in recent versions of the major browsers, although compatibility with older clients must still be taken into account in any deployment strategy.<\/p>\n\n\n\n<h3 id=\"aioseo-the-trusted-types-directive\" class=\"wp-block-heading\">The trusted-types directive<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The `trusted-types` directive controls the names of policies that the code is authorised to create.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy:\n    require-trusted-types-for 'script';\n    trusted-types appPolicy<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The code can then create an authorised policy:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const policy = trustedTypes.createPolicy(\"appPolicy\", {\n    createHTML: input =&gt; sanitizer.sanitize(input)\n});<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The benefit is that it centralises dangerous transformations and makes the points where trusted values are created easier to audit.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This directive does not automatically make the code secure. A policy such as:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>trustedTypes.createPolicy(\"appPolicy\", {\n    createHTML: input =&gt; input\n});<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">does not sanitise anything. It wraps an arbitrary string in an approved object and therefore shifts the vulnerability to the policy itself.<\/p>\n\n\n\n<h3 id=\"aioseo-limitations-of-trusted-types\" class=\"wp-block-heading\">Limitations of Trusted Types<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Trusted Types is particularly useful against DOM XSS attacks linked to certain sinks, but it does not replace data validation or the analysis of trusted code behaviour. Scripts can still create URLs, load resources, interpret data structures or perform operations, not all of which are protected by the same mechanism.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The audit must therefore examine the Trusted Types policies, the <code>createHTML<\/code>, <code>createScript<\/code> and <code>createScriptURL<\/code> functions that are actually defined, as well as the data they receive. The main benefit is architectural: the area of code to be examined is reduced and conversions to trusted types become explicit.<\/p>\n\n\n\n<h2 id=\"the-limitations-of-content-security-policy\" class=\"wp-block-heading\">The Limitations of Content Security Policy<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">CSP can significantly reduce the exploitability of a client-side vulnerability, but it is no substitute for fundamental application-level safeguards.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">An HTML injection remains a vulnerability even if an injected <code>&lt;script&gt;<\/code> is blocked. An attacker can still modify the interface, create interactive elements, influence certain navigation paths, hijack a form or exploit behaviour provided by JavaScript that has already been authorised.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">An application using <code>innerHTML<\/code> with untrusted data also remains vulnerable, even if a restrictive CSP blocks several obvious payloads. The fundamental problem is that untrusted data reaches an HTML interpreter.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Furthermore, CSP considers certain resources to be trusted by definition. If an authorised script contains a vulnerable loader, a gadget or logic that transforms controlled data into a new executable resource, the browser has no reason to regard this behaviour as malicious: the action is carried out by a component that the policy has already trusted.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Finally, CSP is not designed to replace other browser and server controls. HSTS, secure cookies, CSRF protections, access controls, input validation, dependency security and origin isolation remain necessary. CSP should be understood as a specific layer of defence-in-depth rather than as a universal security policy.<\/p>\n\n\n\n<h2 id=\"what-are-the-most-common-csp-configuration-errors\" class=\"wp-block-heading\">What are the Most Common CSP Configuration Errors?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Implementing a CSP is not enough to make an application resistant to XSS. A policy may be syntactically valid and correctly interpreted by the browser, yet still establish an overly permissive trust model. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The most common errors rarely stem from a single keyword: they often result from a series of compromises, introduced to ensure that legacy applications continue to function or to integrate new third-party services.<\/p>\n\n\n\n<h3 id=\"excessive-reliance-on-unsafe-inline\" class=\"wp-block-heading\">Excessive reliance on unsafe-inline<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The following configuration is still common in applications that use numerous inline scripts or event handlers declared directly within the HTML:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy:\n    script-src 'self' 'unsafe-inline'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This policy restricts external scripts to the current origin, but it re-enables a mechanism that accounts for a significant proportion of the classic XSS attack surface. An HTML injection can then become directly executable again if it allows the insertion of a JavaScript block or attribute that is compatible with the directive actually being applied.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Migrating to a strict CSP generally involves moving inline scripts to dedicated files or authorising them individually using nonces or hashes. Inline event handlers should be replaced by JavaScript listeners wherever possible.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">However, one should avoid automatically flagging every occurrence of <code>\u2018unsafe-inline\u2019<\/code> as a critical vulnerability. With correctly used nonces or hashes \u2013 and even more so in a CSP3 policy with <code>strict-dynamic<\/code> \u2013 the value may be ignored by modern browsers whilst serving as a fallback for older clients. The analysis must focus on the actual behaviour.<\/p>\n\n\n\n<h3 id=\"excessive-reliance-on-unsafe-eval\" class=\"wp-block-heading\">Excessive reliance on unsafe-eval<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><code>'unsafe-eval'<\/code> is often introduced because a legacy framework, a template engine, a development tool or a dependency still uses <code>eval()<\/code>, <code>Function()<\/code> or a similar primitive.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy:\n    script-src 'self' 'unsafe-eval'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The problem lies less in the presence of the keyword than in the operations that the code is subsequently able to perform. If user-controlled data reaches a dynamic evaluation function, CSP will no longer block execution on the basis of this restriction.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Teams should therefore treat <code>\u2018unsafe-eval\u2019<\/code> as an exception requiring justification. A dependency that uses it only during development should not impose this value on the production policy. Where there is a need for WebAssembly without the need for general JavaScript evaluation, <code>\u2018wasm-unsafe-eval\u2019<\/code> allows for finer-grained control.<\/p>\n\n\n\n<h3 id=\"wildcards-and-overly-broad-domain-allowlists\" class=\"wp-block-heading\">Wildcards and overly broad domain allowlists<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">A policy such as:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy:\n    script-src \u201cself\u201d\n        https:&#47;&#47;www.googletagmanager.com\n        https:\/\/cdn.jsdelivr.net\n        https:\/\/cdnjs.cloudflare.com\n        https:\/\/*.marketing.example<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">may seem reasonable because it does not directly authorise just any domain. However, every source added becomes part of the application\u2019s trust boundary.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The issue is particularly significant for shared domains, platforms that host user-generated content, services capable of dynamically loading other scripts, and subdomains covered by wildcards. A tag manager is, by its very nature, sensitive: its function is to trigger or load additional code. Allowing it in <code>script-src<\/code> is therefore tantamount to delegating part of the execution decision to that service and its configuration.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A robust allowlist must be kept to a minimum, regularly reassessed and built based on an understanding of the features actually available on each source, not merely on the domain\u2019s reputation.<\/p>\n\n\n\n<h3 id=\"excessive-confidence-in-self\" class=\"wp-block-heading\">Excessive confidence in \u2018self\u2019<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><code>'self'<\/code> simplifies policies, but assumes that the application\u2019s origin constitutes a consistent trust boundary.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This assumption becomes tenuous when the same origin serves files uploaded by users, content generated from untrusted inputs, or resources provided by multiple applications with different levels of trust.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy:\n    script-src 'self'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">does not protect against a resource controlled by an attacker if that resource can be served from a URL such as:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>https:&#47;&#47;app.example.com\/uploads\/user-controlled.js<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">and loaded in a JavaScript-compatible context. The best architectural defence is to isolate user-generated content on an origin that is not trusted by the main application\u2019s policy.<\/p>\n\n\n\n<h3 id=\"incomplete-policy\" class=\"wp-block-heading\">Incomplete policy<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Some policies define only <code>script-src<\/code> and assume that the rest of the application is covered:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy:\n    script-src 'self'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This configuration provides no fallback for the other retrieval directives. A basic configuration such as:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy:\n    default-src 'none';\n    script-src 'self';\n    img-src 'self';\n    style-src 'self'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">makes the intentions much clearer.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">However, it is important to distinguish between directives that inherit from <code>default-src<\/code> and those that must be defined separately. A policy may be very restrictive regarding resources whilst omitting <code>base-uri<\/code>, <code>form-action<\/code> or <code>frame-ancestors<\/code>, leaving open possibilities for abuse that do not necessarily rely on the execution of JavaScript.<\/p>\n\n\n\n<h3 id=\"explicit-absence-of-object-src-and-base-uri\" class=\"wp-block-heading\">Explicit absence of object-src and base-uri<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">In a modern application, <code>object-src \u2018none\u2019<\/code> and <code>base-uri \u2018none\u2019<\/code> are often good default choices when the corresponding features are not in use.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The absence of <code>object-src<\/code> must be interpreted with caution: if <code>default-src \u2018none\u2019<\/code> is already defined, objects remain blocked by the fallback mechanism. The main benefit of an explicit directive is to formalise and document the security decision.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>base-uri<\/code>, on the other hand, is independent of default-src. Omitting it may allow an HTML injection to influence the resolution of relative URLs if the application contains relevant references and if the rest of the CSP allows this to be exploited.<\/p>\n\n\n\n<h3 id=\"static-predictable-or-incorrectly-assigned-nonces\" class=\"wp-block-heading\">Static, predictable or incorrectly assigned nonces<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">A correctly generated nonce can provide a very robust foundation. However, weak implementations negate much of its benefit.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A static value:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy:\n    script-src 'nonce-production123'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">or a value derived from a predictable timestamp does not possess the properties expected of a \u2018number used once\u2019.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">But reuse is not the only problem. A nonce may be random and renewed with every response, yet still be mistakenly assigned to content that can be manipulated by a user. For example, a rendering engine that systematically adds the nonce to all <code>&lt;script&gt;<\/code> elements of a controllable HTML component may mark the script injected by the attacker as trustworthy.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The review must therefore cover the generation, renewal, any storage and, above all, the logic governing the insertion of the nonce into the document.<\/p>\n\n\n\n<h3 id=\"csp-deployed-in-report-only-mode-only\" class=\"wp-block-heading\">CSP deployed in Report-Only mode only<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Sometimes a scanner or a quick review may detect the presence of a CSP header, even though it is solely:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy-Report-Only: ...<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This policy does not prevent any actions under CSP. It is useful for preparing a deployment and detecting violations, but must not be presented as active protection when no enforcement policy exists.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Another common situation is having a strict <code>Report-Only<\/code> policy alongside an older, permissive policy that is currently in force. The pentester must always distinguish between the two and assess the actual level of protection, not the target configuration envisaged by the team.<\/p>\n\n\n\n<h3 id=\"inconsistent-deployment-depending-on-the-routes\" class=\"wp-block-heading\">Inconsistent deployment depending on the routes<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">An application may enforce a strict policy on <code>\/<\/code>, <code>\/account<\/code> or <code>\/admin<\/code>, but overlook error pages, legacy routes, support pages or a sub-application built using a different stack.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This inconsistency becomes particularly significant if an injection vulnerability exists precisely on one of the pages lacking a policy or protected by a different CSP.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A CSP audit must therefore cover a representative sample of routes and authentication contexts. The presence of a correct header on the home page does not prove that the entire application shares the same protection.<\/p>\n\n\n\n<h3 id=\"compromise-on-compatibility-with-older-browsers\" class=\"wp-block-heading\">Compromise on compatibility with older browsers<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Modern policies can combine several expressions to provide a fallback for older browsers. A migration policy might, for example, contain:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy:\n    script-src 'unsafe-inline' https: 'nonce-RANDOM' 'strict-dynamic'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">In a CSP3 browser, the effective model is based on the nonce and <code>strict-dynamic<\/code>, whilst an older browser may interpret only part of the policy and end up with a more permissive model.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This difference is not necessarily a vulnerability if older browsers are no longer supported or if the risk has been accepted. However, it must be understood and documented. A policy must be assessed based on the clients that are actually exposed and the behaviours they implement.<\/p>\n\n\n\n<h2 id=\"common-techniques-for-bypassing-csps\" class=\"wp-block-heading\">Common Techniques for Bypassing Content Security Policy<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A CSP bypass should not be confused with an XSS vulnerability. In most scenarios, the attacker already has a primitive at their disposal: HTML injection, DOM XSS, control over an attribute, file upload or the ability to influence data manipulated by JavaScript. CSP prevents the most direct exploitation of this primitive; bypassing it therefore involves identifying a method that remains compatible with the trust model defined by the policy.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">An analysis of a bypass must therefore answer four questions. What is the initial primitive? Which operation does the CSP block? Which resource or behaviour is still considered trustworthy? How does the initial primitive allow this trust to be circumvented?<\/p>\n\n\n\n<h3 id=\"exploiting-a-policy-allowing-unsafe-inline\" class=\"wp-block-heading\">Exploiting a policy allowing unsafe-inline<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Let us consider an application that returns:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>HTTP\/1.1 200 OK\nContent-Security-Policy:\n    default-src 'self';\n    script-src 'self' 'unsafe-inline'\nContent-Type: text\/html<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">A search feature re-injects the requested term into the response without correct context-sensitive encoding:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;p&gt;Results for: USER_INPUT&lt;\/p&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The attacker observes that they can inject HTML. With a <code>script-src \u2018self\u2019<\/code> policy and no other exceptions, an inline event handler would normally be blocked. Here, the presence of <code>\u2018unsafe-inline\u2019<\/code> may re-enable this primitive.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A demonstration payload such as:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;img src=\"invalid\" onerror=\"alert(document.domain)\"&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">can then be executed if <code>script-src-attr<\/code> or another more specific mechanism does not prohibit it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The browser has not been bypassed in the sense of a vulnerability in its CSP engine. It is applying the policy that explicitly instructs it to allow this behaviour. The weakness stems from the fact that the defence-in-depth mechanism intended to limit injection has been neutralised by an exception that is too broad.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The fix involves removing the reliance on inline scripts and event handlers, using nonces or hashes for legitimate blocks, and, above all, correcting the injection at its source. Removing <code>\u2018unsafe-inline\u2019<\/code> does not make unencoded HTML rendering acceptable.<\/p>\n\n\n\n<h3 id=\"trusted-third-party-domain-and-jsonp-endpoint\" class=\"wp-block-heading\">Trusted third-party domain and JSONP endpoint<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Historical CSPs are often based on a domain allowlist:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy:\n    default-src 'self';\n    script-src 'self' https:\/\/api.example.test<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">An attempt to load directly from <code>https:\/\/attacker.example\/payload.js<\/code> is blocked. The auditor then examines what <code>api.example.test<\/code>, which is already authorised, can actually serve.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Suppose this domain still exposes a JSONP endpoint:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>GET \/legacy\/search?callback=displayResult HTTP\/1.1\nHost: api.example.test<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">with a response:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>displayResult({\"result\":\"example\"});<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">JSONP was designed to produce JavaScript that can be loaded into a <code>&lt;script&gt;<\/code> element. If the callback parameter is insufficiently validated, the user can manipulate the structure of the returned code.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If the main application also has an HTML injection vulnerability that allows the insertion of a <code>&lt;script src=\"...\"&gt;<\/code> element, the auditor can test whether the authorised JSONP endpoint can be hijacked to produce controlled JavaScript.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The chain of events is as follows: the injection allows a script tag to be added; the CSP blocks an origin controlled by the attacker; the JSONP domain is already approved; the endpoint for this domain generates JavaScript from a user parameter; the response is therefore loaded from a source that the browser considers legitimate.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">JSONP is not inherently a CSP bypass. The problem arises because an approved source is itself capable of transforming user input into an executable resource. Remediation involves removing legacy JSONP endpoints where possible, reducing allowlists, and prioritising a Strict CSP based on nonces or hashes rather than on domain reputation alone.<\/p>\n\n\n\n<h3 id=\"bypassing-the-self-script-src-restriction-via-an-upload\" class=\"wp-block-heading\">Bypassing the \u2018self\u2019 script-src restriction via an upload<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Let us consider:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy:\n    default-src 'self';\n    script-src 'self'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This policy prevents scripts from being loaded from an external origin. However, the application allows users to upload files, which are then accessible at:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>https:&#47;&#47;app.example.test\/uploads\/7f94ab\/file.js<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If an attacker can control the content of this file and the server serves it in a manner compatible with active loading, an HTML injection could use:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;script src=\"\/uploads\/7f94ab\/file.js\"&gt;&lt;\/script&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The resource belongs to the same origin as the application. From the perspective of <code>script-src \u2018self\u2019<\/code>, it therefore satisfies the trust model.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">However, one must avoid treating every upload as exploitable. The server may enforce an incompatible Content-Type, add <code>X-Content-Type-Options: nosniff<\/code>, force <code>Content-Disposition: attachment<\/code>, transform the file, or serve it from a different origin. The auditor must verify the actual behaviour of the browser and the response.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The most robust defence is to isolate user-generated content on an origin that lies outside the main application\u2019s trust domain, and then to supplement this separation with correct MIME types, `nosniff`, appropriate download policies and content validation.<\/p>\n\n\n\n<h3 id=\"bypassing-a-weak-or-misused-nonce\" class=\"wp-block-heading\">Bypassing a weak or misused nonce<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">A policy based on a nonce may initially appear robust:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy:\n    script-src 'nonce-static123'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If the same value is reused in all responses, an attacker who knows it could potentially use it in an injection that is compatible with the application\u2019s rendering model.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Another implementation might generate:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const nonce = Date.now().toString();<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The problem here is the predictability of the value, not the CSP syntax.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A more subtle case arises when the nonce is perfectly random but applied to content that can be manipulated. Let\u2019s imagine a template component that automatically generates:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;script nonce=\"RANDOM\"&gt;\n    loadWidget(\"USER_CONTROLLED_VALUE\");\n&lt;\/script&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If `<code>USER_CONTROLLED_VALUE<\/code>` is re-injected into a JavaScript context without appropriate encoding, the nonce specifically authorises the block containing the dangerous data. The attacker does not need to know or guess the value: the server has already marked the vulnerable script as trustworthy.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">An audit of nonces must therefore check their renewal and entropy, as well as the templates and components to which they are assigned. Remediation involves generating unique and cryptographically secure nonces, then ensuring that they are applied only to scripts whose content and parameters are under control.<\/p>\n\n\n\n<h3 id=\"strict-dynamic-and-vulnerable-script-loader\" class=\"wp-block-heading\">strict-dynamic and vulnerable script loader<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">A Strict CSP may take the following form:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy:\n    script-src 'nonce-K7Ew8VQvYpY5...' 'strict-dynamic';\n    object-src 'none';\n    base-uri 'none'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The page loads an authorised root script:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;script nonce=\"K7Ew8VQvYpY5...\" src=\"\/js\/loader.js\"&gt;&lt;\/script&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The nonce is refreshed with every response and cannot realistically be guessed. The auditor then analyses what `loader.js` does:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const params = new URLSearchParams(location.search);\nconst moduleUrl = params.get(\"module\");\n\nif (moduleUrl) {\n    const script = document.createElement(\"script\");\n    script.src = moduleUrl;\n    document.head.appendChild(script);\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The root script is legitimate and explicitly approved. With <code>strict-dynamic<\/code>, the scripts it creates programmatically can benefit from the trust propagated by this root. If `moduleUrl` accepts an arbitrary URL controlled by the user, the real weak point is the loader.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The attacker is therefore not trying to guess the nonce. They are trying to hijack a script that already has the browser\u2019s trust.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This situation encapsulates the shift in reasoning between an allowlist-based CSP and a Strict CSP. The question is no longer simply \u2018from which domains can I load JavaScript?\u2019, but \u2018which scripts are trusted roots, what capabilities do they possess, and what data can influence these capabilities?\u2019.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The fix does not involve removing `strict-dynamic`. The loader needs to be secured. Rather than accepting a URL directly, the code can resolve an internal identifier to a closed list of modules:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const modules = {\n    dashboard: \"\/js\/dashboard.js\",\n    profile: \"\/js\/profile.js\"\n};\n\nconst moduleUrl = modules&#91;userChoice];\n\nif (moduleUrl) {\n    const script = document.createElement(\"script\");\n    script.src = moduleUrl;\n    document.head.appendChild(script);\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The script remains capable of dynamically loading the necessary components, but the user no longer directly chooses the executable destination.<\/p>\n\n\n\n<h3 id=\"gadget-script-trusted-framework-and-dom-clobbering\" class=\"wp-block-heading\">Gadget script, trusted framework and DOM clobbering<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">A modern bypass does not always require loading a new script from a domain controlled by the attacker. Code that has already been authorised may itself provide the necessary primitive.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Consider a legitimate script that iterates through the DOM:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>document.querySelectorAll(\"&#91;data-module]\").forEach(element =&gt; {\n    const script = document.createElement(\"script\");\n    script.src = element.dataset.module;\n    document.head.appendChild(script);\n});<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This component is authorised by a nonce. Meanwhile, an HTML injection allows a user to insert:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;div data-module=\"USER_CONTROLLED_VALUE\"&gt;&lt;\/div&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The injected data contains no inline JavaScript. It is the trusted code itself that reads the DOM and converts the attribute into a script URL. If the value can point to an unintended resource, the script becomes an exploitable gadget.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Older AngularJS applications provided particularly well-known historical examples of this principle: injected content or expressions could be interpreted by a framework that was already authorised, serving as a reminder that a policy cannot assume all behaviour of a trusted script to be automatically safe.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/hackagora.com\/en\/mutation-xss-mxss-and-dom-clobbering-exploitations-and-security-best-practices\/#dom-clobbering-hijacking-javascript-via-html-injection\" target=\"_blank\" rel=\"noopener\">DOM clobbering<\/a> is based on a similar idea. By injecting elements with certain `<code>id<\/code>` or `<code>name<\/code>` attributes, an attacker can sometimes influence the resolution of properties that legitimate code treats as internal objects or values. If this value ends up in a sensitive sink, a seemingly limited HTML primitive can become part of an exploitation chain.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The remediation is to prevent trusted code from directly converting values derived from the DOM into sensitive resources or operations. References must be validated, resolved to internal identifiers and decoupled as far as possible from user-controllable content.<\/p>\n\n\n\n<h3 id=\"html-injection-without-javascript-execution-but-with-an-impact-via-base-uri-or-form-action\" class=\"wp-block-heading\">HTML injection without JavaScript execution, but with an impact via base-uri or form-action<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Not all vulnerabilities resulting from an incomplete CSP should be classified as JavaScript bypasses.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Let\u2019s imagine an HTML injection on a page protected by a robust <code>script-src<\/code>. The injected scripts are correctly blocked. However, the policy contains neither <code>base-uri<\/code> nor <code>form-action<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If the attacker can inject a tag:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;base href=\"https:\/\/attacker.example\/\"&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">and the page subsequently contains relative URLs whose destinations are compatible with the rest of the policy, the attacker can influence their resolution. Similarly, the injection or modification of a form may become relevant if no form-action restriction prevents a submission to another origin.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">These scenarios do not necessarily demonstrate JavaScript execution despite CSP. They highlight another significant limitation: an HTML injection retains security implications even when <code>script-src<\/code> fulfils its role correctly. An analysis of CSP must therefore not be reduced to the question \u2018can I trigger `<code>alert(1)?<\/code>\u2019.<\/p>\n\n\n\n<h2 id=\"dom-xss-csp-and-trusted-types-understanding-the-differences-between-these-mechanisms\" class=\"wp-block-heading\">DOM XSS, CSP and Trusted Types: Understanding the Differences Between these Mechanisms<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A DOM XSS does not automatically constitute a CSP bypass. Let&#8217;s consider:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>output.innerHTML = location.hash.substring(1);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This statement creates a dangerous sink. A strict CSP may, however, prevent certain forms of JavaScript execution triggered by the injected HTML.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The auditor must therefore analyse what happens after the injection: can the content modify a structure read by a trusted script? Influence a loader? Introduce an attribute interpreted by a framework? Trigger a navigation or a form? The mere presence of `<code>innerHTML<\/code>` establishes a DOM XSS attack surface, but not automatically a CSP bypass chain.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Trusted Types reinforces this separation by directly controlling certain sinks. When <code>require-trusted-types-for 'script'<\/code> is active, an assignment of a raw string to `<code>innerHTML<\/code>` may be rejected. The analysis then shifts to the Trusted Types policies and the way in which they construct trusted values.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A policy that actually sanitises the input reduces the attack surface. Conversely, a policy that returns the value as-is creates a new vulnerability. Trusted Types therefore primarily helps to make sensitive conversions explicit and auditable; it does not replace proper sanitisation logic.<\/p>\n\n\n\n<h2 id=\"penetration-testing-methodology-of-a-content-security-policy\" class=\"wp-block-heading\">Penetration Testing Methodology of a Content Security Policy<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Auditing a CSP does not simply involve copying its value into an automated tool and searching for a few keywords deemed to be dangerous. A thorough analysis seeks to reconstruct the policy actually enforced by the browser, to identify the resources and scripts that constitute the trust boundary, and then to determine whether data controlled by an attacker can reach any of these authorised paths.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A CSP may be open to improvement without being exploitable in the context of the application. Conversely, a policy that appears very strict on the face of it may be circumventable due to specific behaviour in the code. The methodology must therefore start with the raw configuration and progress to a conclusion of demonstrated exploitability.<\/p>\n\n\n\n<h3 id=\"aioseo-compile-the-policies-on-the-relevant-pages\" class=\"wp-block-heading\">Compile the policies on the relevant pages<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The first mistake would be to analyse only the homepage. An application may return different policies depending on the route, the authentication status, the response type or the infrastructure component that generated the page.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">An auditor therefore collects headers from the main areas of the application: public pages, authentication, user area, administrative areas, support pages, upload features, error pages and any legacy sub-applications.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A response may, for example, contain:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>GET \/account HTTP\/1.1\nHost: app.example.test\nCookie: session=...<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>HTTP\/1.1 200 OK\nContent-Security-Policy:\n    default-src 'none';\n    script-src 'nonce-Q7jJ8...' 'strict-dynamic';\n    style-src 'self';\n    img-src 'self';\n    object-src 'none';\n    base-uri 'none';\n    form-action 'self'\nContent-Type: text\/html<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The same checks must be repeated on pages generated by other components. An error route originating from a reverse proxy or an old page served by a separate backend may have a much more permissive policy.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>&lt;meta http-equiv=\u2018Content-Security-Policy\u2019><\/code> tag must also be checked where it is used. Its position in the document and its limitations must be taken into account.<\/p>\n\n\n\n<h3 id=\"aioseo-understanding-enforcement-and-report-only-policies\" class=\"wp-block-heading\">Understanding enforcement and \u2018report-only\u2019 policies<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The auditor identifies Content-Security-Policy: &#8230; and Content-Security-Policy-Report-Only: &#8230; separately.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A common scenario is to find an older, permissive policy in enforcement mode, whilst a much stricter policy is being tested in Report-Only mode:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy:\n    script-src 'self' 'unsafe-inline'\n\nContent-Security-Policy-Report-Only:\n    script-src 'nonce-RANDOM' 'strict-dynamic';\n    object-src 'none';\n    base-uri 'none'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The second policy likely describes the migration target, but it does not yet protect the page. The level of security must be assessed based on the policies that are actually enforced.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This distinction is also useful for understanding the deployment cycle. An application that remains for months with a CSP set exclusively to \u2018Report-Only\u2019 has instrumentation in place, not an active barrier.<\/p>\n\n\n\n<h3 id=\"aioseo-rebuilding-the-effective-policy-and-fallbacks\" class=\"wp-block-heading\">Rebuilding the effective policy and fallbacks<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">A policy must be interpreted directive by directive. The auditor identifies the values actually used for scripts, styles, images, connections, workers, frames and other capabilities.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For a <code>&lt;script><\/code> element, one must examine<code> script-src-elem<\/code>, <code>script-src<\/code> and then <code>default-src<\/code>, depending on the directives present. For an inline event handler, <code>script-src-attr<\/code> may take precedence. For workers, the fallback chain may go via <code>worker-src<\/code>, <code>child-src<\/code>, script-src and then <code>default-src<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Conversely, <code>base-uri<\/code>, <code>form-action<\/code> and <code>frame-ancestors<\/code> must be examined independently because they do not automatically fall back to <code>default-src<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">When multiple CSP headers are present, each policy in enforcement mode must be satisfied. The auditor must therefore not merge the allowlists to form a policy that is more permissive than the one actually applied.<\/p>\n\n\n\n<h3 id=\"aioseo-identify-the-script-src-trust-model\" class=\"wp-block-heading\">Identify the script-src trust model<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Once the policy has been understood, the auditor determines how scripts are authorised.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A policy such as:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>script-src 'self' https:\/\/cdn.example.test https:\/\/vendor.example.test<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">relies primarily on an allowlist of origins.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A policy such as:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>script-src 'nonce-RANDOM'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">trusts individually marked scripts.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A policy such as:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>script-src 'nonce-RANDOM' 'strict-dynamic'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">establishes roots of trust capable of propagating that trust to certain dynamically generated scripts.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Finally, a static application may use hashes, possibly combined with <code>strict-dynamic<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The type of policy determines the next steps in the audit. In an allowlist, the focus is on authorised sources. With nonces, the generation and assignment must be analysed. With <code>strict-dynamic<\/code>, root scripts and their loaders become the priority.<\/p>\n\n\n\n<h3 id=\"aioseo-search-for-exceptions-that-affect-execution-options\" class=\"wp-block-heading\">Search for exceptions that affect execution options<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The auditor then examines the settings that may reintroduce dangerous primitives.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>\u2018unsafe-inline\u2019<\/code> must be interpreted in conjunction with nonces, hashes, strict-dynamic and script-src-attr. The mere presence of the keyword is not sufficient to draw a conclusion.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>'unsafe-eval'<\/code> warrants particular attention because it re-enables the evaluation of code from strings. The JavaScript code must therefore be checked for uses of <code>eval()<\/code>, <code>Function()<\/code>, some timer calls constructed using a string, and library APIs capable of interpreting expressions.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>'wasm-unsafe-eval'<\/code> must be distinguished from <code>\u2018unsafe-eval\u2019<\/code> in order to understand whether the application simply requires WebAssembly compilation or whether it has re-enabled a much broader evaluation surface.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>\u2018unsafe-hashes\u2019<\/code> may indicate a migration that retains certain event handlers inline. It is therefore necessary to examine the handlers that are actually permitted and determine whether their behaviour can be manipulated.<\/p>\n\n\n\n<h3 id=\"aioseo-testing-the-generation-and-use-of-nonces\" class=\"wp-block-heading\">Testing the generation and use of nonces<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">When a nonce is used, the auditor makes several independent requests and compares the values.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Response 1:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>script-src 'nonce-xF7QW2mJ0kY9...'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Response 2:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>script-src 'nonce-By9P1tAZ3M6c...'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Response 3:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>script-src 'nonce-Lh4K8dQw2NzR...'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Correct behaviour should show a new nonce in every response, with values that have no apparent predictable pattern. Consistent reuse or a sequence based on a counter, timestamp or short identifier is a red flag.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">However, the analysis must not stop at the values themselves. The HTML must be inspected to identify the scripts that carry the nonce. In particular, the auditor should look for blocks whose content or parameters include user data, components that dynamically create scripts, and templates that may copy the nonce onto a partially controlled element.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Where a code review is possible, the generation function must be verified to confirm the use of a cryptographically secure source and the absence of reuse between responses.<\/p>\n\n\n\n<h3 id=\"aioseo-analyse-the-hashes-and-the-scripts-they-authorise\" class=\"wp-block-heading\">Analyse the hashes and the scripts they authorise<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">A CSP may contain:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>script-src 'sha256-YWJjZGVm...' 'strict-dynamic'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The auditor then identifies which block or file matches the fingerprint. The aim is to understand the role of the authorised script: does it initialise the application, load a bundle, dynamically create other scripts, or read parameters from the URL or the DOM?<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The hash ensures that the approved content matches a specific fingerprint. It does not guarantee that this content is free from exploitable behaviour. A script that is perfectly authenticated by its hash can still become a gadget if it transforms untrusted data into a sensitive operation.<\/p>\n\n\n\n<h3 id=\"aioseo-analyse-the-domains-allowed-by-the-allowlists\" class=\"wp-block-heading\">Analyse the domains allowed by the allowlists<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">In a host-based CSP, each source must be treated as an extension of the trust boundary.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The auditor searches these domains for features that could be used to serve manipulable content: JSONP endpoints, upload areas, multi-tenant CDNs, buckets, dynamic JavaScript generators, staging subdomains, tag management services or platforms where another client can publish a resource.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Wildcards require a broader scope of analysis. An expression such as <code>https:\/\/*.example.test<\/code> brings to light subdomains that have been overlooked, delegated or acquired via an external service.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">However, the analysis must remain contextual. The presence of a third-party domain in <code>script-src<\/code> is not automatically a vulnerability. It must be demonstrated that this domain can serve a controllable resource in a format and at a URL that actually satisfy the CSP source.<\/p>\n\n\n\n<h3 id=\"aioseo-analysing-paths-and-redirects-where-they-influence-trust\" class=\"wp-block-heading\">Analysing paths and redirects where they influence trust<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Some policies attempt to restrict trust to a single path:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>script-src https:\/\/cdn.example.test\/static\/js\/<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The auditor checks the semantics of the match and the behaviour of redirects. CSP has specific rules for paths, and the path component should not be regarded as a boundary as strict as a distinct origin, particularly when a redirect occurs.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You must also look for endpoints capable of redirecting from an authorised path to another resource. Depending on the scenario and the directive, the browser\u2019s actual behaviour must be validated rather than assumed.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This step is particularly important when the development team believes it has \u2018secured\u2019 a broad origin by authorising only a subdirectory.<\/p>\n\n\n\n<h3 id=\"aioseo-search-for-dynamically-loaded-scripts\" class=\"wp-block-heading\">Search for dynamically loaded scripts<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">This step becomes crucial with <code>strict-dynamic<\/code>. The auditor identifies root scripts containing a nonce or corresponding to a hash, then looks for the mechanisms that allow them to create other scripts.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Constructs such as <code>document.createElement(\u2018script\u2019)<\/code> and <code>script.src = value<\/code> are particularly important. The issue is not merely knowing that a script is created, but identifying the origin of value.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Logic such as:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const module = new URLSearchParams(location.search).get(\"module\");\nconst script = document.createElement(\"script\");\nscript.src = module;\ndocument.head.appendChild(script);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">constitutes a strong indicator if no strict validation is in place.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Module loaders, plugin managers, bundling systems and dynamic component libraries must be analysed in the same way. With a Strict CSP, this process of reviewing trusted code is often far more relevant than a conventional search for authorised external domains.<\/p>\n\n\n\n<h3 id=\"aioseo-search-for-script-gadgets-and-controllable-data-streams\" class=\"wp-block-heading\">Search for script gadgets and controllable data streams<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Beyond the obvious loaders, the pentester looks for scenarios where trusted code reads data from the environment and then uses it in a sensitive operation.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Common sources include <code>location.search<\/code>, <code>location.hash<\/code>, <code>postMessage<\/code>, <code>localStorage<\/code>, <code>sessionStorage<\/code>, <code>data-*<\/code> attributes, DOM fields or API responses re-injected on the client-side.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Data is not dangerous simply because it comes from <code>location.hash<\/code>. It becomes significant when it reaches a sink: the creation of a script, the generation of HTML, navigation, the selection of a template, a dynamic function call, or any other primitive capable of having a security impact.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This tracking involves starting from the source, examining the successive transformations, and then identifying the sink reached. It can be presented entirely in textual form: the data is read from the URL fragment, copied into a DOM attribute, and then consumed by an authorised script that uses this attribute as a module URL.<\/p>\n\n\n\n<h3 id=\"aioseo-mapping-dom-sinks\" class=\"wp-block-heading\">Mapping DOM sinks<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Operations capable of interpreting controlled content must be identified, in particular:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>element.innerHTML = value;\nelement.outerHTML = value;\nelement.insertAdjacentHTML(\"beforeend\", value);\ndocument.write(value);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Equivalent framework APIs must also be examined, such as mechanisms that explicitly allow the injection of raw HTML.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The aim is not to automatically classify every sink as a bypass. An assignment to innerHTML can create HTML without the injected JavaScript executing under a strict CSP. The pentester must continue the analysis: can the content influence a gadget script, modify an attribute read by a loader, create a useful navigation path or achieve another authorised capability?<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This distinction prevents confusion between a potential DOM XSS, an HTML injection and an actually demonstrated CSP bypass.<\/p>\n\n\n\n<h3 id=\"aioseo-check-trusted-types-when-enabled\" class=\"wp-block-heading\">Check Trusted Types when enabled<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">If the policy contains:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>require-trusted-types-for 'script'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">the auditor looks for calls to:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>trustedTypes.createPolicy(...)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">and examines the creation functions actually used, such as createHTML, createScript or createScriptURL.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A policy that passes data through a correctly configured sanitiser can significantly reduce the attack surface. A policy that simply returns the input:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>trustedTypes.createPolicy(\"appPolicy\", {\n    createHTML: input => input\n});<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">does not provide the same level of protection.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The `trusted-types` directive must also be examined to determine which policies can be created. The more the application restricts these creation points, the simpler the review becomes and the more difficult it is for a dependency to unexpectedly create a permissive policy.<\/p>\n\n\n\n<h3 id=\"aioseo-analysing-capabilities-other-than-javascript-execution\" class=\"wp-block-heading\">Analysing capabilities other than JavaScript execution<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">A CSP can prevent any injected JavaScript from executing whilst still allowing a significant impact to remain.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The auditor therefore examines <code>base-uri<\/code>, <code>form-action<\/code>, <code>frame-ancestors<\/code>, <code>img-src<\/code>, <code>connect-src<\/code>, <code>frame-src<\/code> and other relevant directives depending on the functionality.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">An HTML injection, for example, can alter a form even if scripts are blocked. If <code>form-action<\/code> is missing, it must be checked whether a submission to an external origin is possible. If <code>base-uri<\/code> is missing, the auditor can test the impact of a <code>&lt;base><\/code> tag on relative URLs. If <code>frame-ancestors<\/code> is not defined, resistance to clickjacking must be assessed separately.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This approach provides a more accurate picture of the impact of an injection than simply testing for an <code>alert(1)<\/code>.<\/p>\n\n\n\n<h3 id=\"aioseo-use-devtools-to-validate-assumptions\" class=\"wp-block-heading\">Use DevTools to validate assumptions<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Browser development tools are essential for monitoring the actual CSP. The console usually indicates which directive has blocked an operation, whilst the Network panel allows you to verify the headers received, any redirects, the resources actually loaded, and the responses from the test endpoints.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">When a payload is blocked, the browser\u2019s message often makes it possible to distinguish whether the block was caused by the <code>script-src-elem<\/code>, <code>script-src-attr<\/code>, <code>connect-src<\/code> or another directive. This information is particularly useful when multiple policies or fallbacks make interpreting the header less intuitive.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">However, the absence of an error message does not prove that a policy is secure. It merely means that the behaviour being tested did not result in any observable violation. The audit must continue to be guided by the trust model and the application code.<\/p>\n\n\n\n<h3 id=\"aioseo-use-csp-evaluator\" class=\"wp-block-heading\">Use CSP Evaluator<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/github.com\/google\/csp-evaluator\" target=\"_blank\" rel=\"noopener\">CSP Evaluator<\/a> can speed up the identification of known weak configurations, flag dangerous allowlists and provide an initial assessment of a policy.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">However, it is not familiar with all domains, all endpoints or the specific behaviours of application code. A policy may generate no major warnings whilst containing a vulnerable loader. Conversely, a configuration flagged as needing improvement may not lead to exploitation in a real-world context.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The tool should therefore be used to speed up the review process and not as a substitute for manual analysis.<\/p>\n\n\n\n<h3 id=\"aioseo-check-the-csp-reporting\" class=\"wp-block-heading\">Check the CSP reporting<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">When reporting is enabled, the auditor checks that the endpoint is correctly declared and that reports are actually being received.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A modern configuration might look like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Reporting-Endpoints:\n    csp-endpoint=\"https:\/\/reports.example.test\/csp\"\n\nContent-Security-Policy:\n    default-src 'self';\n    report-to csp-endpoint<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Depending on compatibility requirements, report-uri can be retained temporarily alongside this.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The collection endpoint must be protected against abuse. Reports are data received from clients, and certain properties may contain values controlled by an attacker. Systems that display or index them must apply the same principles of validation, encoding and volume limitation as for any other untrusted input.<\/p>\n\n\n\n<h2 id=\"how-to-build-a-robust-content-security-policy-csp\" class=\"wp-block-heading\">How to Build a Robust Content Security Policy (CSP)?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A good CSP should not be conceived as a list of domains accumulated as and when required. It must be based on the capabilities actually required by the application and minimise implicit trust as much as possible.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For modern applications, the most robust strategy is generally to use a strict policy for JavaScript, based on nonces or hashes, and then to explicitly define the other categories of resources. Whilst this approach is not always immediately applicable to legacy applications, it represents a sounder migration target than an allowlist that grows indefinitely.<\/p>\n\n\n\n<h3 id=\"start-with-the-actual-functional-requirements\" class=\"wp-block-heading\">Start with the actual functional requirements<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Before drafting the policy, the team must identify the resources actually used by the application: application scripts, APIs, images, fonts, workers, frames, media, forms and external integrations.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The principle of least privilege then involves denying access to anything that is not necessary and subsequently re-authorising the necessary capabilities. A starting point might be:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy:\n    default-src 'none'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This value alone does not constitute a complete policy. It simply forces the team to explicitly state the necessary resources rather than relying on implicit authorisations.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Directives that do not have a fallback to <code>default-src<\/code>, notably <code>base-uri<\/code>, <code>form-action<\/code> and <code>frame-ancestors<\/code>, must be added separately according to the expected behaviour.<\/p>\n\n\n\n<h3 id=\"prioritise-nonces-or-hashes-for-javascript\" class=\"wp-block-heading\">Prioritise nonces or hashes for JavaScript<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">A historical allowlist might take the following form:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>script-src 'self'\n    https:&#47;&#47;cdn1.example\n    https:\/\/cdn2.example\n    https:\/\/vendor.example<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This model delegates trust to several entire origins. A policy based on a nonce:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>script-src 'nonce-RANDOM'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">therefore authorises scripts marked by the application for the current response.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">When authorised scripts need to dynamically load other scripts, <code>strict-dynamic<\/code> can be used:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>script-src 'nonce-RANDOM' 'strict-dynamic'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This approach reduces reliance on third-party domains, but requires root scripts to be treated as security components. Their code must be carefully reviewed, particularly when they dynamically construct URLs or <code>&lt;script><\/code> elements.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For static applications, hashes may be a suitable alternative when inline scripts change infrequently.<\/p>\n\n\n\n<h3 id=\"explicitly-prohibit-inline-event-handlers-wherever-possible\" class=\"wp-block-heading\">Explicitly prohibit inline event handlers wherever possible<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">A modern policy can supplement <code>script-src<\/code> with:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>script-src-attr 'none'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This directive disables inline event handlers and facilitates a clear separation between HTML structure and JavaScript logic.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The code:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;button onclick=\"save()\">Save&lt;\/button><\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">can be replaced by:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>button.addEventListener(\"click\", save);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This migration also simplifies the use of nonces and reduces the need for compatibility flags such as <code>\u2018unsafe-inline\u2019<\/code> or <code>\u2018unsafe-hashes\u2019<\/code>.<\/p>\n\n\n\n<h3 id=\"phase-out-unsafe-inline-and-unsafe-eval\" class=\"wp-block-heading\">Phase out unsafe-inline and unsafe-eval<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Historical exceptions should be subject to a phasing-out plan rather than becoming permanent through inertia.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Legitimate inline scripts can be moved, associated with a nonce or covered by a hash. Inline event handlers can be replaced by listeners. Dependencies requiring <code>eval()<\/code> must be identified to determine whether this requirement is actually necessary in production.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The removal of <code>\u2018unsafe-eval\u2019<\/code> may require changes to the build process or framework. It is often more realistic to treat this as a migration project rather than abruptly blocking the application. The aim, however, remains to gradually reduce the number of mechanisms capable of transforming a string into executable code.<\/p>\n\n\n\n<h3 id=\"minimise-third-party-domains\" class=\"wp-block-heading\">Minimise third-party domains<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Every domain included in a sensitive directive must be justifiable.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For <code>script-src<\/code>, this requirement is particularly stringent. The team must understand whether the domain is dedicated to the organisation, whether it is shared, whether users can upload files to it, whether it loads additional scripts itself, and whether integration is necessary on all pages.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Tag management services, A\/B testing, advanced analytics and external widgets must be treated as components of the attack surface. Where they are necessary, their scope can sometimes be limited to certain pages or isolated in less privileged contexts.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The aim is not to block all third parties, but never to confuse a \u2018known provider\u2019 with a \u2018CSP source that is secure by definition\u2019.<\/p>\n\n\n\n<h3 id=\"isolate-user-controlled-content\" class=\"wp-block-heading\">Isolate user-controlled content<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Ideally, files uploaded by users should not share the same origin from which the application loads its trusted scripts.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A dedicated architecture, for example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>https:&#47;&#47;uploads.example-cdn.test\/<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">allows these resources to be removed from the scope covered by the main application\u2019s <code>\u2018self\u2019<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This separation must be complemented by consistent MIME types, <code>X-Content-Type-Options: nosniff<\/code>, appropriate download policies, content restrictions and, where necessary, antivirus or content transformation checks.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Origin isolation is a particularly robust measure as it simplifies the trust model: the browser can continue to treat the application origin as sensitive without automatically including all user-generated content.<\/p>\n\n\n\n<h3 id=\"use-base-uri-form-action-frame-ancestors-and-object-src-explicitly\" class=\"wp-block-heading\">Use base-uri, form-action, frame-ancestors and object-src explicitly<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">A modern policy should not focus exclusively on JavaScript.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">When the application does not use <code>&lt;base><\/code>: <code>base-uri `none`<\/code> is a straightforward choice.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">When forms must only be submitted to the application: <code>form-action `self`<\/code> limits the possible destinations.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If no framing is required: <code>frame-ancestors \u2018none\u2019<\/code>limits clickjacking.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Finally, for modern applications that do not use content controlled by <code>&lt;object><\/code> or <code>&lt;embed><\/code>: <code>object-src \u2018none\u2019<\/code>explicitly removes this attack surface.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">These directives make the policy more readable and prevent unnecessary capabilities from reappearing should there be a future change to <code>default-src<\/code>.<\/p>\n\n\n\n<h3 id=\"adopt-trusted-types-where-the-architecture-allows\" class=\"wp-block-heading\">Adopt Trusted Types where the architecture allows<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Trusted Types can complement a Strict CSP by reducing direct assignments of strings to certain sensitive DOM sinks.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A migration strategy might begin with monitoring to identify incompatible operations, then gradually enable the following:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>require-trusted-types-for 'script';\ntrusted-types appPolicy<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Legitimate operations are grouped behind audited policies that actually sanitise or validate inputs before producing trusted objects.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">One must resist the temptation to create a permissive \u2018default policy\u2019 that would accept everything simply to eliminate errors. A policy that blindly transforms a string into <code>TrustedHTML<\/code> or <code>TrustedScriptURL<\/code> significantly reduces the benefit of the mechanism.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Trusted Types is particularly useful in rich applications where it is difficult to immediately remove all dangerous DOM sinks. It allows the review to focus on a small number of explicit conversions.<\/p>\n\n\n\n<h3 id=\"roll-out-the-policy-gradually-using-report-only\" class=\"wp-block-heading\">Roll out the policy gradually using Report-Only<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">A strict CSP introduced abruptly to an existing application risks blocking legitimate functionality. Report-Only mode allows you to prepare for the migration without disrupting production.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A team can start by:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Content-Security-Policy-Report-Only:\n    default-src 'none';\n    script-src 'nonce-RANDOM' 'strict-dynamic';\n    object-src 'none';\n    base-uri 'none';\n    report-to csp-endpoint<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The violations observed help to identify overlooked dependencies and sections of code that require adaptation.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The policy must then be strengthened and migrated to Content-Security-Policy once its behaviour is sufficiently understood. A common mistake is to remain in Report-Only mode indefinitely: at this stage, CSP generates data but does not fulfil its enforcement role.<\/p>\n\n\n\n<h3 id=\"monitor-breaches-without-turning-reporting-into-noise\" class=\"wp-block-heading\">Monitor breaches without turning reporting into noise<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">CSP reports can reveal unexpected resources, regressions, new integrations, browser extensions that inject content, or certain exploitation attempts.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">However, they also generate noise. A reporting infrastructure must therefore aggregate, deduplicate and prioritise events rather than generate an alert for every individual violation.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Trends are often more useful than isolated events: the appearance of a new blocked domain following a deployment, a sudden increase in violations of a policy, the repetition of the same resource across several pages, or differing behaviour depending on the browser.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The data received should be treated as unreliable and handled accordingly within the monitoring interface.<\/p>\n\n\n\n<h3 id=\"ensuring-the-policy-is-maintained-over-time\" class=\"wp-block-heading\">Ensure the policy is maintained over time<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">A CSP that is effective on the day it is deployed may become permissive a few months later if each new integration adds an exception without removing the old ones.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The policy must therefore form part of the application\u2019s lifecycle. Periodic reviews must check which third-party domains are still required, nonces and hashes, recurring violations, directives that have become obsolete or unused, as well as changes to the trust code.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Significant front-end changes, framework migrations, new loaders, changes to CDNs and new upload features must trigger a review of the CSP trust model.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The same principle applies to supported browsers. Features such as Trusted Types or certain CSP3 directives are evolving in terms of compatibility; the policy must be designed with full knowledge of the actual client base and the chosen backward compatibility strategy.<\/p>\n\n\n\n<h2 id=\"conclusion\" class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Content Security Policy is one of the most powerful defence-in-depth mechanisms available on the browser side, but its effectiveness depends entirely on how trust is defined.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A long policy is not necessarily a robust one. A CSP based on a large allowlist may grant trust to many domains over which a team does not have complete control. Conversely, a relatively short policy based on correctly generated nonces, hashes or <code>strict-dynamic<\/code> can significantly reduce the execution surface.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For an attacker or auditor, analysing a CSP is therefore less about searching for a \u2018bad\u2019 keyword than about reconstructing the entire trust model. Which scripts are authorised? Why are they authorised? Can they load other scripts? What controllable data influences these loaders? Which domains are considered trustworthy? Can these domains host user-generated content? Are nonces correctly generated and assigned? Do gadget scripts allow a limited HTML injection to be turned into an executable operation? Are forms, frames, workers, uploads and basic URLs also properly controlled?<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It is by answering these questions that it becomes possible to determine whether a CSP provides a genuine security barrier or whether it merely makes exploiting an existing vulnerability slightly more difficult.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">On the defensive side, the aim must be to reduce implicit trust. This involves the gradual adoption of a Strict CSP, managing nonces and hashes, the correct use of <code>strict-dynamic<\/code>, minimising third-party dependencies, isolating untrusted content, removing legacy exceptions and, where compatible with the application, using Trusted Types to better control sensitive DOM sinks.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">CSP is no substitute for secure development or for preventing XSS at source. When properly designed, however, it provides an additional layer capable of transforming many potentially critical client-side scenarios into exploitation chains that are significantly more difficult, or even impossible, to carry out under the intended conditions.<\/p>\n\n\n\n<h2 id=\"aioseo-references\" class=\"wp-block-heading\">References<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>W3C, Content Security Policy Level 3 : https:\/\/www.w3.org\/TR\/CSP3\/<\/li>\n\n\n\n<li>MDN Web Docs, Content Security Policy (CSP) : https:\/\/developer.mozilla.org\/fr\/docs\/Web\/HTTP\/Guides\/CSP<\/li>\n\n\n\n<li>MDN Web Docs, r\u00e9f\u00e9rence de l\u2019en-t\u00eate Content-Security-Policy : https:\/\/developer.mozilla.org\/fr\/docs\/Web\/HTTP\/Reference\/Headers\/Content-Security-Policy<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Content Security Policy (CSP) is one of the main security mechanisms offered by browsers to limit the impact of client-side vulnerabilities. In theory, a well-designed CSP neutralises a large proportion of injection attacks; in practice, it is often weakened by compromises that have accumulated over time: exceptions added to maintain compatibility with legacy code, third-party [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":4029,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[19,24],"tags":[],"class_list":["post-4027","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-applications","category-guides"],"blocksy_meta":{"styles_descriptor":{"styles":{"desktop":"","tablet":"","mobile":""},"google_fonts":[],"version":8}},"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.2 - aioseo.com -->\n\t<meta name=\"description\" content=\"This article explores how CSP works, its key directives, the most common configuration errors, bypass techniques and security best practices to prevent the risk\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Eli T.\"\/>\n\t<link rel=\"canonical\" href=\"https:\/\/hackagora.com\/en\/content-security-policy-csp-how-it-works-bypass-techniques-and-security-best-practices\/\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.2\" \/>\n\t\t<meta property=\"og:locale\" content=\"en_US\" \/>\n\t\t<meta property=\"og:site_name\" content=\"HackAgora \u2013 Expose. Understand. Defend.\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"Content Security Policy (CSP) : Bypass Techniques and Prevention\" \/>\n\t\t<meta property=\"og:description\" content=\"This article explores how CSP works, its key directives, the most common configuration errors, bypass techniques and security best practices to prevent the risk\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/hackagora.com\/en\/content-security-policy-csp-how-it-works-bypass-techniques-and-security-best-practices\/\" \/>\n\t\t<meta property=\"og:image\" content=\"https:\/\/hackagora.com\/wp-content\/uploads\/2026\/06\/logo_hackagora_agora_color.svg\" \/>\n\t\t<meta property=\"og:image:secure_url\" content=\"https:\/\/hackagora.com\/wp-content\/uploads\/2026\/06\/logo_hackagora_agora_color.svg\" \/>\n\t\t<meta property=\"og:image:width\" content=\"313\" \/>\n\t\t<meta property=\"og:image:height\" content=\"122\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2026-09-07T13:28:16+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2026-09-21T17:30:43+00:00\" \/>\n\t\t<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n\t\t<meta name=\"twitter:title\" content=\"Content Security Policy (CSP) : Bypass Techniques and Prevention\" \/>\n\t\t<meta name=\"twitter:description\" content=\"This article explores how CSP works, its key directives, the most common configuration errors, bypass techniques and security best practices to prevent the risk\" \/>\n\t\t<meta name=\"twitter:image\" content=\"https:\/\/hackagora.com\/wp-content\/uploads\/2026\/06\/logo_hackagora_agora_color.svg\" \/>\n\t\t<script type=\"application\/ld+json\" class=\"aioseo-schema\">\n\t\t\t{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"BlogPosting\",\"@id\":\"https:\\\/\\\/hackagora.com\\\/en\\\/content-security-policy-csp-how-it-works-bypass-techniques-and-security-best-practices\\\/#blogposting\",\"name\":\"Content Security Policy (CSP) : Bypass Techniques and Prevention\",\"headline\":\"Content Security Policy (CSP) : How it Works, Bypass Techniques and Security Best Practices\",\"author\":{\"@id\":\"https:\\\/\\\/hackagora.com\\\/en\\\/author\\\/traorea_hg\\\/#author\"},\"publisher\":{\"@id\":\"https:\\\/\\\/hackagora.com\\\/en\\\/#organization\"},\"image\":{\"@type\":\"ImageObject\",\"url\":\"https:\\\/\\\/hackagora.com\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/csp-bypass-techniques.svg\",\"width\":885,\"height\":659,\"caption\":\"content security policy bypass techniques\"},\"datePublished\":\"2026-09-07T13:28:16+00:00\",\"dateModified\":\"2026-09-21T17:30:43+00:00\",\"inLanguage\":\"en-US\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/hackagora.com\\\/en\\\/content-security-policy-csp-how-it-works-bypass-techniques-and-security-best-practices\\\/#webpage\"},\"isPartOf\":{\"@id\":\"https:\\\/\\\/hackagora.com\\\/en\\\/content-security-policy-csp-how-it-works-bypass-techniques-and-security-best-practices\\\/#webpage\"},\"articleSection\":\"Applications, Guides, Facultatif\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/hackagora.com\\\/en\\\/content-security-policy-csp-how-it-works-bypass-techniques-and-security-best-practices\\\/#breadcrumblist\",\"itemListElement\":[{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/hackagora.com\\\/en\\\/#listItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/hackagora.com\\\/en\\\/\",\"nextItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/hackagora.com\\\/en\\\/category\\\/applications\\\/#listItem\",\"name\":\"Applications\"}},{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/hackagora.com\\\/en\\\/category\\\/applications\\\/#listItem\",\"position\":2,\"name\":\"Applications\",\"item\":\"https:\\\/\\\/hackagora.com\\\/en\\\/category\\\/applications\\\/\",\"nextItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/hackagora.com\\\/en\\\/content-security-policy-csp-how-it-works-bypass-techniques-and-security-best-practices\\\/#listItem\",\"name\":\"Content Security Policy (CSP) : How it Works, Bypass Techniques and Security Best Practices\"},\"previousItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/hackagora.com\\\/en\\\/#listItem\",\"name\":\"Home\"}},{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/hackagora.com\\\/en\\\/content-security-policy-csp-how-it-works-bypass-techniques-and-security-best-practices\\\/#listItem\",\"position\":3,\"name\":\"Content Security Policy (CSP) : How it Works, Bypass Techniques and Security Best Practices\",\"previousItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/hackagora.com\\\/en\\\/category\\\/applications\\\/#listItem\",\"name\":\"Applications\"}}]},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/hackagora.com\\\/en\\\/#organization\",\"name\":\"HackAgora\",\"description\":\"Expose. Understand. Defend.\",\"url\":\"https:\\\/\\\/hackagora.com\\\/en\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"url\":\"https:\\\/\\\/hackagora.com\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/logo_hackagora_agora_color.svg\",\"@id\":\"https:\\\/\\\/hackagora.com\\\/en\\\/content-security-policy-csp-how-it-works-bypass-techniques-and-security-best-practices\\\/#organizationLogo\",\"width\":313,\"height\":122},\"image\":{\"@id\":\"https:\\\/\\\/hackagora.com\\\/en\\\/content-security-policy-csp-how-it-works-bypass-techniques-and-security-best-practices\\\/#organizationLogo\"}},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/hackagora.com\\\/en\\\/author\\\/traorea_hg\\\/#author\",\"url\":\"https:\\\/\\\/hackagora.com\\\/en\\\/author\\\/traorea_hg\\\/\",\"name\":\"Eli T.\",\"image\":{\"@type\":\"ImageObject\",\"@id\":\"https:\\\/\\\/hackagora.com\\\/en\\\/content-security-policy-csp-how-it-works-bypass-techniques-and-security-best-practices\\\/#authorImage\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/748941d304278b11e857c7ae5582eefefe0689f1b0642e56a2eda7b98bae722b?s=96&d=mm&r=g\",\"width\":96,\"height\":96,\"caption\":\"Eli T.\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/hackagora.com\\\/en\\\/content-security-policy-csp-how-it-works-bypass-techniques-and-security-best-practices\\\/#webpage\",\"url\":\"https:\\\/\\\/hackagora.com\\\/en\\\/content-security-policy-csp-how-it-works-bypass-techniques-and-security-best-practices\\\/\",\"name\":\"Content Security Policy (CSP) : Bypass Techniques and Prevention\",\"description\":\"This article explores how CSP works, its key directives, the most common configuration errors, bypass techniques and security best practices to prevent the risk\",\"inLanguage\":\"en-US\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/hackagora.com\\\/en\\\/#website\"},\"breadcrumb\":{\"@id\":\"https:\\\/\\\/hackagora.com\\\/en\\\/content-security-policy-csp-how-it-works-bypass-techniques-and-security-best-practices\\\/#breadcrumblist\"},\"author\":{\"@id\":\"https:\\\/\\\/hackagora.com\\\/en\\\/author\\\/traorea_hg\\\/#author\"},\"creator\":{\"@id\":\"https:\\\/\\\/hackagora.com\\\/en\\\/author\\\/traorea_hg\\\/#author\"},\"image\":{\"@type\":\"ImageObject\",\"url\":\"https:\\\/\\\/hackagora.com\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/csp-bypass-techniques.svg\",\"@id\":\"https:\\\/\\\/hackagora.com\\\/en\\\/content-security-policy-csp-how-it-works-bypass-techniques-and-security-best-practices\\\/#mainImage\",\"width\":885,\"height\":659,\"caption\":\"content security policy bypass techniques\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/hackagora.com\\\/en\\\/content-security-policy-csp-how-it-works-bypass-techniques-and-security-best-practices\\\/#mainImage\"},\"datePublished\":\"2026-09-07T13:28:16+00:00\",\"dateModified\":\"2026-09-21T17:30:43+00:00\"},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/hackagora.com\\\/en\\\/#website\",\"url\":\"https:\\\/\\\/hackagora.com\\\/en\\\/\",\"name\":\"HackAgora\",\"description\":\"Expose. Understand. Defend.\",\"inLanguage\":\"en-US\",\"publisher\":{\"@id\":\"https:\\\/\\\/hackagora.com\\\/en\\\/#organization\"}}]}\n\t\t<\/script>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"Content Security Policy (CSP) : Bypass Techniques and Prevention","description":"This article explores how CSP works, its key directives, the most common configuration errors, bypass techniques and security best practices to prevent the risk","canonical_url":"https:\/\/hackagora.com\/en\/content-security-policy-csp-how-it-works-bypass-techniques-and-security-best-practices\/","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"BlogPosting","@id":"https:\/\/hackagora.com\/en\/content-security-policy-csp-how-it-works-bypass-techniques-and-security-best-practices\/#blogposting","name":"Content Security Policy (CSP) : Bypass Techniques and Prevention","headline":"Content Security Policy (CSP) : How it Works, Bypass Techniques and Security Best Practices","author":{"@id":"https:\/\/hackagora.com\/en\/author\/traorea_hg\/#author"},"publisher":{"@id":"https:\/\/hackagora.com\/en\/#organization"},"image":{"@type":"ImageObject","url":"https:\/\/hackagora.com\/wp-content\/uploads\/2026\/09\/csp-bypass-techniques.svg","width":885,"height":659,"caption":"content security policy bypass techniques"},"datePublished":"2026-09-07T13:28:16+00:00","dateModified":"2026-09-21T17:30:43+00:00","inLanguage":"en-US","mainEntityOfPage":{"@id":"https:\/\/hackagora.com\/en\/content-security-policy-csp-how-it-works-bypass-techniques-and-security-best-practices\/#webpage"},"isPartOf":{"@id":"https:\/\/hackagora.com\/en\/content-security-policy-csp-how-it-works-bypass-techniques-and-security-best-practices\/#webpage"},"articleSection":"Applications, Guides, Facultatif"},{"@type":"BreadcrumbList","@id":"https:\/\/hackagora.com\/en\/content-security-policy-csp-how-it-works-bypass-techniques-and-security-best-practices\/#breadcrumblist","itemListElement":[{"@type":"ListItem","@id":"https:\/\/hackagora.com\/en\/#listItem","position":1,"name":"Home","item":"https:\/\/hackagora.com\/en\/","nextItem":{"@type":"ListItem","@id":"https:\/\/hackagora.com\/en\/category\/applications\/#listItem","name":"Applications"}},{"@type":"ListItem","@id":"https:\/\/hackagora.com\/en\/category\/applications\/#listItem","position":2,"name":"Applications","item":"https:\/\/hackagora.com\/en\/category\/applications\/","nextItem":{"@type":"ListItem","@id":"https:\/\/hackagora.com\/en\/content-security-policy-csp-how-it-works-bypass-techniques-and-security-best-practices\/#listItem","name":"Content Security Policy (CSP) : How it Works, Bypass Techniques and Security Best Practices"},"previousItem":{"@type":"ListItem","@id":"https:\/\/hackagora.com\/en\/#listItem","name":"Home"}},{"@type":"ListItem","@id":"https:\/\/hackagora.com\/en\/content-security-policy-csp-how-it-works-bypass-techniques-and-security-best-practices\/#listItem","position":3,"name":"Content Security Policy (CSP) : How it Works, Bypass Techniques and Security Best Practices","previousItem":{"@type":"ListItem","@id":"https:\/\/hackagora.com\/en\/category\/applications\/#listItem","name":"Applications"}}]},{"@type":"Organization","@id":"https:\/\/hackagora.com\/en\/#organization","name":"HackAgora","description":"Expose. Understand. Defend.","url":"https:\/\/hackagora.com\/en\/","logo":{"@type":"ImageObject","url":"https:\/\/hackagora.com\/wp-content\/uploads\/2026\/06\/logo_hackagora_agora_color.svg","@id":"https:\/\/hackagora.com\/en\/content-security-policy-csp-how-it-works-bypass-techniques-and-security-best-practices\/#organizationLogo","width":313,"height":122},"image":{"@id":"https:\/\/hackagora.com\/en\/content-security-policy-csp-how-it-works-bypass-techniques-and-security-best-practices\/#organizationLogo"}},{"@type":"Person","@id":"https:\/\/hackagora.com\/en\/author\/traorea_hg\/#author","url":"https:\/\/hackagora.com\/en\/author\/traorea_hg\/","name":"Eli T.","image":{"@type":"ImageObject","@id":"https:\/\/hackagora.com\/en\/content-security-policy-csp-how-it-works-bypass-techniques-and-security-best-practices\/#authorImage","url":"https:\/\/secure.gravatar.com\/avatar\/748941d304278b11e857c7ae5582eefefe0689f1b0642e56a2eda7b98bae722b?s=96&d=mm&r=g","width":96,"height":96,"caption":"Eli T."}},{"@type":"WebPage","@id":"https:\/\/hackagora.com\/en\/content-security-policy-csp-how-it-works-bypass-techniques-and-security-best-practices\/#webpage","url":"https:\/\/hackagora.com\/en\/content-security-policy-csp-how-it-works-bypass-techniques-and-security-best-practices\/","name":"Content Security Policy (CSP) : Bypass Techniques and Prevention","description":"This article explores how CSP works, its key directives, the most common configuration errors, bypass techniques and security best practices to prevent the risk","inLanguage":"en-US","isPartOf":{"@id":"https:\/\/hackagora.com\/en\/#website"},"breadcrumb":{"@id":"https:\/\/hackagora.com\/en\/content-security-policy-csp-how-it-works-bypass-techniques-and-security-best-practices\/#breadcrumblist"},"author":{"@id":"https:\/\/hackagora.com\/en\/author\/traorea_hg\/#author"},"creator":{"@id":"https:\/\/hackagora.com\/en\/author\/traorea_hg\/#author"},"image":{"@type":"ImageObject","url":"https:\/\/hackagora.com\/wp-content\/uploads\/2026\/09\/csp-bypass-techniques.svg","@id":"https:\/\/hackagora.com\/en\/content-security-policy-csp-how-it-works-bypass-techniques-and-security-best-practices\/#mainImage","width":885,"height":659,"caption":"content security policy bypass techniques"},"primaryImageOfPage":{"@id":"https:\/\/hackagora.com\/en\/content-security-policy-csp-how-it-works-bypass-techniques-and-security-best-practices\/#mainImage"},"datePublished":"2026-09-07T13:28:16+00:00","dateModified":"2026-09-21T17:30:43+00:00"},{"@type":"WebSite","@id":"https:\/\/hackagora.com\/en\/#website","url":"https:\/\/hackagora.com\/en\/","name":"HackAgora","description":"Expose. Understand. Defend.","inLanguage":"en-US","publisher":{"@id":"https:\/\/hackagora.com\/en\/#organization"}}]},"og:locale":"en_US","og:site_name":"HackAgora \u2013 Expose. Understand. Defend.","og:type":"article","og:title":"Content Security Policy (CSP) : Bypass Techniques and Prevention","og:description":"This article explores how CSP works, its key directives, the most common configuration errors, bypass techniques and security best practices to prevent the risk","og:url":"https:\/\/hackagora.com\/en\/content-security-policy-csp-how-it-works-bypass-techniques-and-security-best-practices\/","og:image":"https:\/\/hackagora.com\/wp-content\/uploads\/2026\/06\/logo_hackagora_agora_color.svg","og:image:secure_url":"https:\/\/hackagora.com\/wp-content\/uploads\/2026\/06\/logo_hackagora_agora_color.svg","og:image:width":313,"og:image:height":122,"article:published_time":"2026-09-07T13:28:16+00:00","article:modified_time":"2026-09-21T17:30:43+00:00","twitter:card":"summary_large_image","twitter:title":"Content Security Policy (CSP) : Bypass Techniques and Prevention","twitter:description":"This article explores how CSP works, its key directives, the most common configuration errors, bypass techniques and security best practices to prevent the risk","twitter:image":"https:\/\/hackagora.com\/wp-content\/uploads\/2026\/06\/logo_hackagora_agora_color.svg"},"aioseo_meta_data":{"post_id":"4027","title":"Content Security Policy (CSP) : Bypass Techniques and Prevention","description":"This article explores how CSP works, its key directives, the most common configuration errors, bypass techniques and security best practices to prevent the risk","keywords":null,"keyphrases":{"focus":{"keyphrase":"csp","score":0,"analysis":[]},"additional":[]},"primary_term":null,"canonical_url":null,"og_title":null,"og_description":null,"og_object_type":"default","og_image_type":"default","og_image_custom_url":null,"og_image_custom_fields":null,"og_image_url":null,"og_image_width":null,"og_image_height":null,"og_video":"","og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_image_url":null,"twitter_title":null,"twitter_description":null,"schema_type":"default","schema_type_options":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"BlogPosting","isEnabled":true},"graphs":[]},"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":"-1","robots_max_videopreview":"-1","robots_max_imagepreview":"large","priority":null,"frequency":"default","local_seo":null,"limit_modified_date":false,"ai":{"faqs":[],"keyPoints":[],"schemas":[],"titles":[],"descriptions":[],"socialPosts":{"email":{"subject":"","preview":"","content":""},"linkedin":[],"twitter":[],"facebook":[],"instagram":[]}},"breadcrumb_settings":null,"seo_analyzer_scan_date":null,"created":"2026-09-19 15:56:07","updated":"2026-09-21 17:38:39","focus_keyword":"csp","additional_keywords":null,"truseo_locale":null},"aioseo_breadcrumb":"<div class=\"aioseo-breadcrumbs\"><span class=\"aioseo-breadcrumb\">\n\t\t\t<a href=\"https:\/\/hackagora.com\/en\/\" title=\"Home\">Home<\/a>\n\t\t<\/span><span class=\"aioseo-breadcrumb-separator\">\u00bb<\/span><span class=\"aioseo-breadcrumb\">\n\t\t\t<a href=\"https:\/\/hackagora.com\/en\/category\/applications\/\" title=\"Applications\">Applications<\/a>\n\t\t<\/span><span class=\"aioseo-breadcrumb-separator\">\u00bb<\/span><span class=\"aioseo-breadcrumb\">\n\t\t\tContent Security Policy (CSP) : How it Works, Bypass Techniques and Security Best Practices\n\t\t<\/span><\/div>","aioseo_breadcrumb_json":[{"label":"Home","link":"https:\/\/hackagora.com\/en\/"},{"label":"Applications","link":"https:\/\/hackagora.com\/en\/category\/applications\/"},{"label":"Content Security Policy (CSP) : How it Works, Bypass Techniques and Security Best Practices","link":"https:\/\/hackagora.com\/en\/content-security-policy-csp-how-it-works-bypass-techniques-and-security-best-practices\/"}],"_links":{"self":[{"href":"https:\/\/hackagora.com\/en\/wp-json\/wp\/v2\/posts\/4027","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/hackagora.com\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/hackagora.com\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/hackagora.com\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/hackagora.com\/en\/wp-json\/wp\/v2\/comments?post=4027"}],"version-history":[{"count":38,"href":"https:\/\/hackagora.com\/en\/wp-json\/wp\/v2\/posts\/4027\/revisions"}],"predecessor-version":[{"id":4069,"href":"https:\/\/hackagora.com\/en\/wp-json\/wp\/v2\/posts\/4027\/revisions\/4069"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/hackagora.com\/en\/wp-json\/wp\/v2\/media\/4029"}],"wp:attachment":[{"href":"https:\/\/hackagora.com\/en\/wp-json\/wp\/v2\/media?parent=4027"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/hackagora.com\/en\/wp-json\/wp\/v2\/categories?post=4027"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/hackagora.com\/en\/wp-json\/wp\/v2\/tags?post=4027"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}