luci-plugin-csp: add configuration options for CSP
authorJostein Kjønigsen <redacted>
Wed, 20 May 2026 18:45:28 +0000 (20:45 +0200)
committerPaul Donald <redacted>
Wed, 20 May 2026 20:20:26 +0000 (23:20 +0300)
- ucode: add configured CSP HTTP headers
- js: add dropdown to select CSP-mode and textbox to configure custom CSP policy

Signed-off-by: Jostein Kjønigsen <redacted>
plugins/luci-plugin-csp/Makefile [new file with mode: 0644]
plugins/luci-plugin-csp/htdocs/luci-static/resources/view/plugins/c0ffee2cf63e160c091224d95d69cdff.js [new file with mode: 0644]
plugins/luci-plugin-csp/ucode/plugins/http/headers/c0ffee2cf63e160c091224d95d69cdff.uc [new file with mode: 0644]

diff --git a/plugins/luci-plugin-csp/Makefile b/plugins/luci-plugin-csp/Makefile
new file mode 100644 (file)
index 0000000..2665847
--- /dev/null
@@ -0,0 +1,14 @@
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=luci-plugin-csp
+
+LUCI_TITLE:=LuCI CSP Plugin - Content-Security-Policy HTTP header
+LUCI_DEPENDS:=+luci-base +luci-mod-system
+
+LUCI_TYPE:=plugin
+
+PKG_LICENSE:=Apache-2.0
+
+include ../../luci.mk
+
+# call BuildPackage - OpenWrt buildroot signature
diff --git a/plugins/luci-plugin-csp/htdocs/luci-static/resources/view/plugins/c0ffee2cf63e160c091224d95d69cdff.js b/plugins/luci-plugin-csp/htdocs/luci-static/resources/view/plugins/c0ffee2cf63e160c091224d95d69cdff.js
new file mode 100644 (file)
index 0000000..1968c38
--- /dev/null
@@ -0,0 +1,41 @@
+'use strict';
+'require baseclass';
+'require form';
+
+return baseclass.extend({
+
+       class: 'http',
+       class_i18n: _('HTTP'),
+
+       type: 'headers',
+       type_i18n: _('Headers'),
+
+       name: 'Content-Security-Policy',
+       id: 'c0ffee2cf63e160c091224d95d69cdff',
+       title: _('Content Security Policy'),
+       description: _('Adds a Content-Security-Policy HTTP response header to protect against XSS and content injection attacks.'),
+
+       addFormOptions(s) {
+               let o;
+
+               o = s.option(form.Flag, 'enabled', _('Enabled'));
+
+               o = s.option(form.ListValue, 'mode', _('Mode'));
+               o.value('strict', _('Strict'));
+               o.value('permissive', _('Permissive'));
+               o.value('custom', _('Custom (experts only)'));
+               o.default = 'strict';
+               o.depends('enabled', '1');
+
+               o = s.option(form.Value, 'policy', _('Custom CSP Policy String'),
+                       _('WARNING: Wrong values can render the web-UI inaccessible, requiring SSH to recover (/etc/config/luci_plugins).'));
+               o.default = "default-src 'none'; script-src 'self' 'unsafe-inline' 'unsafe-eval' 'trusted-types-eval'; img-src 'self' data: blob:; style-src 'self' 'unsafe-inline'; connect-src 'self' https://sysupgrade.openwrt.org;";
+               o.depends({ enabled: '1', mode: 'custom' });
+       },
+
+       configSummary(section) {
+               if (section.enabled !== '1')
+                       return _('Disabled');
+               return _('Mode: %s').format(section.mode || 'strict');
+       }
+});
diff --git a/plugins/luci-plugin-csp/ucode/plugins/http/headers/c0ffee2cf63e160c091224d95d69cdff.uc b/plugins/luci-plugin-csp/ucode/plugins/http/headers/c0ffee2cf63e160c091224d95d69cdff.uc
new file mode 100644 (file)
index 0000000..5faae38
--- /dev/null
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: Apache-2.0
+
+'use strict';
+
+import { cursor } from 'uci';
+
+function default_action(plugin_id) {
+       const uci = cursor();
+       const mode = uci.get('luci_plugins', plugin_id, 'mode') || 'strict';
+
+       const presets = {
+               'strict': "default-src 'none'; script-src 'self' 'unsafe-inline' 'unsafe-eval' 'trusted-types-eval'; img-src 'self' data: blob:; style-src 'self' 'unsafe-inline'; connect-src 'self' https://sysupgrade.openwrt.org;",
+               'permissive': "default-src 'self' https://*; script-src 'self' 'unsafe-inline' 'unsafe-eval' 'trusted-types-eval'; img-src 'self' data: blob: https://*; style-src 'self' 'unsafe-inline' https://*;",
+       };
+
+       let policy;
+
+       if (mode === 'custom')
+               policy = uci.get('luci_plugins', plugin_id, 'policy');
+       else
+               policy = presets[mode];
+
+       if (!policy)
+               return null;
+
+       return ['Content-Security-Policy', policy];
+}
+
+return default_action;
git clone https://git.99rst.org/PROJECT