Content Security Policy
Eine Content Security Policy teilt dem Browser mit, welche Skripte, Stylesheets, Bilder und Verbindungen eine Seite
verwenden darf. Eine strenge Policy (script-src 'self', kein 'unsafe-inline') bedeutet, dass ein Skript, das über
einen gespeicherten oder reflektierten Wert in eine Seite eingeschleust wurde, nicht ausgeführt wird, selbst wenn ein
Template vergisst, ihn zu maskieren.
Exponential sendet eine Policy aus site.ini [ContentSecurityPolicy], neben den Sicherheits-Headern von
[HTTPHeaderSettings] SecurityHeaders[] (siehe Sicherheitshärtung, 13.9).
Standardmäßig ist sie in der Administration nur meldend (report-only) und auf den Websites ausgeschaltet.
Einstellungen
[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: keine Policy.report-only: der HeaderContent-Security-Policy-Report-Only. Der Browser meldet, was die Policy blockieren würde (in seiner Entwicklerkonsole und anReportURI, sofern gesetzt), und blockiert nichts.enforce: der HeaderContent-Security-Policy. Der Browser blockiert, was die Policy nicht erlaubt. Er wird als zweite Policy nachSecurityHeaders[Content-Security-Policy](standardmäßigframe-ancestors 'self') gesendet; der Browser wendet beide an.auto(der Standard):report-onlyauf Siteaccesses der Administration, also solchen, deren[DesignSettings] SiteDesignmitadminbeginnt (admin, admin3, admin4, admin4l, adminui) oder derenAdditionalSiteDesignList[]ein solches Design enthält (ein Editor-Siteaccess auf admin4l),disabledauf allen anderen.
- Directives[]: die Policy, eine Direktive pro Eintrag. Setzen Sie einen Eintrag in einer Überschreibung leer, um
die Direktive wegzulassen; eine Überschreibung, die mit
Directives[]allein beginnt, ersetzt die ganze Liste. Ein Eintrag, dessen Wert;oder,enthält oder dessen Name kein Direktivenname ist, wird ignoriert, sodass eine Einstellung den Header weder aufteilen noch eine eigene Direktive hinzufügen kann.frame-ancestorsundsandboxwerden nur gesendet, wenn die Policy durchgesetzt wird: Browser ignorieren sie in einer Report-only-Policy (und weisen in der Konsole darauf hin). Das Einbetten in Frames bleibt in jedem Modus durchSecurityHeaders[](X-Frame-Options,frame-ancestors 'self') eingeschränkt. - ReportURI: wohin der Browser seine Berichte sendet (
report-uri), zum Beispiel an einen eigenen Sammeldienst. Leer: nur die Entwicklerkonsole.
Pro Pfad überschreibt [HTTPHeaderSettings] HeaderList weiterhin jeden Header, auch die Policy. Seiten, die aus dem
HTTP-Cache des Kernels beantwortet werden, tragen dieselben Header.
Einschalten
- Lassen Sie die Administration auf
report-onlyund arbeiten Sie eine Weile damit: Öffnen Sie die Entwicklerkonsole (oder sammeln Sie die Berichte mitReportURI) und notieren Sie, was die Policy blockieren würde. Eigene Extensions und Site-Designs sind die übliche Quelle. - Setzen Sie für eine Website
Mode=report-onlyin ihrem Siteaccess und gehen Sie ebenso vor. - Wenn für die von Ihnen genutzten Seiten nichts mehr gemeldet wird, setzen Sie
Mode=enforce.
# settings/siteaccess/<admin siteaccess>/site.ini.append.php
[ContentSecurityPolicy]
Mode=enforceSchritt für Schritt auf enforce umstellen
Der ausgelieferte Standard ändert für Besucher nichts: Mode=auto sendet auf den Siteaccesses der Administration eine
Report-only-Policy und auf den Websites keine. Das Durchsetzen ist eine Entscheidung pro Siteaccess, die nach einer
Prüfung getroffen wird:
Prüfen Sie die Templates.
AdminContentSecurityPolicyTestprüft die Kern-Designs und die Extensions, die dieses Repository ausliefert. Führen Sie ihn einmal mit allen Extensions der Installation aus, so wie die Administration sie rendert (die Ketten vonuiswitch.ini DesignChain[],AdditionalSiteDesignList[]jedes Siteaccess der Administration und die aktiven Extensions aussettings/override):CSP_ALL_EXTENSIONS=1 php vendor/bin/phpunit tests/tests/design/AdminContentSecurityPolicyTest.phpJede Zeile des Fehlschlags ist ein Template mit Inline-Code, den eine durchgesetzte Policy brechen würde, geordnet nach Extension. Ein Template einer Extension wird im Repository dieser Extension korrigiert (Skripte in Dateien, die mit
ezscriptgeladen werden, Werte indata-*-Attribute, Handler in Listener oder diedata-a4-*-Aktionen, Menüeinträge indata-menu-*).Sammeln Sie die Berichte aus der echten Nutzung. Setzen Sie
ReportURIauf einen eigenen Sammeldienst oder beobachten Sie die Entwicklerkonsole (Chrome und Firefox protokollieren „[Report Only] Refused to ...“), und verwenden Sie die Administration einige Tage lang: die Seiten jeder Extension, die Editoren, die Dialoge. Handler- Attribute werden nur gemeldet, wenn sie ausgeführt werden; öffnen Sie also auch die Kontextmenüs und Dialoge.Setzen Sie die Policy zuerst in einem Siteaccess durch, am besten in einem zweiten Siteaccess der Administration, den nur Sie verwenden, mit denselben Designs und Extensions wie der Haupt-Siteaccess. Behalten Sie für alle anderen den Report-only-Siteaccess bei.
Lockern Sie nur, was Sie müssen, pro Siteaccess und pro Direktive, zum Beispiel für TinyMCE 3:
[ContentSecurityPolicy] Mode=enforce Directives[script-src]='self' 'unsafe-inline' Directives[style-src]='self' 'unsafe-inline'Leeren Sie den INI-Cache (
php bin/php/ezcache.php --clear-tag=ini) und laden Sie PHP neu (und starten Sie Velocity neu), damit jeder Prozess den neuen Header sendet. Zum Zurückkehren setzen SieMode=report-only(oderdisabled) und leeren den INI-Cache erneut; sonst ändert sich nichts.
Websites (Siteaccesses außerhalb der Administration) erhalten mit auto keine Policy. Ihre Designs und der von ihnen
gerenderte Rich Text (style="" aus dem Inhalt, eingebettete Player) werden vom Test nicht geprüft: Beginnen Sie dort
mit Mode=report-only.
Berichte
Der Kernel sammelt die Berichte selbst: Die Ansicht csp/report nimmt entgegen, was der Browser sendet, und
schreibt pro Verstoß eine Zeile nach var/log/csp.log, die der Log-Viewer als Berichte der Content Security
Policy auflistet (Setup > Logdateien, Schlüssel csp). Richten Sie die Policy mit ReportEndpoint=enabled
darauf aus (report-uri und der Header Reporting-Endpoints nennen dann csp/report des Siteaccess) oder mit
ReportURI=/csp/report.
- Beide Formate:
application/csp-report(diereport-urivon Firefox und älteren Browsern) undapplication/reports+json(die Reporting API:Reporting-Endpointsundreport-to, Chrome und Edge);application/jsonwird als eines der beiden gelesen. Berichte der Reporting API anderen Typs (deprecation, intervention) werden übersprungen. - Für jeden Besucher offen: Browser senden Berichte ohne Anmeldung, auch an eine Administration, die von allen eine
Anmeldung verlangt.
site.ini [SiteAccessSettings] AnonymousAccessList[]und[RoleSettings] PolicyOmitList[]nennencsp/report. Ein angemeldeter Browser kann sein Sitzungs-Cookie mit einem Bericht senden: Die Prüfung des Formular-Tokens (ezformtoken) lässt einen POST ancsp/reportmit einem dieser Content-Types unbehelligt; ein Formular einer anderen Website kann keinen davon senden, und die Ansicht schreibt nur eine Log-Zeile. - Es kommt nichts zurück: Die Antwort ist ein Status ohne Body:
204für einen angenommenen Bericht,405für eine andere Methode als POST,415für einen anderen Content-Type,413oberhalb vonMaxReportBytes,400für einen Body, der kein Bericht ist, und429(mitRetry-After: 60) oberhalb eines Ratenlimits. Sie wird nie zwischengespeichert (Cache-Control: no-store). - Ratenbegrenzt: pro Client (
ReportsPerMinute, gezählt über den gesalzenen Adress-Hash des Anfrage-Klassifikators, sodass keine Adresse gespeichert wird) und für alle Clients zusammen (ReportsPerMinuteTotal). Der erste abgelehnte Bericht eines Clients in einer Minute wird vom Anfrage-Klassifikator alsabuse/csp-report-floodnachsecurity.loggeschrieben, sodass eine Flut dort eine Zeile pro Minute ergibt, nicht eine pro Anfrage. - Begrenzt und maskiert: Die Adresse wird wie in den Anfrage-Logs maskiert (
203.0.x.x,site.ini [RequestLogSettings] MaskIP); die Query jeder URI durchläuft die Masken des Log-Viewers (Geheimnisse, E-Mail- und IP-Adressen), ein Benutzername oder Passwort in einer URI sowie das Fragment werden entfernt, einedata:- oderblob:-URI wird nur mit ihrem Schema geschrieben, das Beispiel wird auf 40 Zeichen gekürzt, und kein Wert kann eine Zeile oder ein Feld hinzufügen. Aus einem Bericht werden höchstensMaxViolationsPerReportZeilen geschrieben.
[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.logEine Zeile von csp.log, nach dem Datum und der maskierten Adresse:
[ 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>: die verletzte Direktive (script-src-elem,style-src-attr,img-src...).blocked=: was blockiert wurde: ein Schlüsselwort (inline,eval,wasm-eval,data,blob,self,trusted-types-policy) oder die Adresse.disposition=:enforce(der Browser hat es blockiert) oderreport(eine Report-only-Policy: nichts wurde blockiert).document=,source=,line=,column=: die Seite sowie Datei und Position des Codes, sofern der Browser sie nennt.sample=: die ersten Zeichen des Inline-Codes oder -Stils.
Im Log-Viewer werden die Zeilen Feld für Feld gelesen (Quelle csp/<directive>, Anfrage die Seite). Die
Fehlerübersicht gruppiert csp.log nach Direktive und dem, was blockiert wurde, wobei eine Adresse auf ihren
Ursprung gekürzt wird ([DigestSettings] GroupBy[csp]=directive-blocked): csp/script-src-elem blocked=inline 42,
csp/img-src blocked=https://maps.example.com 3. Sie wird wie die Anfrage-Logs rotiert: 5 Archive, komprimiert,
10 MB, nach 30 Tagen bereinigt (logviewer.ini [RotationSettings], Schlüssel csp).
Die ausgelieferten Direktiven
script-src 'self'erlaubt nur Skriptdateien dieser Website: kein Inline-<script>, keinon...=-Attribut, keinjavascript:-Link, keineval().style-src 'self'erlaubt nur Stylesheet-Dateien: kein<style>-Element und keinstyle=""-Attribut. Ein Skript, daselement.stylesetzt, ist nicht betroffen.img-srcundmedia-srcergänzendata:undblob:(Vorschauen von für einen Upload ausgewählten Dateien, in Stylesheets eingebettete Icons);font-srcergänztdata:.object-src 'none',base-uri 'self',form-action 'self',frame-ancestors 'self'schließen die Wege über Plugins, Basis-URL, Formularziel und Einbettung in Frames.
Bevor Sie Mode=enforce setzen, beachten Sie, was object-src 'none' blockiert: die <object>/<embed>-Player der
alten Medienvorschauen (admin_preview/flash.tpl, windows_media.tpl, real_video.tpl, quicktime.tpl und die
Ansichts-Templates von ezmedia; Browser führen diese Plugins ohnehin nicht mehr aus) sowie ein über <object> oder
<embed> inline angezeigtes PDF. Erlauben Sie es dort, wo Sie es brauchen, z. B. Directives[object-src]='self'.
Eine Seite, die mehr benötigt (einen Kartendienst, einen Font-Host, ein Analyse-Skript), ergänzt den Host in der
Direktive ihres Siteaccess, zum Beispiel Directives[script-src]='self' https://maps.example.com.
Die Administration ohne Inline-Code
admin4 und admin4l sowie die Templates von admin3, admin2, admin und standard, die sie rendern, enthalten kein
Inline-<script>, kein on...=-Attribut, keinen javascript:-Link, kein style=""-Attribut und kein
<style>-Element, sodass die Administration unter der ausgelieferten Policy läuft.
tests/tests/design/AdminContentSecurityPolicyTest.php sichert dies für jedes Template der beiden Design-Ketten und
für die Skripte von admin4 ab; die Ausnahmen, die er auflistet, stehen weiter unten.
Der Test liest die Kette so, wie der Kernel sie aufbaut: die Designs von uiswitch.ini [UISwitch] DesignChain[] (oder
die eines Siteaccess der Administration: SiteDesign, AdditionalSiteDesignList[], StandardDesign) und für jedes
Design zuerst die Design-Verzeichnisse der aktiven Extensions (design.ini [ExtensionSettings] DesignExtensions[]),
dann das Kernverzeichnis. Das Template einer Extension zählt daher dort, wo es ein Kern-Template ersetzt, ebenso wie die
Extension-Templates, die im Kern kein Gegenstück haben, etwa die Bearbeitungs-Templates des Online-Editors. Aktiv sind
die Extensions aus site.ini [ExtensionSettings] ActiveExtensions[] (mit settings/override) und die
ActiveAccessExtensions[] jedes Siteaccess, dessen SiteDesign admin4 oder admin4l ist; ein Checkout ohne
settings/override zählt jede Extension, die er enthält. Was eine Extension noch inline enthält, ist pro Extension mit
Begründung in EXTENSION_ALLOWLIST aufgeführt; wie die Liste der Kern-Templates wird sie nur kürzer: Der Test schlägt
fehl, wenn eine aufgeführte Datei weniger Inline-Code enthält als angegeben oder nicht mehr gerendert wird, bis der
Eintrag verringert oder entfernt wird.
Wie die einzelnen Teile funktionieren, für eigene Templates und für Extensions:
- Seitenskripte sind Dateien:
design/admin4/javascript/page/<module>_<view>.js, ausgegeben mit{ezscript( array( 'page/<file>.js' ) )}an der Stelle, an der das Inline-Skript stand, sodass der Code an derselben Stelle der Seite ausgeführt wird. Verwenden Sie in einem Template des Seiteninhaltsezscript, nichtezscript_load:ezscript_loadwürde dort jede Datei ausgeben, dieezscript_requirebis dahin gesammelt hat, und deren Laden im Head beenden (eine später angeforderte Datei, etwa das Skript der Unterelement-Tabelle, käme dann nach dem Seitenskript, das sie braucht). Die Werte, die ein Skript aus dem Template benötigt, sinddata-*-Attribute an dem Element, mit dem es arbeitet, für HTML bereinigt (data-text-selected="{'%count selected.'|i18n( ... )|wash}"); strukturierte Werte sind JSON:data-conf="{json_encode( hash( ... ) )|wash}". Ein Template, das mehrmals auf einer Seite vorkommen kann (das Bearbeitungs-Template eines Datentyps), erhält seine Datei jedes Mal: Die Datei richtet sich nur einmal ein. - Das Head-Skript jeder Seite (Farbmodus,
window.a4Pref) istdesign/admin4/javascript/a4_head.js, geladen vonpage_head_style.tpl(mitezscript) vor den Stylesheets, als normales (blockierendes) Skript im<head>: Es wird weiterhin ausgeführt, bevor der Body geparst wird, sodass schon die erste Darstellung den gespeicherten Farbmodus, die Spaltenbreiten und diedata-a4-pref-*-Werte hat. - Kleine Aktionen sind
data-a4-*-Attribute, die von einem Listener pro Ereignis indesign/admin4/javascript/a4_actions.js(auf jeder admin4-Seite geladen) behandelt werden:data-a4-confirm="Question?"(ein Link, ein Button oder ein Formular),data-a4-discard-confirm,data-a4-toggle-checkboxes="Name[]"(mitdata-a4-formfür ein anderes Formular),data-a4-check-all="#selector",data-a4-dismiss,data-a4-submit-on-change,data-a4-navigate-on-change,data-a4-reset-fieldunddata-a4-focus. Der Kopfkommentar der Datei beschreibt jedes davon. - Kontextmenüs (
popupmenu/*.tpl,ezpopupmenu.js) werden indata-menu-*-Attributen deklariert: am Menüdata-menu-depthunddata-menu-header; an einem Eintragdata-menu-url,data-menu-shown-if,data-menu-disabled-class,data-menu-disabled-for,data-menu-variablemitdata-menu-template(die ID eines<template>mit dem Markup eines Eintrags); was ein Eintrag tut:data-menu-submenu(mitdata-menu-hide),data-menu-form(mitdata-menu-form-values) oderdata-menu-redirect. Ein Element, das ein Menü öffnet, hatdata-menu-open,data-menu-values(JSON),data-menu-languages,data-menu-classes,data-menu-title,data-menu-disableunddata-menu-disable-item. Der Kopfkommentar vonezpopupmenu.jslistet sie auf. Extensions funktionieren weiter: Ein Menüeintrag mit eigenemonmouseover/onclick-Attribut bleibt diesem überlassen, und ein Skript, das einem Kernmenü einen Eintrag hinzufügt (menuArray['ContextMenu']['elements']['my-item'] = { ... }), findet das aus den Attributen erzeugte Menü vor. Ein solcher Extension-Eintrag ist allerdings Inline-Code und wird von der Policy gemeldet, bis die Extension ihn auf die neue Weise deklariert. - Der Inhaltsbaum (
contentstructuremenu/content_structure_menu_dynamic.tpl) ist eindiv.content-tree-menumit seinen Daten indata-tree-root,data-tree-paramsunddata-tree-request(JSON);contentstructuremenu_dynamic.jszeichnet ihn, wenn die Seite geparst ist (window.treeMenuwird wie bisher gesetzt), und seine Buttons zum Auf- und Zuklappen funktionieren über einen Listener statt über erzeugteonclick-Attribute. - Erzeugter Skript- und Stilinhalt von ezjscore (Serverfunktionen wie
ezjsc::jqueryiobei ausgeschaltetem Packen, etwa imDevelopmentMode) wird in eine Datei im Cache-Verzeichnis des Packers geschrieben, nach seinem Inhalt benannt und verlinkt (ezjscPacker::generatedContentURL()); zuvor wurde er inline ausgegeben.
Dasselbe gilt für die Kopien der Kontextmenü- und Baumskripte und -Templates in design/admin, die identisch mit
denen von admin4 gehalten werden; die übrigen Templates des klassischen Admin-Designs sind unverändert.
Was weiterhin mehr als 'self' benötigt
- Inhaltsausgabe: die Breite einer Rich-Text-Tabelle (
ezxmltags/table.tpl), die Ausrichtung ihrer Zellen (td.tpl) und der Rahmen eines Bildes (content/datatype/view/ezimage.tpl) sindstyle=""-Werte, die aus dem Inhalt stammen. Unter einem durchgesetztenstyle-src 'self'verwirft der Browser sie (die Admin-Vorschau zeigt die Tabelle in ihrer natürlichen Breite). Ergänzen Sie'unsafe-inline'instyle-src, wenn das für Ihre Inhalte wichtig ist. - Der Beleg (
shop/orderreceipt_style.tpl) behält sein<style>-Element: Es wird mit dem Beleg als aufzubewahrende Datei (orderreceipt_document.tpl) geteilt, die ihre eigenen Stile mitführen muss. - Rich-Text-Editoren. Die TinyMCE-8-Engine von ezoe startet aus einer Datei:
ezxmltext_ezoe_tinymce8.tplschreibt die Einstellungen, die alle Editoren teilen, als<script type="application/json" id="ezoe8-settings">und die Toolbar-Zuordnung alsid="ezoe8-button-map", jede Textarea trägt ihre Attribut-ID, Buttons, Pfadposition und Zeilen indata-ezoe8-*-Attributen, undjavascript/tinymce8_ez/ezoe8_start.js(mitdefergeladen) liest sie und rufttinymce.initauf. Einen eigenen Datenblock geben Sie aus einemset-blockmit|explode( '<' )|implode( '<' )|explode( '>' )| implode( '>' )aus:json_encodelässt<!--und<scriptin einem Wert unverändert, und<!--<script>lässt den Browser über das</script>des Blocks hinweglesen, sodass die Skripte danach nicht ausgeführt werden. TinyMCE 8 setzt selbst Stile an seinen eigenen Elementen (seine Dokumentation verlangtstyle-src 'unsafe-inline'); setzen Sie die Policy dort, wo es verwendet wird, also mit'unsafe-inline'instyle-srcdurch. TinyMCE 3, die Standard-Engine (ezoe.ini [EditorSettings] EditorEngine=tinymce3), ist nicht umgestellt: Sein Bearbeitungs-Templateezxmltext_ezoe.tplstartet den Editor mit einem Inline-Skript, und seine Dialoge (die 27 Templates unterextension/ezoe/design/standard/templates/ezoe/, die Inline-Code enthalten) haben 23 Inline-Skripte, 13 Event-Handler-Attribute, 51javascript:-Links, 52style=""-Attribute und ein<style>-Element (gezählt am 16. Oktober 2026).tiny_mce.jsundtiny_mce_popup.jsschreibenjavascript:-Links undonclick-Attribute in die Toolbars und Dialoge, die sie aufbauen, sodass das Verschieben der Templates in Dateien allein TinyMCE 3 nicht unter der Policy lauffähig machen würde. Ein Siteaccess, der mit TinyMCE 3 bearbeitet, benötigtDirectives[script-src]='self' 'unsafe-inline'undDirectives[style-src]='self' 'unsafe-inline', bevor er die Policy durchsetzt, oder er wechselt zu TinyMCE 8. Editoren anderer Pakete (Tiptap, exp_oe_tiptap) werden vom selben Test geprüft, wenn sie installiert sind; was sie noch inline schreiben, steht inEXTENSION_ALLOWLIST. - Extensions, die Inline-Skripte oder Handler in Admin-Seiten schreiben (eigene Menüeinträge, Seitenskripte),
werden gemeldet, bis sie wie oben beschrieben auf Dateien und
data-*-Attribute umgestellt sind.
Gemessen auf einer Installation mit vielen Extensions (17. Oktober 2026)
Ein Crawl von 45 Administrationsseiten pro Siteaccess (admin4l, admin4, adminui von Exponential UI), auf Apache und auf Velocity, angemeldet als Administrator, mit der Report-only-Policy dieses Releases:
| vorher | nachher | |
|---|---|---|
| Inline-Skripte pro Seite | 32-34 | 11-15 |
| Event-Handler-Attribute pro Seite | 165-167 | 36-42 |
| Policy-Berichte pro Seite (admin4, admin4l) | 35 | 11-14 |
Keine Seite ging kaputt, und es kam kein Skriptfehler hinzu (report-only blockiert nichts). Jeder verbleibende Befund
stammt aus einer installierten Extension oder dem eigenen Design von Exponential UI, nicht aus den Templates des
Kernels; CSP_ALL_EXTENSIONS=1 listet sie pro Datei auf. Auf dieser Installation waren es, nach Anzahl der Befunde in
den Templates, die admin4 und admin4l rendern:
cjw_newsletter (193), eztags (49), xrowextract (24, seine Kontextmenüs für Export und Import), explayouts_ui_api (22),
sevenx_authentication_2fa (14), ezodf (14, die OpenOffice-Menüeinträge), explayouts (12), ezgmaplocation, expai (je
8), xrowmetadata, expchangeclass (je 7), syndication, recaptcha, bcwebsitestatistics (je 6), ezie,
explayouts_ui, enhancedselection2 (je 5), ezwt, ezownerchange, ezautosave (je 4), ezstarrating (3) sowie ein oder zwei
in sevenx_dse, git_manager, bccie, ezupdate, expui, expqueue, explayouts_content_browser_ui, enhancedezbinaryfile,
cjw_tmv und cjw_themes_jumper. Zur Laufzeit kommen die Kontextmenüeinträge von ezmultiupload und ezownerchange, die
Debug-Ausgabe und das Head-Skript von Exponential UI (Ace und der jQuery-UI-Fallback) hinzu. Jede davon wird in ihrem
eigenen Repository geändert, bevor ein Siteaccess, der sie verwendet, auf enforce gesetzt wird.
Extensions ohne Inline-Skripte (17. Oktober 2026)
Die in tests/tests/design/ExtensionInlineScriptsTest.php aufgeführten Extensions enthalten ab der dort genannten
Version (composer.json verlangt diese Versionen) in keinem Template ein Inline-Skript, ein Event-Handler-Attribut oder
einen javascript:-Link; der Test hält sie daran, sobald sie installiert sind. Sie folgen den obigen Mustern mit einem
Unterschied: Eine Extension läuft in jedem Administrationsdesign und auf früheren Releases, daher verlässt sie sich
weder auf die data-a4-*-Aktionen von admin4 noch auf die data-menu-*-Menüs. Jede liefert ihre eigene Skriptdatei
mit delegierten Listenern aus; ihre Kontextmenüeinträge werden in Datenattributen deklariert, und ihr Skript fügt sie
menuArray hinzu und ruft dieselben ezpopmenu_*-Funktionen wie bisher auf.
Was sie noch inline enthalten, sind Stile (style="", <style>), die style-src 'self' meldet; setzen Sie die
Policy mit style-src 'self' 'unsafe-inline' durch, bis auch diese verschoben sind.
Gemessen auf derselben Installation nach diesen Releases, wobei der Browsertest die Policy auf jede Seite setzte und
script-src 'self' durchgesetzt war: Die Administrationsseiten von admin4l, admin4 und Exponential UI (darunter die
Seiten für Newsletter, Tags, Export, Layouts, Eigentümerwechsel und die übrigen Extension-Seiten) melden keinen
Skriptverstoß, und ihre Funktionen arbeiten, auf Apache und auf Velocity. Was im Sinne von Skripten noch inline ist, ist
die TinyMCE-3-Engine (ezoe und die Kopie ihres Bearbeitungs-Templates in Exponential UI); externe Skripte (Google Maps,
reCAPTCHA, die Social-Buttons von ezdemo, ein Ad-Server von ezflow) benötigen ihre Hosts in script-src.
Bevor Sie die Policy in der Administration durchsetzen
- Menüeinträge von Extensions. Eine Extension, die Kontextmenüeinträge mit einem Inline-Skript
(
menuArray['ContextMenu']['elements'][...] = ...) undonmouseover/onclick-Attributen hinzufügt - zum Beispiel die Menüs von xrowextract, ezodf (die OpenOffice-Einträge für Import/Export) und ezmultiupload (der Eintrag Multi Upload der Kontextmenüs) -, funktioniert unter report-only. Unter einer durchgesetzten Policy führt der Browser weder das Skript noch die Handler aus: Die Einträge werden weiterhin gezeichnet, bewirken aber nichts, ohne einen Fehler auf der Seite. Stellen Sie sie aufdata-menu-*um (wie die Kernmenüs), bevor SieMode=enforcesetzen, oder behalten Sie'unsafe-inline'inscript-srcfür diesen Siteaccess. menuArrayneu zugewiesen.ezpopupmenu.jserzeugtmenuArrayund legt den Eintrag eines mitdata-menu-*deklarierten Menüs beim ersten Abruf an. Ein Skript, das es neu zuweist (var menuArray = new Array();,menuArray = {};), ersetzt dies: Die deklarierten Kernmenüs (Kontextmenü, Unterelemente, Klassen-, Lesezeichenmenüs und deren Untermenüs) öffnen sich dann nicht mehr. Ergänzen SiemenuArray, weisen Sie es nie neu zu.- Plugins und Inline-PDF: siehe
object-srcoben.
Durchsetzen: Nonce, weitere Quellen, ein strengerer Begleiter, Rückkehr
Diese Einstellungen von site.ini [ContentSecurityPolicy] machen eine durchgesetzte Policy praktikabel. Keine davon
ändert die Policy einer neuen Installation, außer dass die Report-only-Policy der Administration eine Nonce trägt.
[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>: ergänzt Quellen in einer Direktive, dieDirectives[]bereits hat, zum BeispielAdditionalSources[]=script-src https://maps.googleapis.com. Da es eine Liste ist, können diesite.ini.append.phpeiner Extension und der Siteaccess jeweils ihre Hosts hinzufügen, ohne'self'oder die Hosts des jeweils anderen zu wiederholen. Ein Eintrag für eine Direktive, dieDirectives[]nicht hat, wird ignoriert: Er würde sonst eine Direktive beginnen, die den Rückfall aufdefault-srcersetzt. Duplikate werden entfernt.ReportOnlyDirectives[]: nur mit
Mode=enforce. Ein zweiter Header,Content-Security-Policy-Report-Only, trägtDirectives[]mit diesen Einträgen an deren Stelle (und denselben zusätzlichen Quellen, derselben Nonce und derselben Berichtsadresse). Der Browser setzt den ersten durch und meldet nur, was der zweite blockieren würde. Verwenden Sie ihn, um den Weg zu einer strengeren Policy zu vermessen, zum Beispielstyle-src 'self', während die durchgesetzte Policy noch'unsafe-inline'-Stile erlaubt:Mode=enforce Directives[style-src]='self' 'unsafe-inline' ReportOnlyDirectives[style-src]='self'ReportOnlyDesigns[]: Eine Anfrage, deren Design (
[DesignSettings] SiteDesign, nachdem der UI-Switch die Wahl des Benutzers angewendet hat) aufgeführt ist, erhält die Report-only-Policy, auch wenn der Siteaccess sie durchsetzt. Die klassische Administration (adminundadmin2, das deren Pagelayout rendert) hat Inline-Skripte in vielen Templates; ein Benutzer, der sie mit dem UI-Switch auswählt, behält eine funktionierende Seite.Nonce, NonceDirectives[]: Mit
Nonce=enablederhält jede Anfrage eine Nonce (16 Zufallsbytes, base64url), die als'nonce-...'jeder Direktive ausNonceDirectives[]hinzugefügt wird. Ein Template gibt sie mit dem Operatorcsp_nonce()aus:<script nonce="{csp_nonce()}">...</script>Der Operator gibt eine leere Zeichenkette zurück, wenn die Policy keine Nonce trägt. Die Nonce wird keiner Direktive hinzugefügt, die
'unsafe-inline'erlaubt, weil Browser'unsafe-inline'neben einer Nonce ignorieren. So bleibt ein Siteaccess, der'unsafe-inline'noch braucht (TinyMCE 3), funktionsfähig. Die persistenten Worker von Velocity erhalten pro Anfrage eine neue Nonce (der Kernel setzt sie zu Beginn jeder Anfrage zurück). Geben Siecsp_nonce()nie innerhalb eines{cache-block}, eines im View-Cache gespeicherten Templates oder eines statischen Caches aus: Die zwischengespeicherte Kopie trüge die Nonce einer anderen Anfrage, und der Browser würde das Skript blockieren. Bevorzugen Sie in dieser Reihenfolge: eine mitezscriptgeladene Datei (ein Skript im Head läuft vor der ersten Darstellung, es flackert also nicht), einen Hash eines statischen Inline-Skripts (AdditionalSources[]=script-src 'sha256-...'), dann die Nonce, nur für nicht zwischengespeicherte Ausgaben. Der HTTP-Cache des Kernels speichert eine Seite mit ihren Headern, sodass eine zwischengespeicherte Seite und ihre Nonce zusammenbleiben.ReportURI, ReportEndpoint: Mit
ReportEndpoint=enabledund leeremReportURIgehen die Berichte an die Ansichtcsp/reportdes Siteaccess, die pro Verstoß eine Zeile nachvar/log/csp.logprotokolliert (Schlüsselcspim Log-Viewer). Eine absolute Berichtsadresse wird zusätzlich als Reporting-Endpunktcsp-endpointangekündigt, mit dem HeaderReporting-Endpointsund der Direktivereport-to. Chrome sendet an diesen, Firefox anreport-uri.allowsInlineScripts():expContentSecurityPolicy::allowsInlineScripts()teilt Code (zum Beispiel einem Editor, der seine Engine wählt) mit, ob Inline-Skripte und Handler-Attribute unter der Policy des aktuellen Siteaccess ausgeführt werden.
Rückkehr. Setzen Sie Mode=report-only im Siteaccess, leeren Sie den INI-Cache (php bin/php/ezcache.php --clear-tag=ini), laden Sie PHP-FPM neu und starten Sie Velocity neu. Sonst ändert sich nichts: Dieselbe Policy wird
als Content-Security-Policy-Report-Only gesendet, und der strengere Begleiter wird nicht mehr gesendet.
Der ausgelieferte Standard bleibt Mode=auto, report-only in der Administration. Das Durchsetzen ist eine Entscheidung
pro Installation, die nach der obigen Prüfung getroffen wird.
Inline-Wächter und Editoren. Kein Pagelayout der Administration benötigt eine Nonce oder einen Hash: admin4 und
admin4l führen ihren Code vor der ersten Darstellung aus a4_head.js aus, admin3 aus admin3_head.js, beide im Head
geladen. Der Online-Editor bietet TinyMCE 3 nicht an, wo die Policy ohne 'unsafe-inline' in script-src
durchgesetzt wird (TinyMCE 3 schreibt Handler-Attribute); er weicht dann auf TinyMCE 8 aus. Ein Siteaccess, der
TinyMCE 3 behalten muss, setzt für sich Directives[script-src]='self' 'unsafe-inline' und
Directives[style-src]='self' 'unsafe-inline'. Die ausgelieferte Policy nennt außerdem worker-src 'self', sodass
AdditionalSources[]=worker-src blob: einen Blob-Worker erlauben kann, wo eine Funktion einen benötigt.
Eine Ansicht mit eigener Policy. Der Kernel sendet seine Header, bevor die Ansicht läuft. Eine Ansicht, die mit
PHPs header() eine eigene Content-Security-Policy sendet (das Datenbankwerkzeug AdminNeo von sevenx_dse, mit eigener
Nonce), ersetzt für diese Antwort die durchgesetzte Policy des Kernels, unter PHP-FPM wie unter Exponential Velocity.
Das ist beabsichtigt: Die Policy der Ansicht passt zu ihren eigenen Inline-Skripten. Der strengere Report-only-Begleiter
ist ein eigener Header; die Ansicht von AdminNeo entfernt ihn mit header_remove(), sodass ihre Seite nur die Policy
von AdminNeo trägt. Eine Ansicht, die zusätzlich die Policy des Kernels möchte, fügt ihren Header mit
header( ..., false ) hinzu.
Als Sicherheitsnetz für einen Server, der beide Header behält, führen Sie eine solche Ansicht in ReportOnlyViews[]
(module/view) auf; der Kernel sendet seine Policy für diese Ansicht dann nur meldend, sodass die eigene Policy der
Ansicht die einzige durchgesetzte ist. sevenx_dse (ab 1.1.13) liefert diese Zeile in seinen eigenen Einstellungen aus:
[ContentSecurityPolicy]
ReportOnlyViews[]=dse/adminneo'report-sample' in script-src und style-src (zum Beispiel Directives[script-src]='self' 'report-sample') lässt
Browser die ersten 40 Zeichen eines blockierten Inline-Skripts oder -Stils mit dem Bericht senden; csp.log zeigt sie
als sample="...". Es ändert nichts daran, was die Policy erlaubt.
Werden Sie Exponential-Partner
Partner teilen ihre Informationen, stellen Mittel bereit, geben Beiträge zurück und entwickeln die Produkte mit uns, und so wächst Exponential. Eine Partner-Website und ein Dashboard sind für Mitte 2027 geplant: Werden Sie noch heute Partner, damit wir schneller wachsen und neue Funktionen früher liefern.