Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,28 @@ return view.extend({
o.default = o.enabled;
o.rmempty = false;

const csp_mode_option = s.taboption('general', form.RichListValue, "csp_mode", _('Content-Security-Policy'), _('Configure CSP headers to improve security.'));
csp_mode_option.value('none', _('None (default)'), _('Least secure. CSP disabled.'));
csp_mode_option.value('strict', _('Strict'), _('Most secure setting compatible with OpenWRT default installs.'));
csp_mode_option.value('permissive', _('Permissive'), _('Less secure than Strict, but better than None.<br>Use with integrations incompatible with Strict.'));
csp_mode_option.value('custom', _('Custom'), _('For experts only.'));
csp_mode_option.default = 'none';
csp_mode_option.rmempty = false;

const csp_policy_option = s.taboption('general', form.Value, 'csp_policy', _('Custom CSP Policy String'), _('The Content-Security-Policy header-value used in custom-mode.') + "<br />" + _(' WARNING: Wrong values for this setting can render the web-UI inaccessible and require recovery by SSH.'));
csp_policy_option.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;";

csp_mode_option.onchange = function(ev, section_id, value) {
const policy_element = csp_policy_option.getUIElement(section_id);
const node = policy_element.node.querySelector('input');
const isCustom = value === 'custom';
if (isCustom) {
node.removeAttribute('readonly', 'readonly');
} else {
node.setAttribute('readonly', 'readonly');
}
};

o = s.taboption('general', form.Flag, 'rfc1918_filter', _('Ignore private IPs on public interface'), _('Prevent access from private (RFC1918) IPs on an interface if it has an public IP address'));
o.default = o.enabled;
o.rmempty = false;
Expand Down
31 changes: 31 additions & 0 deletions modules/luci-base/ucode/http.uc
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ import {
stdin, stdout, mkstemp
} from 'fs';

import { cursor } from 'uci';

let uci = cursor();

// luci.http module scope
export let HTTP_MAX_CONTENT = 1024*100; // 100 kB maximum content size

Expand Down Expand Up @@ -504,6 +508,33 @@ const Class = {
if (!this.headers?.['x-content-type-options'])
this.header('X-Content-Type-Options', 'nosniff');

if (!this.headers?.['Content-Security-Policy']) {
// Read UCI settings
let csp_mode = uci.get('uhttpd', 'main', 'csp_mode') || 'none';

// Define preset policies
let presets = {
// TODO: strict: default-src: none
'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 csp_policy = null;

if (csp_mode == 'custom') {
// For custom mode, read the user's policy
csp_policy = uci.get('uhttpd', 'main', 'csp_policy');
}
else if (csp_mode in presets) {
// For permissive/strict, use preset
csp_policy = presets[csp_mode];
}
// else mode is 'none' - don't set CSP header

if (csp_policy)
this.header('Content-Security-Policy', csp_policy);
}

this.output('Status: ');
this.output(this.status_code);
this.output(' ');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,28 @@ return view.extend({
o = s.option(form.Flag, 'redirect_https', _('Redirect to HTTPS'), _('Enable automatic redirection of <abbr title="Hypertext Transfer Protocol">HTTP</abbr> requests to <abbr title="Hypertext Transfer Protocol Secure">HTTPS</abbr> port.'));
o.rmempty = false;

const csp_mode_option = s.option(form.RichListValue, "csp_mode", _('Content-Security-Policy'), _('Configure CSP headers to improve security.'));
csp_mode_option.value('none', _('None (default)'), _('Least secure. CSP disabled.'));
csp_mode_option.value('strict', _('Strict'), _('Most secure setting compatible with OpenWRT default installs.'));
csp_mode_option.value('permissive', _('Permissive'), _('Less secure than Strict, but better than None.<br>Use with integrations incompatible with Strict.'));
csp_mode_option.value('custom', _('Custom'), _('For experts only.'));
csp_mode_option.default = 'none';
csp_mode_option.rmempty = false;

const csp_policy_option = s.option(form.Value, 'csp_policy', _('Custom CSP Policy String'), _('The Content-Security-Policy header-value used in custom-mode.') + "<br />" + _(' WARNING: Wrong values for this setting can render the web-UI inaccessible and require recovery by SSH.'));
csp_policy_option.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;";

csp_mode_option.onchange = function(ev, section_id, value) {
const policy_element = csp_policy_option.getUIElement(section_id);
const node = policy_element.node.querySelector('input');
const isCustom = value === 'custom';
if (isCustom) {
node.removeAttribute('readonly', 'readonly');
} else {
node.setAttribute('readonly', 'readonly');
}
};

return m.render();
}
});