Implement nonce mechanism for luci

Hi Everyone,
I need implement nonce mechanism of http CSP header for luci architecture.
Here is explaintion of nonce :https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/nonce

I already use shell scipt gen a 15-digitals random string with mix-ed number and charater and change every minute stored in uci config.
I have added this 15-digitals random string on http header CSP nonce.
But I don't know how add the nonce on javascipt tag in html template page.
Can any one help me how to implement?
Many thanks!

You probably need to extend dispatcher.uc to pass the generated nonce value into the template engine, then modify the various places where inline scripts are present.

Untested patch implementing the functionality here:

diff --git a/modules/luci-base/ucode/dispatcher.uc b/modules/luci-base/ucode/dispatcher.uc
index 8717385be2..c1f9862d61 100644
--- a/modules/luci-base/ucode/dispatcher.uc
+++ b/modules/luci-base/ucode/dispatcher.uc
@@ -865,6 +865,7 @@ dispatch = function(_http, path) {
 
        let version = determine_version();
        let lang = determine_request_language();
+       let nonce = b64enc(randomid(16));
 
        runtime = runtime || LuCIRuntime({
                http,
@@ -886,7 +887,8 @@ dispatch = function(_http, path) {
                        randomid,
                        error404,
                        error500,
-                       lang
+                       lang,
+                       nonce
                },
                striptags,
                entityencode,
@@ -925,6 +927,7 @@ dispatch = function(_http, path) {
 
                                        http.status(403, 'Forbidden');
                                        http.header('X-LuCI-Login-Required', 'yes');
+                                       http.header('Content-Security-Policy', `script-src 'nonce-${nonce}' 'unsafe-eval'`);
 
                                        let scope = { duser: 'root', fuser: user };
                                        let theme_sysauth = `themes/${basename(runtime.env.media)}/sysauth`;
@@ -1000,6 +1003,8 @@ dispatch = function(_http, path) {
                if (require_post_security(action) && !test_post_security(resolved.ctx.authtoken))
                        return;
 
+               http.header('Content-Security-Policy', `script-src 'nonce-${nonce}' 'unsafe-eval'`);
+
                run_action(path, lang, menu, resolved, action);
        }
        catch (ex) {
diff --git a/modules/luci-base/ucode/template/footer.ut b/modules/luci-base/ucode/template/footer.ut
index d0978594f8..e11b28a244 100644
--- a/modules/luci-base/ucode/template/footer.ut
+++ b/modules/luci-base/ucode/template/footer.ut
@@ -5,7 +5,7 @@
 
 {% const rollback = dispatcher.rollback_pending() %}
 {% if (rollback || trigger_apply || trigger_revert): %}
-       <script type="text/javascript">
+       <script type="text/javascript" nonce="{{ dispatcher.nonce }}">
                document.addEventListener("luci-loaded", function() {
                        {% if (trigger_apply): %}
                                L.ui.changes.apply(true);
@@ -19,7 +19,7 @@
 {% endif %}
 
 {% if (media_error): %}
-       <script type="text/javascript">
+       <script type="text/javascript" nonce="{{ dispatcher.nonce }}">
                L.require('ui').then(function(ui) {
                        ui.showIndicator('media_error', _('Theme fallback'), function(ev) {
                                ui.showModal(_('Error loading theme'), [
diff --git a/modules/luci-base/ucode/template/header.ut b/modules/luci-base/ucode/template/header.ut
index 7dc3742a9d..954116f929 100644
--- a/modules/luci-base/ucode/template/header.ut
+++ b/modules/luci-base/ucode/template/header.ut
@@ -7,9 +7,9 @@
        include(`themes/${theme}/header`);
 -%}
 
-<script type="text/javascript" src="{{ resource }}/promis.min.js"></script>
-<script type="text/javascript" src="{{ resource }}/luci.js"></script>
-<script type="text/javascript">
+<script type="text/javascript" src="{{ resource }}/promis.min.js" nonce="{{ dispatcher.nonce }}"></script>
+<script type="text/javascript" src="{{ resource }}/luci.js" nonce="{{ dispatcher.nonce }}"></script>
+<script type="text/javascript" nonce="{{ dispatcher.nonce }}">
        L = new LuCI({{ replace(`${ {
                media          : media,
                resource       : resource,
diff --git a/modules/luci-base/ucode/template/view.ut b/modules/luci-base/ucode/template/view.ut
index 11ac824290..d729c10f15 100644
--- a/modules/luci-base/ucode/template/view.ut
+++ b/modules/luci-base/ucode/template/view.ut
@@ -2,7 +2,7 @@
 
 <div id="view">
        <div class="spinning">{{ _('Loading view…') }}</div>
-       <script type="text/javascript">
+       <script type="text/javascript" nonce="{{ dispatcher.nonce }}">
                L.require('ui').then(function(ui) {
                        ui.instantiateView('{{ view }}');
                });
diff --git a/themes/luci-theme-bootstrap/ucode/template/themes/bootstrap/footer.ut b/themes/luci-theme-bootstrap/ucode/template/themes/bootstrap/footer.ut
index 6031724053..33ce7178b2 100644
--- a/themes/luci-theme-bootstrap/ucode/template/themes/bootstrap/footer.ut
+++ b/themes/luci-theme-bootstrap/ucode/template/themes/bootstrap/footer.ut
@@ -14,7 +14,7 @@
                        </span>
                        <ul class="breadcrumb pull-right" id="modemenu" style="display:none"></ul>
                </footer>
-               <script type="text/javascript">L.require('menu-bootstrap')</script>
+               <script type="text/javascript" nonce="{{ dispatcher.nonce }}">L.require('menu-bootstrap')</script>
                {% endif %}
        </body>
 </html>
diff --git a/themes/luci-theme-bootstrap/ucode/template/themes/bootstrap/header.ut b/themes/luci-theme-bootstrap/ucode/template/themes/bootstrap/header.ut
index b7bc770b4b..e9ac1ca713 100644
--- a/themes/luci-theme-bootstrap/ucode/template/themes/bootstrap/header.ut
+++ b/themes/luci-theme-bootstrap/ucode/template/themes/bootstrap/header.ut
@@ -20,7 +20,7 @@
                <meta charset="utf-8">
                <title>{{ striptags(`${boardinfo.hostname ?? '?'}${node ? ` - ${node.title}` : ''}`) }} - LuCI</title>
                {% if (!darkpref): %}
-                       <script type="text/javascript">
+                       <script type="text/javascript" nonce="{{ dispatcher.nonce }}">
                                var mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'),
                                    rootElement = document.querySelector(':root'),
                                    setDarkMode = function(match) { rootElement.setAttribute('data-darkmode', match.matches) };
@@ -39,8 +39,8 @@
                {% if (css): %}
                <style title="text/css">{{ css }}</style>
                {% endif %}
-               <script src="{{ dispatcher.build_url('admin/translations', dispatcher.lang) }}"></script>
-               <script src="{{ resource }}/cbi.js"></script>
+               <script src="{{ dispatcher.build_url('admin/translations', dispatcher.lang) }}" nonce="{{ dispatcher.nonce }}"></script>
+               <script src="{{ resource }}/cbi.js" nonce="{{ dispatcher.nonce }}"></script>
        </head>
 
        <body class="lang_{{ dispatcher.lang }} {{ entityencode(striptags(node?.title ?? ''), true) }}" data-page="{{ entityencode(join('-', ctx.request_path), true) }}">
diff --git a/themes/luci-theme-bootstrap/ucode/template/themes/bootstrap/sysauth.ut b/themes/luci-theme-bootstrap/ucode/template/themes/bootstrap/sysauth.ut
index 15f3b1435b..fc79f3ffa5 100644
--- a/themes/luci-theme-bootstrap/ucode/template/themes/bootstrap/sysauth.ut
+++ b/themes/luci-theme-bootstrap/ucode/template/themes/bootstrap/sysauth.ut
@@ -38,7 +38,7 @@
 
 <div id="view">
        <div class="spinning">{{ _('Loading view…') }}</div>
-       <script type="text/javascript">
+       <script type="text/javascript" nonce="{{ dispatcher.nonce }}">
                L.require('ui').then(function(ui) {
                        ui.instantiateView('bootstrap.sysauth');
                });

Older, Lua-based LuCI modifications would look similar, you would need to extend dispatcher.lua in that case.

Nonce:

noun

  1. a person convicted of a sexual offence, especially against a child
    Example: every girl he meets soon gets a phone call warning them he's a nonce

Hi Jow,
sorry for late response. Thank you for great help!
I still find where to apply this patch, because my luci package version is too old.