- Issue created by @undersound3
- 🇨🇦Canada gapple
I don't think you would want to add the nonce to the
dataLayer
, but you can access the nonce server-side through thecsp.nonce
service, or in scripts withdrupalSettings.csp.nonce
by adding thecsp.nonce
library as a dependency. - 🇳🇱Netherlands undersound3
@gapple thanks for your response.
Could you elaborate as to why I probably wouldn't want to add a nonce to this script?
Is this not necessary because it is not a security thread or are their other reasons? - 🇨🇦Canada gapple
I think I misunderstood - I thought you had meant adding the nonce as a property to the JS
dataLayer
object.The module's documentation has an example of how to add a nonce to a render element → . In this case since datalayer is already rendering in a lazybuilder, it doesn't have to (shouldn't?) use CSP's nonce lazybuilder.
I'm pretty sure#attached
gets bubbled up from lazy-built elements for CSP to have access to in it's response processor, but that would have to be double-checked.if (\Drupal::service('module_handler')->moduleExists('csp')) { $element['#attributes']['nonce'] = \Drupal::service('csp.nonce')->getValue(); $element['#attached']['csp_nonce'] = [ 'script' => [Csp::POLICY_UNSAFE_INLINE], ]; }
This requires CSP >= 2.1.0, so the module should also add a conflict to its composer.json to ensure it's not installed with an older version that would cause core to through an exception when encountering
['#attached']['csp_nonce']
:"conflict": { "drupal/csp": "<2.1.0-beta1" }
- 🇳🇱Netherlands undersound3
You are correct, I meant adding the nonce to the inline script tag generated by this function https://git.drupalcode.org/project/datalayer/-/blob/2.0.x/src/DatalayerL...
So in the header I want
Content-Security-Policy: script-src 'nonce-2726c7f26c'
And I want the inline script tag from the datalayer module to look like:
<script nonce="2726c7f26c">window.dataLayer = window.dataLayer || []; window.dataLayer.push({"drupalLanguage"....</script>
What I am unsure about is where to apply the code example you provided.
Let's say I don't want to patch the datalayer module for now but want use a hook in my custom module to add the nonce.
What would be the hook to use? hook_element_info_alter?
Do I need to create a subscriber to add the nonce to the header or will the CSP module add this?Thanks in advance for any guidance..
- 🇨🇦Canada gapple
You could decorate datalayer's lazy builder class to alter the element when it's being built.
mymodule.services.yml
datalayer.lazy_builders.mymodule: public: false class: Drupal\mymodule\DatalayerBuilder decorates: datalayer.lazy_builders arguments: ['@datalayer.lazy_builders.inner', '@csp.nonce_builder']
mymodule/src/DatalayerBuilder.php
namespace Drupal\mymodule; use Drupal\csp\Csp; use Drupal\csp\NonceBuilder; use Drupal\datalayer\DatalayerLazyBuilders; use Drupal\Core\Security\Attribute\TrustedCallback; class DatalayerBuilder { public function __construct( protected DatalayerLazyBuilders $decorated, protected NonceBuilder $nonce_builder ) { } #[TrustedCallback] public function build() { $build = $decorated->lazyScriptTag(); $placeholderKey = $nonce_builder->getPlaceholderKey(); $build['datalayer']['#attributes']['nonce'] = $placeholderKey; $build['datalayer']['#attached']['csp_nonce'] = [ 'script' => [Csp::POLICY_UNSAFE_INLINE], ]; $build['datalayer']['#attached']['placeholders'][$placeholderKey] = [ '#lazy_builder' => ['csp.nonce_builder:renderNonce', []], ]; return $build; } }
----
Setting
$build['datalayer']['#attached']['csp_nonce']
tells CSP module that it needs to add the nonce generated for each request to the header if possible, and the placeholder in the script's attribute will be replaced with the same value. - 🇳🇱Netherlands undersound3
Thanks for providing this suggestion. When using this example I am getting the following error:
The service "datalayer.lazy_builders" has a dependency on a non-existent service "datalayer.lazy_builders.inner".
- 🇳🇱Netherlands undersound3
Trying a bit further....
In the services file I changed the first argument to the id of the service from the custom module instead of the service id from the datalayer module.
So@datalayer.lazy_builders.inner
became@datalayer.lazy_builders.my_module.inner
Now when executing I get the following message:
LogicException: You are not allowed to use csp_nonce in #attached. in Drupal\Core\Render\HtmlResponseAttachmentsProcessor->processAttachments() (line 152 of core/lib/Drupal/Core/Render/HtmlResponseAttachmentsProcessor.php).
My files
my_module/my_module.services.yml
services: datalayer.lazy_builders.my_module: public: false class: Drupal\my_module\DatalayerBuilder decorates: datalayer.lazy_builders arguments: ['@datalayer.lazy_builders.my_module.inner', '@csp.nonce_builder']
my_module/src/DatalayerBuilder.php
<?php namespace Drupal\my_module; use Drupal\csp\Csp; use Drupal\csp\NonceBuilder; use Drupal\datalayer\DatalayerLazyBuilders; use Drupal\Core\Security\Attribute\TrustedCallback; class DatalayerBuilder { /** * @var \Drupal\datalayer\DatalayerLazyBuilders */ private DatalayerLazyBuilders $datalayerLazyBuilders; /** * @var \Drupal\csp\NonceBuilder */ private NonceBuilder $nonceBuilder; public function __construct( protected DatalayerLazyBuilders $decorated, protected NonceBuilder $nonce_builder ) { $this->datalayerLazyBuilders = $decorated; $this->nonceBuilder = $nonce_builder; } #[TrustedCallback] public function lazyScriptTag() { $build = $this->datalayerLazyBuilders->lazyScriptTag(); $placeholderKey = $this->nonceBuilder->getPlaceholderKey(); $build['datalayer']['#attributes']['nonce'] = $placeholderKey; $build['datalayer']['#attached']['csp_nonce'] = [ 'script' => [Csp::POLICY_UNSAFE_INLINE], ]; $build['datalayer']['#attached']['placeholders'][$placeholderKey] = [ '#lazy_builder' => ['csp.nonce_builder:renderNonce', []], ]; return $build; } }
- 🇨🇦Canada gapple
I wrote the code in the comment box without running it so there may be typos or bugs 😜.
🤔 decorating would currently require that the datalayer lazy builder be executed before
\Drupal\Core\Render\HtmlResponseAttachmentsProcessor::processAttachments()
, so that CSP's decorator of that class can hide it's own '#attached' properties that core doesn't like, but it looks like that method is actually executing render element lazy builders itself.
It may require ✨ Allow additional keys in #attached Active in core to work, so that core ignores the['#attached']['csp_nonce']
In that case, it's probably necessary to either:
a) patch core with the above changeb)
Use another method than$build['datalayer']['#attached']['csp_nonce']
to add the nonce to the header.
If you know datalayer is on every page, you could have your own subscriber to theCspEvents::POLICY_ALTER
event always call\Drupal::service('csp.policy_helper')->appendNonce($policy, 'script', [Csp::POLICY_UNSAFE_INLINE]);
or c)
usehook_page_bottom()
, weighted to ensure it's executed afterdatalayer_page_bottom()
, to add the attribute and#attached
values so they exist before the final page rendering flow is started. - 🇳🇱Netherlands undersound3
I wrote the code in the comment box without running it so there may be typos or bugs 😜.
I was not expecting you to do so and was assuming that 😜 Very thankful for it anyways, it helps a lot trying to understand the workings of this.
Thanks for these suggestions. Tested option A) quickly by uncommenting the code in https://git.drupalcode.org/project/drupal/-/blob/11.x/core/lib/Drupal/Co... and that works indeed. I am using 10.4.1 btw.
Will ponder upon the other suggestion and see what best fits.
Thanks again!
- Status changed to Fixed
10 days ago 7:38am 1 April 2025