Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CSP issue with inline scripts on checkout, after applying the 2.4.6-p6 patch #87

Open
redo-interactive opened this issue Jun 19, 2024 · 8 comments

Comments

@redo-interactive
Copy link

redo-interactive commented Jun 19, 2024

After applying latest security patch from Adobe Commerce/Magento there is an issue on checkout, with inline script in

view/frontend/templates/js.phtml

There is a need to use
$secureRenderer->renderTag to generate script with unique nonce.

Magento version #:

2.4.7-p1, 2.4.6-p6, 2.4.5-p8, 2.4.4-p9

Edition (EE, CE, OS, etc):

EE, CE, OS

Expected behavior:

js scripts won't break execution on checkout.

Actual behavior:

js scripts are breaking execution on checkout.

Steps to reproduce:

add product to the cart and go to checkout

Preconditions

M2/AC 2.4.6-p6, PHP 8.1

I have created a fix for this:

<?php
/**
 * Copyright © MagePal LLC. All rights reserved.
 * See COPYING.txt for license details.
 * http://www.magepal.com | support@magepal.com
 */

/** @var $block MagePal\GoogleTagManager\Block\DataLayer **/
$dataLayerName = $escaper->escapeJs($block->getDataLayerName());
$accountId = $escaper->escapeJs($block->getAccountId());
$containerCode = $block->getEmbeddedCode() ? "+'{$block->getEmbeddedCode()}'" : '';

$gtmScript = <<<SCRIPT
    window.{$dataLayerName} = window.{$dataLayerName} || [];
    SCRIPT;

if (!$block->isGdprEnabled() && $block->addJsInHead() && !$block->isAdvancedSettingsEnabled()) {
    $dataLayerJs = $block->getDataLayerJs();
    $gtmScript .= <<<SCRIPT
            {$dataLayerJs}
            (function(w,d,s,l,i){
            w[l]=w[l]||[];
            w[l].push({'gtm.start': new Date().getTime(),event:'gtm.js'});
            var f=d.getElementsByTagName(s)[0],
                j=d.createElement(s),
                dl=l!='{$dataLayerName}'?'&l='+l:'';
            j.async=true;
            j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl'{$containerCode}';
            f.parentNode.insertBefore(j,f);
        })(window,document,'script','{$dataLayerName}','{$accountId}');
        SCRIPT;
}

if ($block->isAdvancedSettingsEnabled()) {
    $advancedSettingsJsCode = $block->getAdvancedSettingsJsCode();
    $gtmScript .= <<<SCRIPT
            {$dataLayerJs}
            {$advancedSettingsJsCode}
            SCRIPT;
}

// phpcs:ignore
echo /* @noEscape */ $secureRenderer->renderTag('script', [], $gtmScript, false); ?>

<?php if (($block->isGdprEnabled() || !$block->addJsInHead()) && !$block->isAdvancedSettingsEnabled()): ?>
    <script type="text/x-magento-init">
        {
            "*": {
                "magepalGtmDatalayer": {
                    "isCookieRestrictionModeEnabled": <?= /* @noEscape */ $block->isCookieRestrictionModeEnabled() ?>,
                    "currentWebsite": <?= /* @noEscape */ $block->getCurrentWebsiteId() ?>,
                    "cookieName": "<?= /* @noEscape */ $block->getCookieRestrictionName() ?>",
                    "dataLayer": "<?= /* @noEscape */ $block->getDataLayerName() ?>",
                    "accountId": "<?= /* @noEscape */ $block->getAccountId() ?>",
                    "data": <?= /* @noEscape */ $block->getDataLayerJson() ?>,
                    "isGdprEnabled": <?= /* @noEscape */ $block->isGdprEnabled() ?>,
                    "gdprOption": <?= /* @noEscape */ $block->getGdprOption() ?>,
                    "addJsInHeader": <?= /* @noEscape */ $block->addJsInHead() ?>,
                    "containerCode": "<?= /* @noEscape */ $block->getEmbeddedCode() ?>"
                }
            }
        }
    </script>
<?php else : ?>
    <script type="text/x-magento-init">
        {
            "*": {
                "magepalGtmDatalayer": {
                    "dataLayer": "<?= /* @noEscape */ $block->getDataLayerName() ?>"
            }
        }
    }
    </script>
<?php endif; ?>
<!-- End Google Tag Manager by MagePal -->
@redo-interactive
Copy link
Author

redo-interactive commented Jun 20, 2024

the patch for current version of this file:

diff --git a/view/frontend/templates/js.phtml b/view/frontend/templates/js.phtml
index b6d42fe..c37a269 100755
--- a/view/frontend/templates/js.phtml
+++ b/view/frontend/templates/js.phtml
@@ -6,60 +6,70 @@
  */
 
 /** @var $block MagePal\GoogleTagManager\Block\DataLayer **/
