From bf4d25e3c9c34ecd7036ec2f7807046df59d64b5 Mon Sep 17 00:00:00 2001 From: David Cook Date: Tue, 17 Feb 2026 05:31:35 +0000 Subject: [PATCH] Bug 38365: Move default Content-Security-Policy value into Perl module By defining a default policy in the Perl module, we can easily update it over time. Using koha-conf.xml will just be for overrides, so people can manage their own there. Signed-off-by: Lari Taskula --- Koha/ContentSecurityPolicy.pm | 33 ++++++++++++++------- debian/templates/koha-conf-site.xml.in | 40 +++++++++++++++++++------- etc/koha-conf.xml | 40 +++++++++++++++++++------- t/Koha/ContentSecurityPolicy.t | 11 +------ 4 files changed, 81 insertions(+), 43 deletions(-) diff --git a/Koha/ContentSecurityPolicy.pm b/Koha/ContentSecurityPolicy.pm index d112b586154..306af6f4fb4 100644 --- a/Koha/ContentSecurityPolicy.pm +++ b/Koha/ContentSecurityPolicy.pm @@ -88,11 +88,10 @@ sub header_name { my $conf_csp = $self->{config}->get('content_security_policy'); - Koha::Exceptions::Config::MissingEntry->throw( error => 'Missing request content_security_policy csp_header_value' ) - unless exists $conf_csp->{$interface}->{csp_mode}; - - return 'Content-Security-Policy-Report-Only' if $conf_csp->{$interface}->{csp_mode} eq 'report-only'; - return 'Content-Security-Policy' if $conf_csp->{$interface}->{csp_mode} eq 'enabled'; + if ( $conf_csp && $conf_csp->{$interface} && $conf_csp->{$interface}->{csp_mode} ) { + return 'Content-Security-Policy-Report-Only' if $conf_csp->{$interface}->{csp_mode} eq 'report-only'; + return 'Content-Security-Policy' if $conf_csp->{$interface}->{csp_mode} eq 'enabled'; + } Koha::Exceptions::Config::MissingEntry->throw( error => 'Content Security Policy is disabled. Header name should only be retrieved when CSP is enabled.' ); @@ -112,8 +111,6 @@ sub header_name { Returns content_security_policy.[opac|staff].csp_header_value - Throws Koha::Exceptions::Config::MissingEntry is CSP property "csp_header_value" is missing in KOHA_CONF - =cut sub header_value { @@ -121,15 +118,29 @@ sub header_value { my $interface = $args->{interface} || C4::Context->interface; + my @default_policy_lines = ( + q#default-src 'self'#, + q# script-src 'self' 'nonce-_CSP_NONCE_'#, + q# style-src 'self' 'nonce-_CSP_NONCE_'#, + q# style-src-attr 'unsafe-inline'#, + q# img-src 'self' data:#, + q# font-src 'self'#, + q# object-src 'none'#, + ); + my $default_policy = join( ';', @default_policy_lines ); + my $conf_csp = $self->{config}->get('content_security_policy'); - Koha::Exceptions::Config::MissingEntry->throw( error => 'Missing request content_security_policy csp_header_value' ) - unless exists $conf_csp->{$interface}->{csp_header_value}; + my $user_policy = $conf_csp->{$interface}->{csp_header_value}; + + my $csp_policy = ($user_policy) ? $user_policy : $default_policy; - my $csp_header_value = $conf_csp->{$interface}->{csp_header_value}; + my $csp_header_value = $csp_policy; my $csp_nonce = $self->get_nonce; - $csp_header_value =~ s/_CSP_NONCE_/$csp_nonce/g; + if ($csp_nonce) { + $csp_header_value =~ s/_CSP_NONCE_/$csp_nonce/g; + } return $csp_header_value; } diff --git a/debian/templates/koha-conf-site.xml.in b/debian/templates/koha-conf-site.xml.in index 9b09b89ecf2..88845b246d7 100644 --- a/debian/templates/koha-conf-site.xml.in +++ b/debian/templates/koha-conf-site.xml.in @@ -514,21 +514,39 @@ __END_SRU_PUBLICSERVER__ csp_header_value: The CSP policy directives. Special placeholders: - _CSP_NONCE_: Replaced with a unique nonce for each request - Recommended workflow: - 1. Start with report-only mode to identify violations - 2. Review logs and fix any legitimate violations - 3. Switch to enabled mode once violations are resolved + NOTE: Koha comes with a hard-coded csp_header_value. You only need to define your own + csp_header_value if you want or need a different Content-Security-Policy value. + + To enable violation reporting, create your own csp_header_value including + 'report-to' pointing to csp-violations and add 'report-uri'. + + NOTE: You must be using HTTPS to receive violation reports. - To enable violation reporting, add report-to to your policy pointing to csp-violations. Example with reporting: - default-src 'self'; script-src 'self' 'nonce-_CSP_NONCE_'; style-src 'self' 'nonce-_CSP_NONCE_'; style-src-attr 'unsafe-inline'; img-src 'self' data:; font-src 'self'; object-src 'none'; report-uri /api/v1/public/csp-reports; report-to csp-violations + + + report-only + default-src 'self'; script-src 'self' 'nonce-_CSP_NONCE_'; style-src 'self' 'nonce-_CSP_NONCE_'; style-src-attr 'unsafe-inline'; img-src 'self' data:; font-src 'self'; object-src 'none'; report-uri /api/v1/public/csp-reports; report-to csp-violations + + + report-only + default-src 'self'; script-src 'self' 'nonce-_CSP_NONCE_'; style-src 'self' 'nonce-_CSP_NONCE_'; style-src-attr 'unsafe-inline'; img-src 'self' data:; font-src 'self'; object-src 'none'; report-uri /api/v1/public/csp-reports; report-to csp-violations + + Reports are logged via Koha's logging system. Configure log4perl to capture them: - log4perl.logger.api.csp = WARN, CSP - log4perl.appender.CSP = Log::Log4perl::Appender::File - log4perl.appender.CSP.filename = /var/log/koha/__KOHASITE__/csp-violations.log - log4perl.appender.CSP.layout = PatternLayout - log4perl.appender.CSP.layout.ConversionPattern = [%d] %m%n + log4perl.logger.plack-csp = WARN, PLACKCSP + log4perl.appender.PLACKCSP=Log::Log4perl::Appender::File + log4perl.appender.PLACKCSP.filename=/var/log/koha//plack-csp-violations.log + log4perl.appender.PLACKCSP.mode=append + log4perl.appender.PLACKCSP.layout=PatternLayout + log4perl.appender.PLACKCSP.layout.ConversionPattern=[%d] %m%n + log4perl.appender.PLACKCSP.utf8=1 + + Recommended workflow: + 1. Start with report-only mode to identify violations + 2. Review logs and fix any legitimate violations (by fixing the code or by modifying your csp_header_value) + 3. Switch to enabled mode once violations are resolved --> diff --git a/etc/koha-conf.xml b/etc/koha-conf.xml index 97108498fd9..5ef7295b566 100644 --- a/etc/koha-conf.xml +++ b/etc/koha-conf.xml @@ -325,21 +325,39 @@ csp_header_value: The CSP policy directives. Special placeholders: - _CSP_NONCE_: Replaced with a unique nonce for each request - Recommended workflow: - 1. Start with report-only mode to identify violations - 2. Review logs and fix any legitimate violations - 3. Switch to enabled mode once violations are resolved + NOTE: Koha comes with a hard-coded csp_header_value. You only need to define your own + csp_header_value if you want or need a different Content-Security-Policy value. + + To enable violation reporting, create your own csp_header_value including + 'report-to' pointing to csp-violations and add 'report-uri'. + + NOTE: You must be using HTTPS to receive violation reports. - To enable violation reporting, add report-to to your policy pointing csp-violations. Example with reporting: - default-src 'self'; script-src 'self' 'nonce-_CSP_NONCE_'; style-src 'self' 'nonce-_CSP_NONCE_'; style-src-attr 'unsafe-inline'; img-src 'self' data:; font-src 'self'; object-src 'none'; report-uri /api/v1/public/csp-reports; report-to csp-violations + + + report-only + default-src 'self'; script-src 'self' 'nonce-_CSP_NONCE_'; style-src 'self' 'nonce-_CSP_NONCE_'; style-src-attr 'unsafe-inline'; img-src 'self' data:; font-src 'self'; object-src 'none'; report-uri /api/v1/public/csp-reports; report-to csp-violations + + + report-only + default-src 'self'; script-src 'self' 'nonce-_CSP_NONCE_'; style-src 'self' 'nonce-_CSP_NONCE_'; style-src-attr 'unsafe-inline'; img-src 'self' data:; font-src 'self'; object-src 'none'; report-uri /api/v1/public/csp-reports; report-to csp-violations + + Reports are logged via Koha's logging system. Configure log4perl to capture them: - log4perl.logger.api.csp = WARN, CSP - log4perl.appender.CSP = Log::Log4perl::Appender::File - log4perl.appender.CSP.filename = /var/log/koha/csp-violations.log - log4perl.appender.CSP.layout = PatternLayout - log4perl.appender.CSP.layout.ConversionPattern = [%d] %m%n + log4perl.logger.plack-csp = WARN, PLACKCSP + log4perl.appender.PLACKCSP=Log::Log4perl::Appender::File + log4perl.appender.PLACKCSP.filename=/var/log/koha//plack-csp-violations.log + log4perl.appender.PLACKCSP.mode=append + log4perl.appender.PLACKCSP.layout=PatternLayout + log4perl.appender.PLACKCSP.layout.ConversionPattern=[%d] %m%n + log4perl.appender.PLACKCSP.utf8=1 + + Recommended workflow: + 1. Start with report-only mode to identify violations + 2. Review logs and fix any legitimate violations (by fixing the code or by modifying your csp_header_value) + 3. Switch to enabled mode once violations are resolved --> diff --git a/t/Koha/ContentSecurityPolicy.t b/t/Koha/ContentSecurityPolicy.t index b24f26e9acf..ef7ce71589a 100755 --- a/t/Koha/ContentSecurityPolicy.t +++ b/t/Koha/ContentSecurityPolicy.t @@ -117,22 +117,13 @@ subtest 'header_name tests' => sub { }; subtest 'header_value tests' => sub { - plan tests => 6; + plan tests => 3; t::lib::Mocks::mock_config( $conf_csp_section, {} ); C4::Context->interface('opac'); my $csp = Koha::ContentSecurityPolicy->new; - throws_ok { $csp->header_value } 'Koha::Exceptions::Config::MissingEntry', - 'Exception thrown when missing csp_header_value'; - - t::lib::Mocks::mock_config( $conf_csp_section, { opac => {} } ); - throws_ok { $csp->header_value } 'Koha::Exceptions::Config::MissingEntry', - 'Exception thrown when missing csp_header_value'; - - t::lib::Mocks::mock_config( $conf_csp_section, { opac => { csp_header_value => '' } } ); - is( $csp->header_value, '', 'Even an empty csp_header_value is retrieved form koha-conf.xml' ); t::lib::Mocks::mock_config( $conf_csp_section, { opac => { csp_header_value => 'some value' } } ); is( $csp->header_value, 'some value', 'csp_header_value is retrieved from koha-conf.xml' ); -- 2.34.1