From 7df6c201f2e6a6521d20718dfd8d9794c7437d1f Mon Sep 17 00:00:00 2001 From: Inverle Date: Mon, 11 Aug 2025 19:35:54 +0200 Subject: Put CSP everywhere (#7810) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Puts CSP everywhere in `p/api` * including the HTML query page ❗ * Also in `p/ext.php` * Puts `X-Content-Type-Options: nosniff` everywhere * Fixes custom icon configuration not showing `blob:` icon in statsController (idle feeds) * Also removes `style-src 'unsafe-inline'` since it doesn't seem to be needed * Improves CSP of `p/f.php` * Add `sandbox` directive --- app/Controllers/statsController.php | 3 +-- app/FreshRSS.php | 2 +- p/api/fever.php | 2 ++ p/api/greader.php | 3 +++ p/api/index.php | 2 ++ p/api/pshb.php | 1 + p/api/query.php | 7 +++++++ p/ext.php | 2 ++ p/f.php | 4 +++- 9 files changed, 22 insertions(+), 4 deletions(-) diff --git a/app/Controllers/statsController.php b/app/Controllers/statsController.php index 67b1f80e9..5a7e9b79a 100644 --- a/app/Controllers/statsController.php +++ b/app/Controllers/statsController.php @@ -30,8 +30,7 @@ class FreshRSS_stats_Controller extends FreshRSS_ActionController { $this->_csp([ 'default-src' => "'self'", 'frame-ancestors' => "'none'", - 'img-src' => '* data:', - 'style-src' => "'self' 'unsafe-inline'", + 'img-src' => '* data: blob:', ]); $catDAO = FreshRSS_Factory::createCategoryDao(); diff --git a/app/FreshRSS.php b/app/FreshRSS.php index bff9f1b18..62e91ff95 100644 --- a/app/FreshRSS.php +++ b/app/FreshRSS.php @@ -149,7 +149,7 @@ class FreshRSS extends Minz_FrontController { } public static function preLayout(): void { - header("X-Content-Type-Options: nosniff"); + header('X-Content-Type-Options: nosniff'); FreshRSS_Share::load(join_path(APP_PATH, 'shares.php')); self::loadStylesAndScripts(); diff --git a/p/api/fever.php b/p/api/fever.php index 6251842e7..92ddb82b8 100644 --- a/p/api/fever.php +++ b/p/api/fever.php @@ -1,5 +1,7 @@ diff --git a/p/api/pshb.php b/p/api/pshb.php index 91dd4e901..b6cbc5089 100644 --- a/p/api/pshb.php +++ b/p/api/pshb.php @@ -6,6 +6,7 @@ require(LIB_PATH . '/lib_rss.php'); //Includes class autoloader const MAX_PAYLOAD = 3_145_728; header('Content-Type: text/plain; charset=UTF-8'); +header("Content-Security-Policy: default-src 'none'; frame-ancestors 'none'; sandbox"); header('X-Content-Type-Options: nosniff'); $ORIGINAL_INPUT = file_get_contents('php://input', false, null, 0, MAX_PAYLOAD) ?: ''; diff --git a/p/api/query.php b/p/api/query.php index 5deedc932..f7458e823 100644 --- a/p/api/query.php +++ b/p/api/query.php @@ -1,5 +1,8 @@ _layout(null); $view->_path('index/rss.phtml'); } elseif (in_array($format, ['greader', 'json'], true)) { header('Content-Type: application/json; charset=utf-8'); + header("Content-Security-Policy: default-src 'none'; frame-ancestors 'none'; sandbox"); $view->_layout(null); $view->type = 'query/' . $token; $view->list_title = $query->getName(); @@ -190,9 +195,11 @@ if (in_array($format, ['rss', 'atom'], true)) { die(); } header('Content-Type: application/xml; charset=utf-8'); + header("Content-Security-Policy: default-src 'none'; frame-ancestors 'none'; sandbox"); $view->_layout(null); $view->_path('index/opml.phtml'); } else { + header("Content-Security-Policy: default-src 'self'; frame-src *; img-src * data:; frame-ancestors 'none'; media-src *"); $view->_layout('layout'); $view->_path('index/html.phtml'); } diff --git a/p/ext.php b/p/ext.php index f470b7210..5fdd3df6c 100644 --- a/p/ext.php +++ b/p/ext.php @@ -94,6 +94,8 @@ if (!is_valid_path($absolute_filename)) { $content_type = FreshRSS_extension_Controller::MIME_TYPES[$file_type]; header("Content-Type: {$content_type}"); header("Content-Disposition: inline; filename='{$file_name}'"); +header("Content-Security-Policy: default-src 'self'; frame-ancestors 'none'"); +header('X-Content-Type-Options: nosniff'); header('Referrer-Policy: same-origin'); $mtime = @filemtime($absolute_filename); diff --git a/p/f.php b/p/f.php index fdec38c02..fc9a60abd 100644 --- a/p/f.php +++ b/p/f.php @@ -5,6 +5,9 @@ require(LIB_PATH . '/lib_rss.php'); //Includes class autoloader require(LIB_PATH . '/favicons.php'); require(LIB_PATH . '/http-conditional.php'); +header("Content-Security-Policy: default-src 'none'; frame-ancestors 'none'; sandbox"); +header('X-Content-Type-Options: nosniff'); + function show_default_favicon(int $cacheSeconds = 3600): void { $default_mtime = @filemtime(DEFAULT_FAVICON) ?: 0; if (!httpConditional($default_mtime, $cacheSeconds, 2)) { @@ -56,7 +59,6 @@ if (($ico_mtime == false || $ico_mtime < $txt_mtime || ($ico_mtime < time() - (m } } -header("Content-Security-Policy: default-src 'none'; frame-ancestors 'none'; img-src 'self'; style-src 'self';"); if (!httpConditional($ico_mtime, mt_rand(14, 21) * 86400, 2)) { $ico_content_type = contentType($ico); header('Content-Type: ' . $ico_content_type); -- cgit v1.2.3