Staff replied EasyStore Joomla 6 v3.1.1

Frontend `orders`, `invoice`, and `search` Layouts Bypass `WebAssetManager` and Break Under Joomla `plg_system_httpheaders` CSP Nonce / Script Hashes

Asked by Philip S. Hempel 3 days ago Last activity 2 days ago

Frontend orders, invoice, and search Layouts Bypass WebAssetManager and Break Under Joomla plg_system_httpheaders CSP Nonce / Script Hashes

  • Product: JoomShaper EasyStore (com_easystore)
  • Product Version: 3.1.1
  • Joomla Version: 6.2.0 (PHP 8.4)
  • Files:
    • components/com_easystore/tmpl/orders/default.php (lines 56, 64, 75, 88)
    • components/com_easystore/tmpl/order/invoice.php (lines 198-202)
    • components/com_easystore/layouts/filter/search.php (lines 46-58)
    • components/com_easystore/layouts/filter/basicfilter/search.php (lines 47-59)

What the Bug Is

When a Joomla site enables the built-in System - HTTP Headers (plg_system_httpheaders) plugin with Content Security Policy Nonce ({nonce}) and/or Script Hashes ({script-hashes}) and script-src configured without unsafe-inline, Joomla's ScriptsRenderer automatically attaches nonce attributes and computes SHA-256 hashes for scripts registered through WebAssetManager (EasyStoreHelper::wa()->addInlineScript(...)). However, four frontend EasyStore templates/layouts hardcode inline on​click / on​change attributes or raw <scr​ipt> tags directly in the HTML body instead of using EasyStoreHelper::wa()->addInlineScript(...), causing the browser to block them under strict CSP:

  1. components/com_easystore/tmpl/orders/default.php: Uses on​click on the search-clear button (line 56) and on​change on filter_payment_status, filter_order_status, and filter_date (lines 64, 75, 88).
  2. components/com_easystore/tmpl/order/invoice.php: Outputs a raw <scr​ipt> tag calling window.print() (lines 198-202) without a nonce attribute instead of registering it via EasyStoreHelper::wa()->addInlineScript(...).
  3. components/com_easystore/layouts/filter/search.php and components/com_easystore/layouts/filter/basicfilter/search.php: Output raw <scr​ipt defer> tags (lines 46-58) without a nonce attribute instead of registering them via EasyStoreHelper::wa()->addInlineScript(...).

How to Reproduce It

  1. In Joomla Administrator, enable System - HTTP Headers (plg_system_httpheaders) with Content-Security-Policy = Enabled, Nonce = Enabled, Script Hashes = Enabled, and script-src configured with {nonce} {script-hashes} (without unsafe-inline).
  2. Visit the frontend customer Order History page (index.php?option=com_easystore&view=orders) and change the Payment Status or Order Status filter dropdown, or click the search clear button.
  3. Open the browser DevTools Console: the browser blocks the inline on​change/on​click execution under Content Security Policy, and raw <scr​ipt> blocks in order/invoice.php and layouts/filter/search.php are similarly blocked because they lack the document's csp_nonce.

Fix (Unified Patch Against Clean com_easystore 3.1.1)

Register the inline scripts via EasyStoreHelper::wa()->addInlineScript(...) and replace the 4 inline on​click/on​change attributes in tmpl/orders/default.php with standard DOM event listeners:

--- a/components/com_easystore/tmpl/orders/default.php
+++ b/components/com_easystore/tmpl/orders/default.php
@@ -22,2 +22,19 @@
     ->useStyle('com_easystore.profile.site')
-    ->useStyle('com_easystore.orders.site');
+    ->useStyle('com_easystore.orders.site')
+    ->addInlineScript(
+        "document.addEventListener('DOMContentLoaded', function () {
+            var form = document.getElementById('adminForm');
+            if (!form) return;
+            form.querySelectorAll('.easystore-search-clear').forEach(function (btn) {
+                btn.addEventListener('click', function () {
+                    if (btn.previousElementSibling) {
+                        btn.previousElementSibling.value = '';
+                    }
+                    form.submit();
+                });
+            });
+            form.querySelectorAll('[name=\"filter_payment_status\"], [name=\"filter_order_status\"], [name=\"filter_date\"]').forEach(function (el) {
+                el.addEventListener('change', function () { form.submit(); });
+            });
+        });"
+    );
 ?>
@@ -46,3 +63,2 @@
                                 aria-label="Clear search"
-                                on​click="this.previousElementSibling.value = ''; this.form.submit();"
                             >
@@ -54,3 +70,3 @@
                         <label><​?php echo Text::_('COM_EASYSTORE_ORDERS_PAYMENT_STATUS'); ?​></label>
-                        <sel​ect name="filter_payment_status" on​change="this.form.submit()">
+                        <sel​ect name="filter_payment_status">
                             <option value=""><​?php echo Text::_('JALL'); ?​></option>
@@ -63,3 +79,3 @@
                         <label><​?php echo Text::_('COM_EASYSTORE_ORDERS_STATUS'); ?​></label>
-                        <sel​ect name="filter_order_status" on​change="this.form.submit()">
+                        <sel​ect name="filter_order_status">
                             <option value=""><​?php echo Text::_('JALL'); ?​></option>
@@ -75,4 +91,3 @@
                                name="filter_date"
-                               value="<​?php echo htmlspecialchars($this->state->get('filter.date', '')); ?​>"
-                               on​change="this.form.submit()">
+                               value="<​?php echo htmlspecialchars($this->state->get('filter.date', '')); ?​>">
                     </div>
--- a/components/com_easystore/tmpl/order/invoice.php
+++ b/components/com_easystore/tmpl/order/invoice.php
@@ -23,3 +23,4 @@
 EasyStoreHelper::wa()
-    ->useStyle('com_easystore.invoice');
+    ->useStyle('com_easystore.invoice')
+    ->addInlineScript("document.addEventListener('DOMContentLoaded', function () { window.print(); });");

@@ -32,8 +33,2 @@
     ?​>
-
-<scr​ipt>
-document.addEventListener('DOMContentLoaded', function () {
-    window.print();
-});
-</scr​ipt>

--- a/components/com_easystore/layouts/filter/search.php
+++ b/components/com_easystore/layouts/filter/search.php
@@ -32,2 +32,14 @@
 }

+EasyStoreHelper::wa()->addInlineScript(
+    "window.addEventListener('DOMContentLoaded', () => {
+        const form = document.querySelector('[data-easystore-search]');
+        if (!form) return;
+        const queryElement = form.querySelector('[name=filter_query]');
+        if (!queryElement) return;
+        let previousValue = queryElement.value;
+        queryElement.addEventListener('input', (event) => {
+            if (event.target.value === '' && previousValue !== '') {
+                form.submit();
+            }
+        });
+    });"
+);
 ?​>
@@ -43,15 +55,1 @@
 </form>
-
-<scr​ipt defer>
-    window.addEventListener('DOMContentLoaded', () => {
-        const form = document.querySelector('[data-easystore-search]');
-        const queryElement = form.querySelector('[name=filter_query]');
-        let previousValue = queryElement.value;
-        
-        queryElement.addEventListener('input', (event) => {
-            if (event.target.value === '' && previousValue !== '') {
-                form.submit();
-            }
-        });
-    });
-</scr​ipt>

1 reply

Ziaul Kabir Staff

Hi Philip,

Thank you for the detailed report and for sharing the patch.

We have forwarded it to our development team for further review.

Thank you for your understanding and continued support.

Best regards,

Log in to reply.