From 58c7bc74a931471c34ca4de7b544bda909be1113 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jostein=20Kj=C3=B8nigsen?= Date: Wed, 20 May 2026 20:45:28 +0200 Subject: [PATCH] luci-plugin-csp: add configuration options for CSP MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- plugins/luci-plugin-csp/Makefile | 14 +++++++ .../c0ffee2cf63e160c091224d95d69cdff.js | 41 +++++++++++++++++++ .../c0ffee2cf63e160c091224d95d69cdff.uc | 29 +++++++++++++ 3 files changed, 84 insertions(+) create mode 100644 plugins/luci-plugin-csp/Makefile create mode 100644 plugins/luci-plugin-csp/htdocs/luci-static/resources/view/plugins/c0ffee2cf63e160c091224d95d69cdff.js create mode 100644 plugins/luci-plugin-csp/ucode/plugins/http/headers/c0ffee2cf63e160c091224d95d69cdff.uc diff --git a/plugins/luci-plugin-csp/Makefile b/plugins/luci-plugin-csp/Makefile new file mode 100644 index 0000000000..2665847ea3 --- /dev/null +++ b/plugins/luci-plugin-csp/Makefile @@ -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 index 0000000000..1968c384cb --- /dev/null +++ b/plugins/luci-plugin-csp/htdocs/luci-static/resources/view/plugins/c0ffee2cf63e160c091224d95d69cdff.js @@ -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 index 0000000000..5faae38f52 --- /dev/null +++ b/plugins/luci-plugin-csp/ucode/plugins/http/headers/c0ffee2cf63e160c091224d95d69cdff.uc @@ -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;