CSP-safe design
This page is for designers whose templates must work under a Content Security Policy (CSP). A strict policy stops an injected script from running even where a template forgets to escape a value. The administration sends one; public sites can switch one on. The settings are described in Content Security Policy.
Contents
- The policy in brief
- The three rules for designs
- Replacement patterns
- The nonce: the rare exception
- Auditing a design
The policy in brief
site.ini [ContentSecurityPolicy] with Mode=auto (default) sends a report-only policy on administration siteaccesses
and none on public sites; report-only and enforce can be set per siteaccess. The default directives allow only the
site's own origin:
Directives[script-src]='self'
Directives[style-src]='self'
Directives[img-src]='self' data: blob:
Directives[font-src]='self' data:
Directives[connect-src]='self'So: scripts and styles only from files on the same host; no inline <script>, no onclick="", no <style> blocks or
style="" attributes once the policy is enforced.
The three rules for designs
- No inline JavaScript: no
<script>with code, noon*=attributes, nojavascript:links (No inline JavaScript). - No inline CSS: no
<style>blocks, nostyle=""attributes (No inline CSS). - No third-party origins you have not planned: fonts, scripts, styles, frames from other hosts each need a directive change (External resources and CDNs).
Replacement patterns
| Instead of | Use |
|---|---|
<script>var cfg = {...}</script> | <script type="application/json" id="cfg">{json_encode(...)}</script> read by your script file |
onclick="doThing()" | data-action="thing" and addEventListener in a script file |
<a href="javascript:..."> | a <button type="button"> with a listener |
style="width: 40%" | a class (.pct-40) or a data- value a script turns into a custom property |
<style> with per-page rules | a stylesheet required by the view template (ezcss_require) |
| a head script that must run before paint | a small file loaded with ezscript in the head (The theme toggle and the pre-paint guard) |
The nonce: the rare exception
With [ContentSecurityPolicy] Nonce=enabled (default) the policy carries a fresh nonce per request for the
directives in NonceDirectives[] (script-src). The template operator csp_nonce() prints it:
<script nonce="csp_nonce()">/* code a template truly cannot move into a file */</script>Never print csp_nonce() inside a {cache-block} or a view-cached template: the cached copy would carry another
request's nonce and the script would be blocked (comments in settings/site.ini and
kernel/common/expcontentsecuritypolicyoperator.php). That is why files are always the better choice.
Auditing a design
Run the site with
Mode=report-onlyfor a while and read the reports: the browser console shows them, and withReportEndpoint=enabledthe kernel writes them tovar/log/csp.log, listed in Setup > Log files.For administration designs and extensions, the design tests find inline code in templates:
php vendor/bin/phpunit tests/tests/design/AdminContentSecurityPolicyTest.php php vendor/bin/phpunit tests/tests/design/ExtensionInlineScriptsTest.phpSearch your own design:
grep -rn -E "<script>|<script [^>]*>[^<]|on(click|load|change|submit)=|javascript:|<style|style=\"" extension/mytheme/designWhen nothing is reported for the pages you use, switch the siteaccess to
Mode=enforce.
Related pages
- No inline CSS
- No inline JavaScript
- Checklist: CSP
- Free documentation: this page is free to copy, translate and improve under the GNU FDL 1.3.
Become an Exponential Partner
Partners share their information, provide funding, contribute back and develop the products with us, and that is how Exponential grows. A partner website and dashboard are planned for mid-2027: please become a partner today to speed up our growth rate and land new features faster.