Content Security Policy
Introduction
Content Security Policy (CSP) is a security standard introduced to prevent cross-site scripting (XSS), clickjacking and other code injection attacks resulting of malicious content being executed in the trusted web page context.
Think of CSP in terms of an "allow/deny" list for remote contents.
See also
If you are not familiar with Content Security Policy, please read the following resources:
- Introduction to Content Security Policy (CSP)
- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
At the TYPO3 Developer Days 2023 Oliver Hader talked about Content Security Policy in general and in TYPO3:
Another great and often updated resource from Chris Müller from his CSP workshops:
CSP rules are used to describe, which external assets or functionality are allowed for
certain HTML tags (like
<script>
,
<img>
,
<iframe>
). This allows
to restrict external resources or JavaScript execution with security in mind. When
accessing a page, these rules are sent as part of the HTTP request from the server to
the browser, and the browser will enforce these rules (and reject non-allowed content).
These rejection are always logged in the browser console. Additionally, external tools
can be configured to receive and track violations of the policy.
Content Security Policy declarations can be applied to a TYPO3 website in
frontend and backend scope with a dedicated API. This API allows for site-specific or
extension-specific configuration instead of manually setting the CSP rules with
server-side configuration through httpd.
or .htaccess
files.
To delegate Content Security Policy handling to the TYPO3 frontend, at least one of the feature flags:
- security.frontend.enforceContentSecurityPolicy (for enforcing)
- security.frontend.reportContentSecurityPolicy (for report-only mode)
needs to be enabled, or the site-specific csp.
configuration
file needs to set the enforce
or reporting
disposition like this:
enforce:
inheritDefault: true
# site-specific mutations could also be listed, like this:
# mutations:
# - mode: "append"
# directive: "img-src"
# sources:
# - "cdn.example.com"
# - "assets.example.com"
# alternatively (or additionally!), reporting can be set too,
# for example when testing stricter rules than above
# (note the missing 'assets.example.com')
# reporting:
# inheritDefault: true
# mutations:
# - mode: "append"
# directive: "img-src"
# sources:
# - "cdn.example.com"
#
Changed in version 13.0
In the TYPO3 backend the Content Security Policy is always enforced.
Within the TYPO3 backend, a specific backend module is available to inspect policy violations / reports, and there is also a list to see all configured CSP rules, see section Active content security policy rules.
Terminology
This document will use very specific wording that is part of the CSP W3C specification, these terms are not "invented" by TYPO3. Since reading the W3C RFC can be very intimidating, here are a few key concepts.
Note
Skip to the section Example scenario to see a "real-life" usage scenario, if you can better understand from actual code examples.
Directives
- CSP consists of multiple rules (or "directives"), that are part of a "policy". This policy says, what functionality the site's output is allowed to use.
- With that, several HTML tags can be controlled, like from which URLs images can be
requested from, if and from where iframes are allowed, if and from where JavaScripts
are allowed and so on. There is a long list of applicable directives, see
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy#directives
with specific identifiers like
default-
,src img-
and so on.src - Each directive may have several "attributes" (like the allowed URLs).
-
Directives may build upon each other, a bit like CSS definitions (
Cascading Style Sheet
) do. However, these are more meant to modify a basic rule on an earlier level, and called "mutations". The relation of a "child rule" to its "parent" is also called "ancestor chain". An example:If
frame-
is not defined, it falls back tosrc child-
, and finally falls back tosrc default-
. But ifsrc frame-
is defined, it is used, and the sources fromsrc default-
are not used. In such a case,src default-
listed sources have to be repeated (when wanted) explicitly insrc frame-
.src
Applying the policy
- A final policy is compiled of all these directives, and then sent as a HTTP
response header
Content-
(respectivelySecurity- Policy: ... Content-
).Security- Policy- Reporty- Only - In TYPO3, directives can be specified via PHP syntax (within Extensions) and YAML syntax (within site configuration). Additionally, rules can be set via the PSR-14 event PolicyMutatedEvent.
Mutations
- These rules can influence each other, this is where the concept of "mutations" come in. The "policy builder" of TYPO3 applies each configured mutation, no matter where it was defined.
- Because of this, each mutation (directive definition) needs a specific "mode" that can instruct, how this mutation is applied: Should an existing directive be set, inherited, appended, remove or extended to the final policy (see Content Security Police modes).
- Each directive is then applied in regard to its defined mode and can list one or more
"sources" with the values of additional parameters of a directive. Sources are
web site addresses / URLs (or just protocols), and also include some special keywords like
self
/none
/data:
.
Nonces
-
There are possible exemptions to directives for specific content created on specific pages created by TYPO3 in your frontend (or backend modules). To verify, that these exemptions are valid in a policy, a so-called "Nonce" (a unique "number used once") is created (details on https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/nonce).
- TYPO3 can manage these Nonces and apply them were configured.
- Nonces are retrieved from
\TYPO3\
and will be used for any directive within the scope of a single HTTP request.CMS\ Core\ Security\ Content Security Policy\ Consumable Nonce - More details are covered in Nonce.
Policy violations and reporting
- When a webpage with activated policies is shown in a client's browser, each HTML tag violating the policy will not be interpreted by the browser.
- Depending on a configuration of a possible "Report", such violations can be submitted back to a server and be evaluated there. TYPO3 provides such an endpoint to receive and display reports in a backend module, but also third-party servers are usable.
- Policies can be declared with "dispositions", to indicate how they are handled. "Enforce" means that a policy is in effect, and "Report" allows to only pretend a policy is in effect, to gather knowledge about possible improvements of a webpage's output. Both dispositions can be set independently in TYPO3.
- All active rules can be seen in the backend configuration section, see Active content security policy rules.
Example scenario
Let's define a small real-world scenario:
- You have one TYPO3 installation with two sites (frontend),
example.
andcom example.
.org - You have created custom backend modules for some distinct functionality.
example.
is a site where your editors fully control all frontend content, and they want to have full flexibility of what and how to embed. You use a CDN network to deliver your own large assets, like videos.com example.
is a community-driven site, where users can manage profiles and post chats, and where you want to prevent exploits on your site. Some embedding to a set of allowed web services (YouTube, Google Analytics) must be possible.org
So you need to take care of security measures, and find a pragmatic way how to allow foreign content (like YouTube, widgets, tracking codes, assets)
Specifically you want to to set these following rules, as an example.
Note
The domains listed are for demonstration only, and will not match real requirements; for example, YouTube is already allowed by the default TYPO3 frontend CSP configuration, which can be inherited.
Rules for example.com (editorial)
<iframe>
to many services should be allowed<img>
sources to anywhere should be allowed<script>
sources tocdn.
andexample. com *.
andyoutube. com *.
should be allowedgoogle. com
Rules for example.org (community)
<iframe>
tocdn.
,example. com *.
should be allowedyoutube. com <img>
sources tocdn.
andexample. com *.
should be allowedinstagram. com <script>
sources tocdn.
andexample. com *.
andyoutube. com *.
should be allowedgoogle. com
Rules for the TYPO3 backend
Normal TYPO3 backend rules need to be applied, so we only want to add some rules for custom backend modules:
<iframe>
tocdn.
should be allowedexample. com <img>
sources tocdn.
should be allowedexample. com <script>
sources tocdn.
should be allowedexample. com
Resulting configuration example:
And this is how you would do that with a CSP YAML configuration file, one per site:
# Inherits default frontend policy mutations provided by Core and 3rd-party extensions (enabled per default)
inheritDefault: true
mutations:
# Allow frames/iframes to TRUSTED specific locations
# Avoid "protocol only" white-list like "https:" here,
# because it could inject javascript easily, the most important reason
# why CSP was invented was to block security issues like this.
# (Note: it's "frame-src" not "iframe-src")
- mode: "extend"
directive: "frame-src"
sources:
- "https://*.example.org"
- "https://*.example.com"
- "https://*.instagram.com"
- "https://*.vimeo.com"
- "https://*.youtube.com"
# Allow img src to anyhwere (HTTPS only, not HTTP)
- mode: "extend"
directive: "img-src"
sources:
- "https:"
# Allow script src to the specified domains (HTTPS only)
- mode: "extend"
directive: "script-src"
sources:
- "https://cdn.example.com"
- "https://*.youtube.com"
- "https://*.google.com"
# Inherits default frontend policy mutations provided by Core and 3rd-party extensions (enabled per default)
inheritDefault: true
mutations:
# Allow frame/iframe src to the specified domains (HTTPS only)
- mode: "extend"
# (Note: it's "frame-src" not "iframe-src")
directive: "frame-src"
sources:
- "https://cdn.example.com"
- "https://*.youtube.com"
# Allow img src to the specified domains (HTTPS only)
- mode: "extend"
directive: "img-src"
sources:
- "https://cdn.example.com"
- "https://*.instagram.com"
# Allow script src to the specified domains (HTTPS only)
- mode: "extend"
directive: "script-src"
sources:
- "https://cdn.example.com"
- "https://*.youtube.com"
- "https://*.google.com"
<?php
declare(strict_types=1);
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Directive;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Mutation;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\MutationCollection;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\MutationMode;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Scope;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\UriValue;
use TYPO3\CMS\Core\Type\Map;
return Map::fromEntries([
// Provide declarations for the backend only
Scope::backend(),
new MutationCollection(
new Mutation(
MutationMode::Extend,
// Note: it's "FrameSrc" not "IFrameSrc"
Directive::FrameSrc,
new UriValue('https://cdn.example.com'),
),
new Mutation(
MutationMode::Extend,
Directive::ImgSrc,
new UriValue('https://cdn.example.com'),
),
new Mutation(
MutationMode::Extend,
Directive::ScriptSrc,
new UriValue('https://cdn.example.com'),
),
),
]);
This is really just a simple demo, that has room for improvements. For example,
the allowed list of *-
values to any directive could actually be set through their common
parent, the default-
attribute. There is a very deep and nested possibility
to address the attributes of many HTML5 tags, which is covered in depth on
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy#directives.
You can take a look into the PHP enum
\TYPO3\
,
which gives you an overview of all supported directives.
Read on to understand more of the underlying API builder concepts below.
Configuration
Policy builder approach
The following approach illustrates how a policy is build:
<?php
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Directive;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Policy;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\SourceKeyword;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\SourceScheme;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\UriValue;
use TYPO3\CMS\Core\Security\Nonce;
$nonce = Nonce::create();
$policy = (new Policy())
// Results in `default-src 'self'`
->default(SourceKeyword::self)
// Extends the ancestor directive ('default-src'),
// thus reuses 'self' and adds additional sources
// Results in `img-src 'self' data: https://*.typo3.org`
->extend(Directive::ImgSrc, SourceScheme::data, new UriValue('https://*.typo3.org'))
// Extends the ancestor directive ('default-src'),
// thus reuses 'self' and adds additional sources
// Results in `script-src 'self' 'nonce-[random]'`
// ('nonce-proxy' is substituted when compiling the policy)
->extend(Directive::ScriptSrc, SourceKeyword::nonceProxy)
// Sets (overrides) the directive,
// thus ignores 'self' of the 'default-src' directive
// Results in `worker-src blob:`
->set(Directive::WorkerSrc, SourceScheme::blob);
header('Content-Security-Policy: ' . $policy->compile($nonce));
The result of the compiled and serialized result as HTTP header would look similar to this (the following sections are using the same example, but utilize different techniques for the declarations):
Content-Security-Policy: default-src 'self';
img-src 'self' data: https://*.typo3.org; script-src 'self' 'nonce-[random]';
worker-src blob:
Note
The policy builder is the low-level representation and interaction in PHP,
any other configuration is using the same verbs to describe the CSP
instructions. The
\TYPO3\
object is used for compiling the CSP in a middleware.
Thus, custom controllers or middlewares could use this approach; the last
line
header('Content-Security-Policy: ' . $policy->compile($nonce));
is an example to show the basic principle without having to explain PSR-7/PSR-15 details.
For project integrations, the "mutations" (via configuration, YAML, resolutions in the UI or events) shall be used.
Extension-specific
Policies for frontend and backend can be applied automatically by providing a
Configuration/ContentSecurityPolicies.php
file in an extension, for
example:
<?php
declare(strict_types=1);
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Directive;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Mutation;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\MutationCollection;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\MutationMode;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Scope;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\SourceKeyword;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\SourceScheme;
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\UriValue;
use TYPO3\CMS\Core\Type\Map;
return Map::fromEntries(
[
// Provide declarations for the backend
Scope::backend(),
// NOTICE: When using `MutationMode::Set` existing declarations will be overridden
new MutationCollection(
// Results in `default-src 'self'`
new Mutation(
MutationMode::Set,
Directive::DefaultSrc,
SourceKeyword::self,
),
// Extends the ancestor directive ('default-src'),
// thus reuses 'self' and adds additional sources
// Results in `img-src 'self' data: https://*.typo3.org`
new Mutation(
MutationMode::Extend,
Directive::ImgSrc,
SourceScheme::data,
new UriValue('https://*.typo3.org'),
),
// NOTICE: the following two instructions for `Directive::ImgSrc` are identical to the previous instruction,
// `MutationMode::Extend` is a shortcut for `MutationMode::InheritOnce` and `MutationMode::Append`
// new Mutation(MutationMode::InheritOnce, Directive::ImgSrc, SourceScheme::data),
// new Mutation(MutationMode::Append, Directive::ImgSrc, SourceScheme::data, new UriValue('https://*.typo3.org')),
// Extends the ancestor directive ('default-src'),
// thus reuses 'self' and adds additional sources
// Results in `script-src 'self' 'nonce-[random]'`
// ('nonce-proxy' is substituted when compiling the policy)
new Mutation(
MutationMode::Extend,
Directive::ScriptSrc,
SourceKeyword::nonceProxy,
),
// Sets (overrides) the directive,
// thus ignores 'self' of the 'default-src' directive
// Results in `worker-src blob:`
new Mutation(
MutationMode::Set,
Directive::WorkerSrc,
SourceScheme::blob,
),
),
],
[
// You can also additionally provide frontend declarations
Scope::frontend(),
new MutationCollection(
// Sets (overrides) the directive,
// thus ignores 'self' of the 'default-src' directive
// Results in `worker-src https://*.workers.example.com:`
new Mutation(
MutationMode::Set,
Directive::WorkerSrc,
new UriValue('https://*.workers.example.com'),
),
),
],
);
The API here is much like the YAML syntax. The PHP code needs to return
a mapped array of an
Mutation
instance with all rules
put into a sub-array, containing instances of a single
Mutation
.
Each
Mutation
instance is like a Data Object (DO) where its constructor
allows you to specifiy a mode
(type
Mutation
), a directive
(type
Directive
) and one ore more actual values ("sources", type
Uri
or Source
).
Additionally, a
Scope
instance object is included, which can either
be
Scope::
or
Scope::
.
A good PHP IDE will allow for good autocompletion and hinting, and using a boilerplate configuration like the example above helps you to get started.
Backend-specific
The YAML configuration only applies to the frontend part of TYPO3. Backend policies need to be set using the PHP API, within an extension as described in the section above.
You need to ensure that Scope::
is set in the mapped return array
for the rules you want to setup.
Site-specific (frontend)
In frontend, a dedicated sites/<my_
can be
used to declare policies for a specific site, for example:
# Inherits default site-unspecific frontend policy mutations (enabled per default)
inheritDefault: true
mutations:
# Results in `default-src 'self'`
- mode: "set"
directive: "default-src"
sources:
- "'self'"
# Extends the ancestor directive ('default-src'),
# thus reuses 'self' and adds additional sources
# Results in `img-src 'self' data: https://*.typo3.org`
- mode: "extend"
directive: "img-src"
sources:
- "data:"
- "https://*.typo3.org"
# Extends the ancestor directive ('default-src'),
# thus reuses 'self' and adds additional sources
# Results in `script-src 'self' 'nonce-[random]'`
# ('nonce-proxy' is substituted when compiling the policy)
- mode: "extend"
directive: "script-src"
sources:
- "'nonce-proxy'"
# Results in `worker-src blob:`
- mode: "set"
directive: "worker-src"
sources:
- "blob:"
Disable CSP for a site
The Content Security Policy for a particular site can be disabled with the
active
key set to
false
:
Site-specific Content-Security-Policy endpoints
The reporting endpoint is used to receive browser reports about violations to the security policy, for example if a YouTube URL was requested, but could not be displayed in an iframe due to a directive not allowing this.
Reports like this can help to gain insight, what URLs are used by editors and might need inclusion into the policy.
Since reports can be sent by any browser, they can possibly easily flood
a site with requests and take up storage space. Reports are stored in the
sys_
database table when using the endpoint provided by TYPO3.
To influence whether this endpoint accepts reports,
the disposition-specific property reporting
can be configured and
set to either:
true
- to enable the reporting endpoint
false
- to disable the reporting endpoint
- (string)
- to use the given value as external reporting endpoint
If defined, the site-specific configuration takes precedence over the global configuration contentSecurityPolicyReportingUrl.
In case the explicitly disabled endpoint still would be called, the server-side process responds with a 403 HTTP error message.
Changed in version 12.4.27 / 13.4.5
This reporting
setting has been introduced with
Important: #105856 - Allow site-specific Content-Security-Policy endpoints.
Example: Disabling the reporting endpoint
Example: Using custom external reporting endpoint
Content Security Police modes
Adjusting specific directives / mutations for a policy can be performed via the following modes:
append
-
- YAML
append
- PHP
\TYPO3\
CMS\ Core\ Security\ Content Security Policy\ Mutation Mode:: Append
Appends to a given directive.
Example:
config/sites/<my_site>/csp.yaml | typo3conf/sites/<my_site>/csp.yamlmutations: - mode: "set" directive: "default-src" sources: - "'self'" - mode: "set" directive: "img-src" sources: - "example.org" - mode: "append" directive: "img-src" sources: - "example.com"
EXT:my_extension/Configuration/ContentSecurityPolicies.php<?php declare(strict_types=1); use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Directive; use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Mutation; use TYPO3\CMS\Core\Security\ContentSecurityPolicy\MutationCollection; use TYPO3\CMS\Core\Security\ContentSecurityPolicy\MutationMode; use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Scope; use TYPO3\CMS\Core\Security\ContentSecurityPolicy\SourceKeyword; use TYPO3\CMS\Core\Security\ContentSecurityPolicy\UriValue; use TYPO3\CMS\Core\Type\Map; return Map::fromEntries([ Scope::frontend(), new MutationCollection( new Mutation( MutationMode::Set, Directive::DefaultSrc, SourceKeyword::self, ), new Mutation( MutationMode::Set, Directive::ImgSrc, new UriValue('example.org'), ), new Mutation( MutationMode::Append, Directive::ImgSrc, new UriValue('example.com'), ), ), ]);
Results in:
Content-Security-Policy: default-src 'self'; img-src example.org example.com
Copied!
extend
-
- YAML
extend
- PHP
\TYPO3\
CMS\ Core\ Security\ Content Security Policy\ Mutation Mode:: Extend
Extends the given directive. It is a shortcut for inherit-once and append.
Example:
config/sites/<my_site>/csp.yaml | typo3conf/sites/<my_site>/csp.yamlmutations: - mode: "set" directive: "default-src" sources: - "'self'" - mode: "extend" directive: "img-src" sources: - "example.com"
EXT:my_extension/Configuration/ContentSecurityPolicies.php<?php declare(strict_types=1); use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Directive; use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Mutation; use TYPO3\CMS\Core\Security\ContentSecurityPolicy\MutationCollection; use TYPO3\CMS\Core\Security\ContentSecurityPolicy\MutationMode; use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Scope; use TYPO3\CMS\Core\Security\ContentSecurityPolicy\SourceKeyword; use TYPO3\CMS\Core\Security\ContentSecurityPolicy\UriValue; use TYPO3\CMS\Core\Type\Map; return Map::fromEntries([ Scope::frontend(), new MutationCollection( new Mutation( MutationMode::Set, Directive::DefaultSrc, SourceKeyword::self, ), new Mutation( MutationMode::Extend, Directive::ImgSrc, new UriValue('example.com'), ), ), ]);
Results in:
Content-Security-Policy: default-src 'self'; img-src 'self' example.com
Copied!
inherit-again
-
- YAML
inherit-
again - PHP
\TYPO3\
CMS\ Core\ Security\ Content Security Policy\ Mutation Mode:: Inherit Again
Inherits again from the corresponding ancestor chain and merges existing sources.
Example:
config/sites/<my_site>/csp.yaml | typo3conf/sites/<my_site>/csp.yamlinheritDefault: false mutations: - mode: "set" directive: "default-src" sources: - "'self'" - mode: "inherit-again" directive: "img-src" - mode: "append" directive: "img-src" sources: - "example.com" - mode: "set" directive: "default-src" sources: - "data:" - mode: "inherit-again" directive: "img-src"
EXT:my_extension/Configuration/ContentSecurityPolicies.php<?php declare(strict_types=1); use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Directive; use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Mutation; use TYPO3\CMS\Core\Security\ContentSecurityPolicy\MutationCollection; use TYPO3\CMS\Core\Security\ContentSecurityPolicy\MutationMode; use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Scope; use TYPO3\CMS\Core\Security\ContentSecurityPolicy\SourceKeyword; use TYPO3\CMS\Core\Security\ContentSecurityPolicy\SourceScheme; use TYPO3\CMS\Core\Security\ContentSecurityPolicy\UriValue; use TYPO3\CMS\Core\Type\Map; return Map::fromEntries([ Scope::frontend(), new MutationCollection( new Mutation( MutationMode::Set, Directive::DefaultSrc, SourceKeyword::self, ), new Mutation( MutationMode::InheritAgain, Directive::ImgSrc, ), new Mutation( MutationMode::Append, Directive::ImgSrc, new UriValue('example.com'), ), new Mutation( MutationMode::Set, Directive::DefaultSrc, SourceScheme::data, ), new Mutation( MutationMode::InheritAgain, Directive::ScriptSrc, ), ), ]);
Results in:
Content-Security-Policy: default-src data:; img-src data: 'self' example.com
Copied!Note that
data:
is inherited toimg-
(in opposite to inherit-once).src
inherit-once
-
- YAML
inherit-
once - PHP
\TYPO3\
CMS\ Core\ Security\ Content Security Policy\ Mutation Mode:: Inherit Once
Inherits once from the corresponding ancestor chain. When
inherit-
is called multiple times on the same directive, only the first time is applied.once Example:
config/sites/<my_site>/csp.yaml | typo3conf/sites/<my_site>/csp.yamlinheritDefault: false mutations: - mode: "set" directive: "default-src" sources: - "'self'" - mode: "inherit-once" directive: "img-src" - mode: "append" directive: "img-src" sources: - "example.com" - mode: "set" directive: "default-src" sources: - "data:" - mode: "inherit-once" directive: "img-src"
EXT:my_extension/Configuration/ContentSecurityPolicies.php<?php declare(strict_types=1); use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Directive; use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Mutation; use TYPO3\CMS\Core\Security\ContentSecurityPolicy\MutationCollection; use TYPO3\CMS\Core\Security\ContentSecurityPolicy\MutationMode; use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Scope; use TYPO3\CMS\Core\Security\ContentSecurityPolicy\SourceKeyword; use TYPO3\CMS\Core\Security\ContentSecurityPolicy\SourceScheme; use TYPO3\CMS\Core\Security\ContentSecurityPolicy\UriValue; use TYPO3\CMS\Core\Type\Map; return Map::fromEntries([ Scope::frontend(), new MutationCollection( new Mutation( MutationMode::Set, Directive::DefaultSrc, SourceKeyword::self, ), new Mutation( MutationMode::InheritOnce, Directive::ImgSrc, ), new Mutation( MutationMode::Append, Directive::ImgSrc, new UriValue('example.com'), ), new Mutation( MutationMode::Set, Directive::DefaultSrc, SourceScheme::data, ), new Mutation( MutationMode::InheritOnce, Directive::ImgSrc, ), ), ]);
Results in:
Content-Security-Policy: default-src data:; img-src 'self' example.com
Copied!Note that
data:
is not inherited toimg-
. If you want to inherit alsosrc data:
toimg-
use inherit-again.src
reduce
-
- YAML
reduce
- PHP
\TYPO3\
CMS\ Core\ Security\ Content Security Policy\ Mutation Mode:: Reduce
Reduces a directive by a given aspect.
Example:
config/sites/<my_site>/csp.yaml | typo3conf/sites/<my_site>/csp.yamlmutations: - mode: "set" directive: "img-src" sources: - "'self'" - "data:" - "example.com" - mode: "reduce" directive: "img-src" sources: - "data:"
EXT:my_extension/Configuration/ContentSecurityPolicies.php<?php declare(strict_types=1); use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Directive; use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Mutation; use TYPO3\CMS\Core\Security\ContentSecurityPolicy\MutationCollection; use TYPO3\CMS\Core\Security\ContentSecurityPolicy\MutationMode; use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Scope; use TYPO3\CMS\Core\Security\ContentSecurityPolicy\SourceKeyword; use TYPO3\CMS\Core\Security\ContentSecurityPolicy\SourceScheme; use TYPO3\CMS\Core\Security\ContentSecurityPolicy\UriValue; use TYPO3\CMS\Core\Type\Map; return Map::fromEntries([ Scope::frontend(), new MutationCollection( new Mutation( MutationMode::Set, Directive::ImgSrc, SourceKeyword::self, SourceScheme::data, new UriValue('example.com'), ), new Mutation( MutationMode::Reduce, Directive::ImgSrc, SourceScheme::data, ), ), ]);
Results in:
Content-Security-Policy: default-src 'self' example.com
Copied!
remove
-
- YAML
remove
- PHP
\TYPO3\
CMS\ Core\ Security\ Content Security Policy\ Mutation Mode:: Remove
Removes a directive completely.
Example:
config/sites/<my_site>/csp.yaml | typo3conf/sites/<my_site>/csp.yamlmutations: - mode: "set" directive: "default-src" sources: - "'self'" - mode: "set" directive: "img-src" sources: - "data:" - mode: "remove" directive: "img-src"
EXT:my_extension/Configuration/ContentSecurityPolicies.php<?php declare(strict_types=1); use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Directive; use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Mutation; use TYPO3\CMS\Core\Security\ContentSecurityPolicy\MutationCollection; use TYPO3\CMS\Core\Security\ContentSecurityPolicy\MutationMode; use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Scope; use TYPO3\CMS\Core\Security\ContentSecurityPolicy\SourceKeyword; use TYPO3\CMS\Core\Security\ContentSecurityPolicy\SourceScheme; use TYPO3\CMS\Core\Type\Map; return Map::fromEntries([ Scope::frontend(), new MutationCollection( new Mutation( MutationMode::Set, Directive::DefaultSrc, SourceKeyword::self, ), new Mutation( MutationMode::Set, Directive::ImgSrc, SourceScheme::data, ), new Mutation( MutationMode::Remove, Directive::ImgSrc, ), ), ]);
Results in:
Content-Security-Policy: default-src 'self'
Copied!
set
-
- YAML
set
- PHP
\TYPO3\
CMS\ Core\ Security\ Content Security Policy\ Mutation Mode:: Set
Sets (overrides) a directive completely.
Example:
config/sites/<my_site>/csp.yaml | typo3conf/sites/<my_site>/csp.yamlEXT:my_extension/Configuration/ContentSecurityPolicies.php<?php declare(strict_types=1); use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Directive; use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Mutation; use TYPO3\CMS\Core\Security\ContentSecurityPolicy\MutationCollection; use TYPO3\CMS\Core\Security\ContentSecurityPolicy\MutationMode; use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Scope; use TYPO3\CMS\Core\Security\ContentSecurityPolicy\SourceKeyword; use TYPO3\CMS\Core\Type\Map; return Map::fromEntries([ Scope::frontend(), new MutationCollection( new Mutation( MutationMode::Set, Directive::ImgSrc, SourceKeyword::self, ), ), ]);
Results in:
Content-Security-Policy: img-src 'self'
Copied!
Nonce
The nonce attribute is useful to allowlist specific elements, such as a particular inline script or style elements. It can help you to avoid using the CSP unsafe-inline directive, which would allowlist all inline scripts or styles.
-- MDN Web Docs, https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/nonce
It may look like this in your HTML code:
<link
rel="stylesheet"
href="/_assets/af46f1853e4e259cbb8ebcb816eb0403/Css/styles.css?1687696548"
media="all"
nonce="sqK8LkqFp-aWHc7jkHQ4aT-RlUp5cde9ZW0F0-BlrQbExX-PRMoTkw"
>
<style nonce="sqK8LkqFp-aWHc7jkHQ4aT-RlUp5cde9ZW0F0-BlrQbExX-PRMoTkw">
/* some inline styles */
</style>
<script
src="/_assets/27334a649e36d0032b969fa8830590c2/JavaScript/scripts.js?1684880443"
nonce="sqK8LkqFp-aWHc7jkHQ4aT-RlUp5cde9ZW0F0-BlrQbExX-PRMoTkw"
></script>
<script nonce="sqK8LkqFp-aWHc7jkHQ4aT-RlUp5cde9ZW0F0-BlrQbExX-PRMoTkw">
/* some inline JavaScript */
</script>
The nonce changes with each request so that (possibly malicious) inline scripts or styles are blocked by the browser.
The nonce is applied automatically, when scripts or styles are defined with the
TYPO3 API, like TypoScript (
page.
, etc.) or the
asset collector. This only refers to referenced files
(via
src
and
href
attributes) and not inline scripts
or inline styles. For those, you should either use the PHP/Fluid approach
as listed below, or use TypoScript only for passing DOM attributes
and using external scripts to actually evaluate these attributes to control
functionality.
TYPO3 provides APIs to get the nonce for the current request:
Retrieve with PHP
The nonce can be retrieved via the nonce request attribute:
// use TYPO3\CMS\Core\Domain\ConsumableString
/** @var ConsumableString|null $nonce */
$nonceAttribute = $this->request->getAttribute('nonce');
if ($nonceAttribute instanceof ConsumableString) {
$nonce = $nonceAttribute->consume();
}
In a Fluid template
The f:security.nonce ViewHelper is available, which provides the nonce in a Fluid template, for example:
<script nonce="{f:security.nonce()}">
const inline = 'script';
</script>
<style nonce="{f:security.nonce()}">
.some-style { color: red; }
</style>
You can also use the f:asset.script
or f:asset.css
ViewHelpers with the use
attribute:
<f:asset.script identifier="my-inline-script" useNonce="1">
const inline = 'script';
</f:asset.script>
<f:asset.css identifier="my-inline-style" useNonce="1">
.some-style { color: red; }
</f:asset.css>
Notes about nonces and caching
Nonces are implemented via a PSR middleware and thus applied dynamically. This also means, they are somewhat "bad" for caching (especially for reverse proxies), since they create unique output for a specific visitor.
Since the goal of nonces are to allow "exemptions" for otherwise forbidden content, this closely relates to validity or integrity of this forbidden content. Instead of emitting unique nonces, another possibility is to utilize hashing functionality to content regarded as "safe".
This can be done with sha256/sha384/sha512 hashing of referenced script, and including them as a valid directive, like this:
The "sha256-..." block would be the SHA256 hash created from a file like 'script.js'.
For example, a file like this:
console.log('Hello.');
would correspond to a SHA256 hash of 6c7d3c1bf856597a2c8ae2ca7498cb4454a32286670b20cf36202fa578b491a9
.
Note
These hashes can be created by shell scripts like sha256
and several libraries,
also in nodeJS bundling tools.
The browser would evaluate a reference JavaScript file and calculate it's SHA256 hash and compare it to the list of allowed hashes.
The downside of this is: Everytime an embedded file changes (like via build processes), the CSP SHA hash would need to be adopted. This could be automated by a PHP definition of CSP rules and hashing files automatically, which would be a performance-intense process and call for its own caching.
There is no automatism for this kind of hashing in TYPO3 (yet, see https://forge.typo3.org/issues/100887), so it has to be done manually as outlined above.
Reporting of violations, CSP Backend module
Potential CSP violations are reported back to the TYPO3 system and persisted
internally in the database table
sys_
. A corresponding
Admin Tools > Content Security Policy backend module supports users
to keep track of recent violations and - if applicable - to select potential
resolutions (stored in the database table
sys_
) which
extends the Content Security Policy for the given scope during runtime:

Backend module "Content Security Policy" which displays the violations
Clicking on a row displays the details of this violation on the right side including suggestions on how to resolve this violation. You have the choice to apply this suggestion, or to mute or delete the specific violation.
Note
If you apply the suggestion, it is stored in the database table
sys_
. To have all policies in one place, you
should consider adding the suggestion to your
extension-specific or
site-specific CSP definitions
manually.
Warning
Resolutions, once applied, can not be removed again via the GUI. You would
need to manually remove entries in the
sys_
database
table.
Using a third-party service
As an alternative to the built-in reporting module, an external reporting URL can be configured to use a third-party service as well:
// For backend
$GLOBALS['TYPO3_CONF_VARS']['BE']['contentSecurityPolicyReportingUrl']
= 'https://csp-violation.example.org/';
// For frontend
$GLOBALS['TYPO3_CONF_VARS']['FE']['contentSecurityPolicyReportingUrl']
= 'https://csp-violation.example.org/';
Violations are then sent to the third-party service instead of the TYPO3 endpoint. Resolutions would then not be applied dynamically.
Disabling content security policy reporting globally
Administrators can disable the reporting endpoint globally or configure it per site as needed. (See Example: Disabling the reporting endpoint).
If defined, the site-specific configuration takes precedence over the global configuration.
In case the explicitly disabled endpoint still would be called, the server-side process responds with a 403 HTTP error message.
The global scope-specific setting content
can
be set to zero ('0') to disable the CSP reporting endpoint:
// For backend
$GLOBALS['TYPO3_CONF_VARS']['BE']['contentSecurityPolicyReportingUrl'] = '0';
// For frontend
$GLOBALS['TYPO3_CONF_VARS']['FE']['contentSecurityPolicyReportingUrl'] = '0';
Active content security policy rules
The backend module System > Configuration > Content Security Policy Mutations uses a simple tree display of all configured directives, grouped by frontend or backend. Each rule shows where it is defined, and what its final policy is set to:

Backend module "Configuration > Content Security Policy Mutations" which displays a tree of all policy directives.
PSR-14 events
The following PSR-14 events are available: