Content Security Policy
A Content Security Policy tells the browser which scripts, stylesheets, images and connections a page may use. A
strict policy (script-src 'self', no 'unsafe-inline') means that a script injected into a page through a stored
or reflected value does not run, even where a template forgets to escape it.
Exponential sends a policy from site.ini [ContentSecurityPolicy], beside the security headers of
[HTTPHeaderSettings] SecurityHeaders[] (see Security hardening, 13.9).
By default it is report-only on the administration and off on the sites.
Settings
[ContentSecurityPolicy]
# disabled | report-only | enforce | auto
Mode=auto
Directives[]
Directives[default-src]='self'
Directives[script-src]='self'
Directives[style-src]='self'
Directives[img-src]='self' data: blob:
Directives[font-src]='self' data:
Directives[media-src]='self' blob:
Directives[connect-src]='self'
Directives[frame-src]='self'
Directives[object-src]='none'
Directives[base-uri]='self'
Directives[form-action]='self'
Directives[frame-ancestors]='self'
ReportURI=- Mode
disabled: no policy.report-only: the headerContent-Security-Policy-Report-Only. The browser reports what the policy would block (in its developer console, and toReportURIwhen set) and blocks nothing.enforce: the headerContent-Security-Policy. The browser blocks what the policy does not allow. It is sent as a second policy afterSecurityHeaders[Content-Security-Policy](frame-ancestors 'self'by default); the browser applies both.auto(the default):report-onlyon administration siteaccesses, those whose[DesignSettings] SiteDesignbegins withadmin(admin, admin3, admin4, admin4l, adminui) or whoseAdditionalSiteDesignList[]has such a design (an editor siteaccess on admin4l),disabledon all others.
- Directives[]: the policy, one directive per entry. Set an entry empty in an override to leave the directive out;
an override that starts with
Directives[]alone replaces the whole list. An entry whose value holds;or,, or whose name is not a directive name, is ignored, so a setting cannot split the header or add a directive of its own.frame-ancestorsandsandboxare sent only when the policy is enforced: browsers ignore them in a report-only policy (and say so in the console). Framing stays limited bySecurityHeaders[](X-Frame-Options,frame-ancestors 'self') in every mode. - ReportURI: where the browser posts its reports (
report-uri), for example a collector of your own. Empty: the developer console only.
Per path, [HTTPHeaderSettings] HeaderList still overrides any header, the policy included. Pages answered from the
kernel's HTTP cache carry the same headers.
Turning it on
- Leave the administration in
report-onlyand work with it for a while: open the developer console (or collect the reports withReportURI) and note what the policy would block. Extensions and site designs of your own are the usual source. - For a site, set
Mode=report-onlyin its siteaccess and do the same. - When nothing is reported for the pages you use, set
Mode=enforce.
# settings/siteaccess/<admin siteaccess>/site.ini.append.php
[ContentSecurityPolicy]
Mode=enforceSwitching to enforce, step by step
The shipped default changes nothing for a visitor: Mode=auto sends a report-only policy on administration
siteaccesses and none on the sites. Enforcing is a decision per siteaccess, made after an audit:
Audit the templates.
AdminContentSecurityPolicyTestchecks the core designs and the extensions this repository ships. Run it once with every extension of the installation, as the administration renders them (the chains ofuiswitch.ini DesignChain[], each administration siteaccess'sAdditionalSiteDesignList[]and the active extensions ofsettings/override):CSP_ALL_EXTENSIONS=1 php vendor/bin/phpunit tests/tests/design/AdminContentSecurityPolicyTest.phpEvery line of the failure is a template with inline code that an enforced policy would break, by extension. A template of an extension is fixed in that extension's repository (scripts into files loaded with
ezscript, values intodata-*attributes, handlers into listeners or thedata-a4-*actions, menu items intodata-menu-*).Collect the reports of real use. Set
ReportURIto a collector of your own, or watch the developer console (Chrome and Firefox log "[Report Only] Refused to ..."), and use the administration for a few days: the pages of every extension, the editors, the dialogs. Handler attributes are reported only when they run, so open the context menus and dialogs too.Enforce one siteaccess first, best a second administration siteaccess that only you use, with the same designs and extensions as the main one. Keep the report-only one for everyone else.
Loosen only what you must, per siteaccess and per directive, for example for TinyMCE 3:
[ContentSecurityPolicy] Mode=enforce Directives[script-src]='self' 'unsafe-inline' Directives[style-src]='self' 'unsafe-inline'Clear the INI cache (
php bin/php/ezcache.php --clear-tag=ini) and reload PHP (and restart Velocity) so every process sends the new header. To go back, setMode=report-only(ordisabled) and clear the INI cache again; nothing else changes.
Sites (non-administration siteaccesses) get no policy with auto. Their designs and the rich text they render
(style="" from content, embedded players) are not checked by the test: start them with Mode=report-only.
Reports
The kernel collects the reports itself: the view csp/report takes what the browser sends and writes one line
per violation to var/log/csp.log, which the log viewer lists as Content Security Policy reports (Setup > Log
files, key csp). Point the policy at it with ReportEndpoint=enabled (the report-uri and the
Reporting-Endpoints header then name the csp/report of the siteaccess), or with ReportURI=/csp/report.
- Both formats:
application/csp-report(thereport-uriof Firefox and older browsers) andapplication/reports+json(the Reporting API:Reporting-Endpointsandreport-to, Chrome and Edge);application/jsonis read as either. Reports of the Reporting API of another type (deprecation, intervention) are skipped. - Open to every visitor: browsers send reports without signing in, also on an administration that asks
everyone to sign in.
site.ini [SiteAccessSettings] AnonymousAccessList[]and[RoleSettings] PolicyOmitList[]namecsp/report. A signed-in browser may send its session cookie with a report: the form token check (ezformtoken) leaves a POST tocsp/reportwith one of these content types alone; a form of another site can send none of them, and the view only writes a log line. - Nothing comes back: the answer is a status without a body:
204for a report taken,405for another method than POST,415for another content type,413aboveMaxReportBytes,400for a body that is no report and429(withRetry-After: 60) above a rate limit. It is never cached (Cache-Control: no-store). - Rate limited: per client (
ReportsPerMinute, counted by the salted address hash of the request classifier, so no address is stored) and for all clients together (ReportsPerMinuteTotal). The first refused report of a client in a minute is written tosecurity.logasabuse/csp-report-floodby the request classifier, so a flood is one line a minute there, not one per request. - Bounded and masked: the address is masked as in the request logs (
203.0.x.x,site.ini [RequestLogSettings] MaskIP); the query of every URI goes through the masks of the log viewer (secrets, e-mail and IP addresses), a user name or password in a URI and the fragment are taken off, adata:orblob:URI is written as its scheme only, the sample is cut to 40 characters, and no value can add a line or a field. At mostMaxViolationsPerReportlines are written from one report.
[ContentSecurityPolicy]
# per client and minute; above it 429 and one abuse/csp-report-flood line in security.log
ReportsPerMinute=60
# all clients together, per minute
ReportsPerMinuteTotal=600
# the biggest report body taken, in bytes
MaxReportBytes=65536
# the most violations written from one report
MaxViolationsPerReport=20
# the log file, in the log directory
LogFile=csp.logA line of csp.log, after the date and the masked address:
[ Oct 17 2026 12:00:00 ] [203.0.x.x] csp/script-src-elem blocked=inline disposition=enforce document=https://example.com/admin/content/view/full/2 siteaccess=admin line=12 column=5 source=https://example.com/admin/content/view/full/2 status=200 sample="alert(1)" ua=3f2a9c1b "Mozilla/5.0 (X11; Linux x86_64)"csp/<directive>: the directive that was violated (script-src-elem,style-src-attr,img-src...).blocked=: what it blocked: a keyword (inline,eval,wasm-eval,data,blob,self,trusted-types-policy) or the address.disposition=:enforce(the browser blocked it) orreport(a report-only policy: nothing was blocked).document=,source=,line=,column=: the page, and the file and position of the code, where the browser names them.sample=: the first characters of the inline code or style.
In the log viewer the lines are read field by field (source csp/<directive>, request the page). The error
digest groups csp.log by directive and what it blocked, an address cut to its origin
([DigestSettings] GroupBy[csp]=directive-blocked): csp/script-src-elem blocked=inline 42,
csp/img-src blocked=https://maps.example.com 3. It is rotated like the request logs: 5 archives, compressed, 10 MB,
pruned after 30 days (logviewer.ini [RotationSettings], key csp).
The directives shipped
script-src 'self'allows script files of this site only: no inline<script>, noon...=attribute, nojavascript:link, noeval().style-src 'self'allows stylesheet files only: no<style>element and nostyle=""attribute. A script that setselement.styleis not affected.img-srcandmedia-srcadddata:andblob:(previews of files picked for an upload, icons embedded in stylesheets);font-srcaddsdata:.object-src 'none',base-uri 'self',form-action 'self',frame-ancestors 'self'close the plugin, base URL, form target and framing paths.
Before you set Mode=enforce, mind what object-src 'none' blocks: the <object>/<embed> players of the legacy
media previews (admin_preview/flash.tpl, windows_media.tpl, real_video.tpl, quicktime.tpl and the ezmedia
view templates; browsers no longer run these plugins anyway) and a PDF shown inline through <object> or <embed>.
Allow it where you need it, e.g. Directives[object-src]='self'.
A page that needs more (a map service, a font host, an analytics script) adds the host to the directive in its
siteaccess, for example Directives[script-src]='self' https://maps.example.com.
The administration without inline code
admin4 and admin4l, and the templates of admin3, admin2, admin and standard that they render, carry no inline
<script>, no on...= attribute, no javascript: link, no style="" attribute and no <style> element, so the
administration runs under the shipped policy. tests/tests/design/AdminContentSecurityPolicyTest.php holds this for
every template of the two design chains and for the scripts of admin4; the exceptions it lists are below.
The test reads the chain the way the kernel builds it: the designs of uiswitch.ini [UISwitch] DesignChain[] (or
those of an administration siteaccess: SiteDesign, AdditionalSiteDesignList[], StandardDesign) and, for each
design, first the design directories of the active extensions (design.ini [ExtensionSettings] DesignExtensions[]),
then the core directory. An extension's template therefore counts where it replaces a core template, and so do the
extension templates nothing in the core has, such as the edit templates of the online editor. Active are the
extensions of site.ini [ExtensionSettings] ActiveExtensions[] (with settings/override) and the
ActiveAccessExtensions[] of every siteaccess whose SiteDesign is admin4 or admin4l; a checkout without
settings/override counts every extension it carries. What an extension still has inline is listed per extension in
EXTENSION_ALLOWLIST, with the reason; like the list of core templates it only shrinks: the test fails when a listed
file has less inline code than listed, or is no longer rendered, until the entry is lowered or removed.
How the pieces work, for templates of your own and for extensions:
- Page scripts are files:
design/admin4/javascript/page/<module>_<view>.js, printed with{ezscript( array( 'page/<file>.js' ) )}where the inline script stood, so the code runs at the same point of the page. Useezscript, notezscript_load, in a template of the page's content:ezscript_loadthere would print every file thatezscript_requirecollected so far at that place and end their loading in the head (a file required later, such as the sub-items table's script, would then come after the page script that needs it). The values a script needs from the template aredata-*attributes on the element it works on, washed for HTML (data-text-selected="{'%count selected.'|i18n( ... )|wash}"); structured values are JSON:data-conf="{json_encode( hash( ... ) )|wash}". A template that can appear more than once on a page (a datatype's edit template) gets its file each time: the file sets itself up once. - The head script of every page (color mode,
window.a4Pref) isdesign/admin4/javascript/a4_head.js, loaded bypage_head_style.tpl(withezscript) before the stylesheets, as a normal (blocking) script in<head>: it still runs before the body is parsed, so the first paint has the saved color mode, column widths anddata-a4-pref-*values. - Small actions are
data-a4-*attributes, handled by one listener per event indesign/admin4/javascript/a4_actions.js(loaded on every admin4 page):data-a4-confirm="Question?"(a link, button or form),data-a4-discard-confirm,data-a4-toggle-checkboxes="Name[]"(withdata-a4-formfor another form),data-a4-check-all="#selector",data-a4-dismiss,data-a4-submit-on-change,data-a4-navigate-on-change,data-a4-reset-fieldanddata-a4-focus. The header comment of the file describes each. - Context menus (
popupmenu/*.tpl,ezpopupmenu.js) are declared indata-menu-*attributes: on the menudata-menu-depthanddata-menu-header; on an itemdata-menu-url,data-menu-shown-if,data-menu-disabled-class,data-menu-disabled-for,data-menu-variablewithdata-menu-template(the id of a<template>with the markup of one entry); what an item does:data-menu-submenu(withdata-menu-hide),data-menu-form(withdata-menu-form-values) ordata-menu-redirect. An element that opens a menu hasdata-menu-open,data-menu-values(JSON),data-menu-languages,data-menu-classes,data-menu-title,data-menu-disableanddata-menu-disable-item. The header comment ofezpopupmenu.jslists them. Extensions keep working: a menu item with its ownonmouseover/onclickattribute is left to it, and a script that adds an item to a core menu (menuArray['ContextMenu']['elements']['my-item'] = { ... }) finds the menu created from its attributes. Such an extension item is inline code, though, and reported by the policy until the extension declares it the new way. - The content tree (
contentstructuremenu/content_structure_menu_dynamic.tpl) is adiv.content-tree-menuwith its data indata-tree-root,data-tree-paramsanddata-tree-request(JSON);contentstructuremenu_dynamic.jsdraws it when the page is parsed (window.treeMenuis set as before), and its open/close buttons work through one listener instead of generatedonclickattributes. - Generated script and style content of ezjscore (server functions such as
ezjsc::jqueryiowith packing off, as underDevelopmentMode) is written to a file in the packer's cache directory, named after its content, and linked (ezjscPacker::generatedContentURL()); it was printed inline before.
The same applies to the design/admin copies of the context menu and tree scripts and templates, which are kept
identical to admin4's; the other templates of the classic admin design are unchanged.
What still needs more than 'self'
- Content output: the width of a rich text table (
ezxmltags/table.tpl), the alignment of its cells (td.tpl) and the border of an image (content/datatype/view/ezimage.tpl) arestyle=""values that come from the content. Under an enforcedstyle-src 'self'the browser drops them (the admin preview shows the table at its natural width). Add'unsafe-inline'tostyle-srcif that matters for your content. - The receipt (
shop/orderreceipt_style.tpl) keeps its<style>element: it is shared with the receipt as a file to keep (orderreceipt_document.tpl), which must carry its own styles. - Rich text editors. The TinyMCE 8 engine of ezoe starts from a file:
ezxmltext_ezoe_tinymce8.tplwrites the settings every editor shares as<script type="application/json" id="ezoe8-settings">and the toolbar map asid="ezoe8-button-map", each textarea carries its attribute id, buttons, path location and rows indata-ezoe8-*attributes, andjavascript/tinymce8_ez/ezoe8_start.js(loaded withdefer) reads them and callstinymce.init. A data block of your own is printed from aset-blockwith|explode( '<' )|implode( '\u003c' )|explode( '>' )| implode( '\u003e' ):json_encodeleaves<!--and<scriptin a value as they are, and<!--<script>makes the browser read past the block's</script>, so the scripts after it do not run. TinyMCE 8 itself sets styles on its own elements (its documentation asks forstyle-src 'unsafe-inline'), so enforce with'unsafe-inline'instyle-srcwhere it is used. TinyMCE 3, the default engine (ezoe.ini [EditorSettings] EditorEngine=tinymce3), is not converted: its edit templateezxmltext_ezoe.tplstarts the editor with an inline script, and its dialogs (the 27 templates underextension/ezoe/design/standard/templates/ezoe/that carry inline code) have 23 inline scripts, 13 event handler attributes, 51javascript:links, 52style=""attributes and one<style>element (counted 16 October 2026).tiny_mce.jsandtiny_mce_popup.jswritejavascript:links andonclickattributes into the toolbars and dialogs they build, so moving the templates into files alone would not make TinyMCE 3 run under the policy. A siteaccess that edits with TinyMCE 3 needsDirectives[script-src]='self' 'unsafe-inline'andDirectives[style-src]='self' 'unsafe-inline'before it enforces the policy, or switches to TinyMCE 8. Editors of other packages (Tiptap, exp_oe_tiptap) are checked by the same test when they are installed; what they still write inline is inEXTENSION_ALLOWLIST. - Extensions that write inline scripts or handlers into admin pages (their own menu items, page scripts) are
reported until they move to files and
data-*attributes as described above.
Measured on an installation with many extensions (17 October 2026)
A crawl of 45 administration pages per siteaccess (admin4l, admin4, Exponential UI's adminui), on Apache and on Velocity, signed in as an administrator, with the report-only policy of this release:
| before | after | |
|---|---|---|
| inline scripts per page | 32-34 | 11-15 |
| event handler attributes per page | 165-167 | 36-42 |
| policy reports per page (admin4, admin4l) | 35 | 11-14 |
No page broke and no script error was added (report-only blocks nothing). Every remaining finding comes from an
installed extension or Exponential UI's own design, not from the kernel's templates; CSP_ALL_EXTENSIONS=1 lists
them per file. On that installation they were, by number of findings in the templates admin4 and admin4l render:
cjw_newsletter (193), eztags (49), xrowextract (24, its export and import context menus), explayouts_ui_api (22),
sevenx_authentication_2fa (14), ezodf (14, the OpenOffice menu entries), explayouts (12), ezgmaplocation, expai (8
each), xrowmetadata, expchangeclass (7 each), syndication, recaptcha, bcwebsitestatistics (6 each), ezie,
explayouts_ui, enhancedselection2 (5 each), ezwt, ezownerchange, ezautosave (4 each), ezstarrating (3), and one or two
in sevenx_dse, git_manager, bccie, ezupdate, expui, expqueue, explayouts_content_browser_ui, enhancedezbinaryfile,
cjw_tmv and cjw_themes_jumper. At run time the context menu entries of ezmultiupload and ezownerchange, the debug
output and Exponential UI's head script (Ace and the jQuery UI fallback) add to them. Each of these is changed in its
own repository before a siteaccess that uses it is set to enforce.
Extensions without inline scripts (17 October 2026)
The extensions listed in tests/tests/design/ExtensionInlineScriptsTest.php carry no inline script, no event handler
attribute and no javascript: link in any template from the version listed there on (composer.json requires those
versions); the test holds them to it whenever they are installed. They follow the patterns above with one
difference: an extension runs in every administration design and on earlier releases, so it does not rely on
admin4's data-a4-* actions or on the data-menu-* menus. Each ships its own script file with delegated listeners;
its context menu entries are declared in data attributes, and its script adds them to menuArray and calls the
same ezpopmenu_* functions as before.
What they still have inline is styles (style="", <style>), which style-src 'self' reports; enforce with
style-src 'self' 'unsafe-inline' until those are moved as well.
Measured on the same installation after these releases, with the policy put on every page by the browser test and
script-src 'self' enforced: the administration pages of admin4l, admin4 and Exponential UI (the newsletter, tags,
export, layouts, owner change and the other extension pages among them) report no script violation and their
features work, on Apache and on Velocity. What is still inline in a script sense is the TinyMCE 3 engine (ezoe and
Exponential UI's copy of its edit template); external scripts (Google Maps, reCAPTCHA, the social buttons of ezdemo,
an ad server of ezflow) need their hosts in script-src.
Before enforcing on the administration
- Menu items of extensions. An extension that adds context menu items with an inline script
(
menuArray['ContextMenu']['elements'][...] = ...) andonmouseover/onclickattributes - for example the menus of xrowextract, ezodf (the OpenOffice import/export entries) and ezmultiupload (the Multi Upload entry of the context menus) - works under report-only. Under an enforced policy the browser runs neither the script nor the handlers: the entries are still drawn but do nothing, without an error on the page. Convert them todata-menu-*(as the core menus are) before you setMode=enforce, or keep'unsafe-inline'inscript-srcfor that siteaccess. menuArrayassigned anew.ezpopupmenu.jscreatesmenuArray, and creates the entry of a menu declared withdata-menu-*the first time it is asked for. A script that assigns it anew (var menuArray = new Array();,menuArray = {};) replaces that: the declared core menus (context menu, sub-items, class, bookmark menus and their submenus) then no longer open. Add tomenuArray, never assign it.- Plugins and inline PDF: see
object-srcabove.
Enforcing: nonce, more sources, a stricter companion, rollback
These settings of site.ini [ContentSecurityPolicy] make an enforced policy practical. None of them changes the
policy of a new installation, except that the report-only policy of the administration carries a nonce.
[ContentSecurityPolicy]
# sources added to a directive of Directives[], a list
AdditionalSources[]
# with Mode=enforce: a stricter Content-Security-Policy-Report-Only beside it
ReportOnlyDirectives[]
# designs that stay report-only under Mode=enforce
ReportOnlyDesigns[]
ReportOnlyDesigns[]=admin
ReportOnlyDesigns[]=admin2
ReportOnlyDesigns[]=admin_classic
Nonce=enabled
NonceDirectives[]
NonceDirectives[]=script-src
ReportURI=
ReportEndpoint=disabledAdditionalSources[]
=<directive> <sources>: adds sources to a directive thatDirectives[]already has, for exampleAdditionalSources[]=script-src https://maps.googleapis.com. Because it is a list, an extension'ssite.ini.append.phpand the siteaccess can each add their hosts without restating'self'or each other's hosts. An entry for a directive thatDirectives[]does not have is ignored: it would otherwise start a directive that replaces thedefault-srcfallback. Duplicates are dropped.ReportOnlyDirectives[]: only with
Mode=enforce. A second header,Content-Security-Policy-Report-Only, carriesDirectives[]with these entries in their place (and the same additional sources, nonce and report address). The browser enforces the first one and only reports what the second would block. Use it to measure the way to a stricter policy, for examplestyle-src 'self'while the enforced policy still allows'unsafe-inline'styles:Mode=enforce Directives[style-src]='self' 'unsafe-inline' ReportOnlyDirectives[style-src]='self'ReportOnlyDesigns[]: a request whose design (
[DesignSettings] SiteDesign, after the UI switch has applied the user's choice) is listed gets the report-only policy even when the siteaccess enforces. The classic administration (admin, andadmin2, which renders its pagelayout) has inline scripts in many templates; a user who picks it with the UI switch keeps a working page.Nonce, NonceDirectives[]: with
Nonce=enabled, every request gets a nonce (16 random bytes, base64url) that is added as'nonce-...'to each directive ofNonceDirectives[]. A template prints it with the operatorcsp_nonce():<script nonce="{csp_nonce()}">...</script>The operator returns an empty string when the policy carries no nonce. The nonce is not added to a directive that allows
'unsafe-inline', because browsers ignore'unsafe-inline'next to a nonce. That keeps a siteaccess that still needs'unsafe-inline'(TinyMCE 3) working. Velocity's persistent workers get a new nonce per request (the kernel resets it at the start of each one). Never printcsp_nonce()inside a{cache-block}, a view-cached template or a static cache: the cached copy would carry the nonce of another request and the browser would block the script. Prefer, in this order: a file loaded withezscript(a script in the head runs before the first paint, so it does not flash), a hash of a static inline script (AdditionalSources[]=script-src 'sha256-...'), then the nonce, for uncached output only. The kernel's HTTP cache stores a page with its headers, so a cached page and its nonce stay together.ReportURI, ReportEndpoint: with
ReportEndpoint=enabledandReportURIempty, the reports go to thecsp/reportview of the siteaccess, which logs one line per violation tovar/log/csp.log(log viewer keycsp). An absolute report address is also announced as the reporting endpointcsp-endpoint, with the headerReporting-Endpointsand the directivereport-to. Chrome sends to that one; Firefox toreport-uri.allowsInlineScripts():expContentSecurityPolicy::allowsInlineScripts()tells code (an editor that chooses its engine, for example) whether inline scripts and handler attributes run under the current siteaccess's policy.
Rollback. Set Mode=report-only in the siteaccess, clear the INI cache (php bin/php/ezcache.php --clear-tag=ini), reload PHP-FPM and restart Velocity. Nothing else changes: the same policy is sent as
Content-Security-Policy-Report-Only and the stricter companion is no longer sent.
The shipped default stays Mode=auto, report-only on the administration. Enforcing is a decision per installation,
taken after the audit above.
Inline guards and editors. No administration pagelayout needs a nonce or a hash: admin4 and admin4l run their
pre-paint code from a4_head.js, admin3 from admin3_head.js, both loaded in the head. The online editor does not
offer TinyMCE 3 where the policy is enforced without 'unsafe-inline' in script-src (TinyMCE 3 writes handler
attributes); it falls back to TinyMCE 8. A siteaccess that must keep TinyMCE 3 sets
Directives[script-src]='self' 'unsafe-inline' and Directives[style-src]='self' 'unsafe-inline' for itself. The
shipped policy also names worker-src 'self', so AdditionalSources[]=worker-src blob: can allow a blob worker
where a feature needs one.
A view with a policy of its own. The kernel sends its headers before the view runs. A view that sends its own
Content-Security-Policy with PHP's header() (the database tool AdminNeo of sevenx_dse, with its own nonce) replaces
the kernel's enforced policy for that response, on PHP-FPM and on Exponential Velocity alike. That is intended: the
view's policy fits its own inline scripts. The stricter report-only companion is a header of its own; AdminNeo's view
removes it with header_remove(), so its page carries AdminNeo's policy only. A view that wants the kernel's policy as
well adds its header with header( ..., false ).
As a safety net for a server that keeps both headers, list such a view in ReportOnlyViews[] (module/view); the
kernel then sends its policy report-only for that view, so the view's own policy is the only one enforced. sevenx_dse
(1.1.13 and later) ships this line in its own settings:
[ContentSecurityPolicy]
ReportOnlyViews[]=dse/adminneo'report-sample' in script-src and style-src (for example Directives[script-src]='self' 'report-sample') makes
browsers send the first 40 characters of a blocked inline script or style with the report; csp.log shows them as
sample="...". It changes nothing about what the policy allows.
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.