-$dataLayerName = $block->getDataLayerName();
-$accountId = $block->getAccountId();
-$containerCode = $block->getEmbeddedCode();
-?>
+$dataLayerName = $escaper->escapeJs($block->getDataLayerName());
+$accountId = $escaper->escapeJs($block->getAccountId());
+$containerCode = $block->getEmbeddedCode() ? "+'{$block->getEmbeddedCode()}'" : '';
 
-<!-- Google Tag Manager by MagePal -->
-<script type="text/javascript">
-    window.<?= /* @noEscape */ $dataLayerName ?> = window.<?= /* @noEscape */ $dataLayerName ?> || [];
+$gtmScript = <<<SCRIPT
+    window.{$dataLayerName} = window.{$dataLayerName} || [];
+    SCRIPT;
 
-<?php if (!$block->isGdprEnabled() && $block->addJsInHead() && !$block->isAdvancedSettingsEnabled()): ?>
-    <?= /* @noEscape */ $block->getDataLayerJs() ?>
-    (function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
-            new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
-        j=d.createElement(s),dl=l!='<?= /* @noEscape */ $dataLayerName ?>'?'&l='+l:'';j.async=true;j.src=
-        'https://www.googletagmanager.com/gtm.js?id='+i+dl<?= /* @noEscape */ $containerCode ? "+'{$containerCode}'" : '' ?>;f.parentNode.insertBefore(j,f);
-    })(window,document,'script','<?= /* @noEscape */ $dataLayerName ?>','<?= /* @noEscape */ $accountId ?>');
-<?php endif; ?>
-</script>
+if (!$block->isGdprEnabled() && $block->addJsInHead() && !$block->isAdvancedSettingsEnabled()) {
+    $dataLayerJs = $block->getDataLayerJs();
+    $gtmScript .= <<<SCRIPT
+            {$dataLayerJs}
+            (function(w,d,s,l,i){
+            w[l]=w[l]||[];
+            w[l].push({'gtm.start': new Date().getTime(),event:'gtm.js'});
+            var f=d.getElementsByTagName(s)[0],
+                j=d.createElement(s),
+                dl=l!='{$dataLayerName}'?'&l='+l:'';
+            j.async=true;
+            j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl'{$containerCode}';
+            f.parentNode.insertBefore(j,f);
+        })(window,document,'script','{$dataLayerName}','{$accountId}');
+        SCRIPT;
+}
 
-<?php if ($block->isAdvancedSettingsEnabled()): ?>
-<script type="text/javascript">
-    <?= /* @noEscape */ $block->getDataLayerJs() ?>
-</script>
-    <?= /* @noEscape */ $block->getAdvancedSettingsJsCode() ?>
-<?php endif; ?>
+if ($block->isAdvancedSettingsEnabled()) {
+    $advancedSettingsJsCode = $block->getAdvancedSettingsJsCode();
+    $gtmScript .= <<<SCRIPT
+            {$dataLayerJs}
+            {$advancedSettingsJsCode}
+            SCRIPT;
+}
 
