From 333ba96d987e0f230505ff4637a40e5ec23d9a25 Mon Sep 17 00:00:00 2001 From: Lari Taskula Date: Wed, 4 Feb 2026 16:42:34 +0200 Subject: [PATCH] Bug 38365: Use report-to with Reporting-Endpoints report-uri is deprecated, replaced by report-to and Reporting-Endpoints. To test: 1. prove t/db_dependent/Koha/Middleware/ContentSecurityPolicy.t Signed-off-by: David Cook --- Koha/Middleware/ContentSecurityPolicy.pm | 30 +++++++++++- debian/templates/koha-conf-site.xml.in | 6 +-- etc/koha-conf.xml | 6 +-- .../Koha/Middleware/ContentSecurityPolicy.t | 49 ++++++++++++++++++- 4 files changed, 82 insertions(+), 9 deletions(-) diff --git a/Koha/Middleware/ContentSecurityPolicy.pm b/Koha/Middleware/ContentSecurityPolicy.pm index 816922a9986..49fe6379926 100644 --- a/Koha/Middleware/ContentSecurityPolicy.pm +++ b/Koha/Middleware/ContentSecurityPolicy.pm @@ -47,11 +47,37 @@ sub call { return Plack::Util::response_cb( $res, sub { - my $res = shift; - my $headers = $res->[1]; + my $res = shift; + my $headers = $res->[1]; + my $add_reporting_endpoints = 1; + for ( my $i = 0 ; $i < @$headers ; $i++ ) { + + # if reporting-endpoints already exists, append it + if ( lc( $headers->[$i] ) eq 'reporting-endpoints' ) { + $headers->[ $i + 1 ] = _add_csp_to_reporting_endpoints( $headers->[ $i + 1 ] ); + $add_reporting_endpoints = 0; + last; + } + } + + # reporting-endpoints is not yet defined, so let's define it + if ($add_reporting_endpoints) { + push @$headers, ( 'Reporting-Endpoints' => _add_csp_to_reporting_endpoints() ); + } push @$headers, ( $csp->header_name => $csp->header_value ); } ); } +sub _add_csp_to_reporting_endpoints { + my ($value) = @_; + if ( $value && $value =~ /^\w+/ ) { + $value = $value . ', '; + } else { + $value = ''; + } + $value = $value . 'csp-violations="/api/v1/public/csp-reports"'; + + return $value; +} 1; diff --git a/debian/templates/koha-conf-site.xml.in b/debian/templates/koha-conf-site.xml.in index e90b7784b73..9b09b89ecf2 100644 --- a/debian/templates/koha-conf-site.xml.in +++ b/debian/templates/koha-conf-site.xml.in @@ -519,9 +519,9 @@ __END_SRU_PUBLICSERVER__ 2. Review logs and fix any legitimate violations 3. Switch to enabled mode once violations are resolved - To enable violation reporting, add report-uri to your policy pointing to Koha's - built-in CSP report endpoint. 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 + 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 Reports are logged via Koha's logging system. Configure log4perl to capture them: log4perl.logger.api.csp = WARN, CSP diff --git a/etc/koha-conf.xml b/etc/koha-conf.xml index 991d9318b86..97108498fd9 100644 --- a/etc/koha-conf.xml +++ b/etc/koha-conf.xml @@ -330,9 +330,9 @@ 2. Review logs and fix any legitimate violations 3. Switch to enabled mode once violations are resolved - To enable violation reporting, add report-uri to your policy pointing to Koha's - built-in CSP report endpoint. 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 + 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 Reports are logged via Koha's logging system. Configure log4perl to capture them: log4perl.logger.api.csp = WARN, CSP diff --git a/t/db_dependent/Koha/Middleware/ContentSecurityPolicy.t b/t/db_dependent/Koha/Middleware/ContentSecurityPolicy.t index 6095169ca11..7d12f3cd9c4 100755 --- a/t/db_dependent/Koha/Middleware/ContentSecurityPolicy.t +++ b/t/db_dependent/Koha/Middleware/ContentSecurityPolicy.t @@ -21,19 +21,23 @@ use strict; use warnings; use Test::NoWarnings; -use Test::More tests => 4; +use Test::More tests => 5; use HTTP::Request::Common; use Plack::App::CGIBin; use Plack::Builder; use Plack::Test; +use Koha::Database; + use t::lib::Mocks; use_ok("Koha::Middleware::ContentSecurityPolicy"); my $conf_csp_section = 'content_security_policy'; +my $schema = Koha::Database->schema; + subtest 'test CSP in OPAC' => sub { plan tests => 5; @@ -159,3 +163,46 @@ subtest 'test CSP in staff client' => sub { like( $res->content, qr/ sub { + plan tests => 1; + + my $test_nonce = 'TEST_NONCE'; + my $csp_header_value = + "default-src 'self'; script-src 'self' 'nonce-_CSP_NONCE_'; style-src 'self' 'nonce-_CSP_NONCE_'; img-src 'self' data:; font-src 'self'; object-src 'none'"; + + t::lib::Mocks::mock_preference( 'SessionStorage', 'tmp' ); + + $schema->storage->txn_begin; + + t::lib::Mocks::mock_preference( 'OpacPublic', 1 ); + t::lib::Mocks::mock_config( + $conf_csp_section, + { opac => { csp_mode => 'enabled', csp_header_value => $csp_header_value } } + ); + + # Set nonce + Koha::ContentSecurityPolicy->new->set_nonce($test_nonce); + + my $env = {}; + my $home = $ENV{KOHA_HOME}; + my $app = Plack::App::CGIBin->new( root => $ENV{GIT_INSTALL} ? "$home/opac" : "$home/opac/cgi-bin/opac" )->to_app; + + $app = builder { + mount '/opac' => builder { + enable "+Koha::Middleware::ContentSecurityPolicy"; + $app; + }; + }; + + my $test = Plack::Test->create($app); + my $res = $test->request( GET "/opac/opac-main.pl" ); + my $expected_csp_header_value = $csp_header_value; + $expected_csp_header_value =~ s/_CSP_NONCE_/$test_nonce/g; + like( + $res->header('reporting-endpoints'), qr/^csp-violations="\/api\/v1\/public\/csp-reports"$/, + "Response contains Reporting-Endpoints header with the expected value" + ); + + $schema->storage->txn_rollback; +}; -- 2.39.5