-<?php if (($block->isGdprEnabled() || !$block->addJsInHead()) && !$block->isAdvancedSettingsEnabled()) : ?>
-<script type="text/x-magento-init">
-    {
-        "*": {
-            "magepalGtmDatalayer": {
-                "isCookieRestrictionModeEnabled": <?= /* @noEscape */ $block->isCookieRestrictionModeEnabled() ?>,
-                "currentWebsite": <?= /* @noEscape */ $block->getCurrentWebsiteId() ?>,
-                "cookieName": "<?= /* @noEscape */ $block->getCookieRestrictionName() ?>",
-                "dataLayer": "<?= /* @noEscape */ $block->getDataLayerName() ?>",
-                "accountId": "<?= /* @noEscape */ $block->getAccountId() ?>",
-                "data": <?= /* @noEscape */ $block->getDataLayerJson() ?>,
-                "isGdprEnabled": <?= /* @noEscape */ $block->isGdprEnabled() ?>,
-                "gdprOption": <?= /* @noEscape */ $block->getGdprOption() ?>,
-                "addJsInHeader": <?= /* @noEscape */ $block->addJsInHead() ?>,
-                "containerCode": "<?= /* @noEscape */ $block->getEmbeddedCode() ?>"
+// phpcs:ignore
+echo /* @noEscape */ $secureRenderer->renderTag('script', [], $gtmScript, false); ?>
+
+<?php if (($block->isGdprEnabled() || !$block->addJsInHead()) && !$block->isAdvancedSettingsEnabled()): ?>
+    <script type="text/x-magento-init">
+        {
+            "*": {
+                "magepalGtmDatalayer": {
+                    "isCookieRestrictionModeEnabled": <?= /* @noEscape */ $block->isCookieRestrictionModeEnabled() ?>,
+                    "currentWebsite": <?= /* @noEscape */ $block->getCurrentWebsiteId() ?>,
+                    "cookieName": "<?= /* @noEscape */ $block->getCookieRestrictionName() ?>",
+                    "dataLayer": "<?= /* @noEscape */ $block->getDataLayerName() ?>",
+                    "accountId": "<?= /* @noEscape */ $block->getAccountId() ?>",
+                    "data": <?= /* @noEscape */ $block->getDataLayerJson() ?>,
+                    "isGdprEnabled": <?= /* @noEscape */ $block->isGdprEnabled() ?>,
+                    "gdprOption": <?= /* @noEscape */ $block->getGdprOption() ?>,
+                    "addJsInHeader": <?= /* @noEscape */ $block->addJsInHead() ?>,
+                    "containerCode": "<?= /* @noEscape */ $block->getEmbeddedCode() ?>"
+                }
             }
         }
-    }
-</script>
+    </script>
 <?php else : ?>
-<script type="text/x-magento-init">
-    {
-        "*": {
-            "magepalGtmDatalayer": {
-                "dataLayer": "<?= /* @noEscape */ $block->getDataLayerName() ?>"
+    <script type="text/x-magento-init">
+        {
+            "*": {
+                "magepalGtmDatalayer": {
+                    "dataLayer": "<?= /* @noEscape */ $block->getDataLayerName() ?>"
             }
         }
     }
-</script>
+    </script>
 <?php endif; ?>
 <!-- End Google Tag Manager by MagePal -->


@srenon
Copy link
Contributor

srenon commented Jun 20, 2024

@redo-interactive ... thank for the code, we already have all the coding done internally and will be releasing shortly after a full review since we will need to drop support for older version of Magento < 2.4.0

They seem to be a few issues and bug right here in your code
image

You may have an issue when this getEmbeddedCode() is null

$containerCode = $block->getEmbeddedCode() ? "+'{$block->getEmbeddedCode()}'" : '';

@redo-interactive
Copy link
Author

redo-interactive commented Jun 20, 2024

@srenon Oh you are right. Thank you.

just updated the code.

@norgeindian
Copy link

@srenon , could you share some details, when the new version will be available?

@FY0u11
Copy link

FY0u11 commented Dec 5, 2024

Still have the issue with latest 3.0.0 version and magento 2.4.6-p8. You guys have wrapped your scripts with secure renderer, but it didn't solve the problem because when gtm.js is loaded, inside it appends scripts without nonce and they are blocked.

Here is error from gtm.js?id={ID_HERE}:644

Refused to execute inline script because it violates the following Content Security Policy directive: "script-src assets.adobedtm.com .adobe.com www.googleadservices.com www.google-analytics.com googleads.g.doubleclick.net analytics.google.com www.googletagmanager.com .newrelic.com .nr-data.net geostag.cardinalcommerce.com 1eafstag.cardinalcommerce.com geoapi.cardinalcommerce.com 1eafapi.cardinalcommerce.com songbird.cardinalcommerce.com includestest.ccdc02.com www.paypal.com www.sandbox.paypal.com www.paypalobjects.com t.paypal.com s.ytimg.com www.googleapis.com vimeo.com www.vimeo.com .vimeocdn.com .youtube.com https://www.gstatic.com/recaptcha/ https://www.google.com/recaptcha/ js.braintreegateway.com assets.braintreegateway.com c.paypal.com pay.google.com api.braintreegateway.com api.sandbox.braintreegateway.com client-analytics.braintreegateway.com client-analytics.sandbox.braintreegateway.com .paypal.com songbirdstag.cardinalcommerce.com .googleapis.com .gstatic.com js.hsforms.net js-na1.hs-scripts.com js.hs-banner.com js.hsadspixel.net js.hscollectedforms.net js.hubspot.com js.hs-analytics.net connect.facebook.net snap.licdn.com embed.typeform.com bat.bing.com static.hotjar.com .stripe.com klarna.com .klarna.com .klarnacdn.net .klarnaevt.com http://www.googletagmanager.com/ https://www.googletagmanager.com/ .avada.io .google.com/ 'self' 'unsafe-eval' 'unsafe-hashes' 'nonce-' 'sha256-=' 'sha256-=' 'sha256-' 'sha256-' 'sha256-' 'sha256-'". Either the 'unsafe-inline' keyword, a hash ('sha256-**'), or a nonce ('nonce-...') is required to enable inline execution.

image

@srenon
Copy link
Contributor

srenon commented Dec 5, 2024

@FY0u11... Are you sure the issue isn't your GTM container trying to load a third party script that not whitelisted by CSP?

@FY0u11
Copy link

FY0u11 commented Dec 6, 2024

@srenon You are right.

In the GTM container there are "inline Html" tag with the script. But I cannot add it to the csp_whitelist (because it just a script tag). The one solution that I initially thought on is to add a nonce to the script. But it changes every page reload. Please can you give a hint how to handle it?

@FY0u11
Copy link

FY0u11 commented Dec 6, 2024

@screnon, I have figured out. Need to add a <value id="hash1" type="hash" algorithm="sha256">hash-here</value> with the hash of the script to the whitelist_csp.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants