From bc1224a27efeaac888452c71069ab1ad2e1dfa20 Mon Sep 17 00:00:00 2001 From: Josh Date: Sat, 19 Sep 2026 14:45:52 -0400 Subject: [PATCH 1/2] fix(settings): allow compatible X-Robots-Tag directives Preserve the required `noindex,nofollow` directives while allowing compatible additions such as `noarchive`, `nosnippet`, `noimageindex`, and `notranslate`. Signed-off-by: Josh --- .../lib/SetupChecks/SecurityHeaders.php | 40 ++++++++++++++++++- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/apps/settings/lib/SetupChecks/SecurityHeaders.php b/apps/settings/lib/SetupChecks/SecurityHeaders.php index 09f8b09b331aa..d7a8a32c7bc24 100644 --- a/apps/settings/lib/SetupChecks/SecurityHeaders.php +++ b/apps/settings/lib/SetupChecks/SecurityHeaders.php @@ -60,6 +60,29 @@ public function run(): SetupResult { return implode(',', $values); }; + $parseDirectives = static function (string $value): array { + $directives = array_map( + static fn (string $directive): string => strtolower(trim($directive)), + explode(',', $value) + ); + + if (in_array('', $directives, true)) { + return []; + } + + return $directives; + }; + + // These directives can safely co-exist + $allowedRobotsDirectives = [ + 'noindex', + 'nofollow', + 'noarchive', + 'nosnippet', + 'noimageindex', + 'notranslate', + ]; + foreach ($urls as [$verb,$url,$validStatuses]) { $works = null; foreach ($this->runRequest($verb, $url, ['httpErrors' => false]) as $response) { @@ -71,11 +94,24 @@ public function run(): SetupResult { $msg = ''; $msgParameters = []; foreach ($securityHeaders as $header => [$expected, $accepted]) { - $normalizedValue = $normalize($response->getHeader($header)); + $headerValue = $response->getHeader($header); + $normalizedValue = $normalize($headerValue); $normalizedExpected = $normalize($expected); $normalizedAccepted = $accepted !== null ? $normalize($accepted) : null; - if ($normalizedValue !== $normalizedExpected) { + if ($header === 'X-Robots-Tag') { + $directives = $parseDirectives($headerValue); + $uniqueDirectives = array_unique($directives); + + $isValid = $directives !== [] + && count($directives) === count($uniqueDirectives) + && !array_diff($directives, $allowedRobotsDirectives) + && !array_diff(['noindex', 'nofollow'], $directives); + } else { + $isValid = $normalizedValue === $normalizedExpected; + } + + if (!$isValid) { if ($normalizedAccepted !== null && $normalizedValue === $normalizedAccepted) { $msg .= $this->l10n->t( '- The `%1$s` HTTP header is not set to `%2$s`. Some features might not work correctly, as it is recommended to adjust this setting accordingly.', From 679b2bfa6eb30998445a8d4310abdb43f9c93bc7 Mon Sep 17 00:00:00 2001 From: Josh Date: Sat, 19 Sep 2026 14:50:31 -0400 Subject: [PATCH 2/2] test(settings): cover compatible X-Robots-Tag directives Assisted-by: Copilot:gpt-5.6-luna Signed-off-by: Josh --- .../tests/SetupChecks/SecurityHeadersTest.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/apps/settings/tests/SetupChecks/SecurityHeadersTest.php b/apps/settings/tests/SetupChecks/SecurityHeadersTest.php index 9a7a606cff048..070242eada845 100644 --- a/apps/settings/tests/SetupChecks/SecurityHeadersTest.php +++ b/apps/settings/tests/SetupChecks/SecurityHeadersTest.php @@ -97,6 +97,8 @@ public static function dataSuccess(): array { 'basic' => [[]], 'no-space-in-x-robots' => [['X-Robots-Tag' => 'noindex,nofollow']], 'reordered-x-robots' => [['X-Robots-Tag' => 'nofollow, noindex']], + 'x-robots-noarchive' => [['X-Robots-Tag' => 'noindex,nofollow,noarchive']], + 'x-robots-all-allowed-directives' => [['X-Robots-Tag' => 'notranslate, noarchive, nofollow, noimageindex, noindex, nosnippet']], 'strict-origin-when-cross-origin' => [['Referrer-Policy' => 'strict-origin-when-cross-origin']], 'referrer-no-referrer-when-downgrade' => [['Referrer-Policy' => 'no-referrer-when-downgrade']], 'referrer-strict-origin' => [['Referrer-Policy' => 'strict-origin']], @@ -138,7 +140,14 @@ public static function dataFailure(): array { return [ // description => modifiedHeaders 'x-robots-none' => [['X-Robots-Tag' => 'none'], "- The `X-Robots-Tag` HTTP header is not set to `noindex,nofollow`. This is a potential security or privacy risk, as it is recommended to adjust this setting accordingly.\n"], - 'x-robots-additional-directive' => [['X-Robots-Tag' => 'noindex,nofollow,noarchive'], "- The `X-Robots-Tag` HTTP header is not set to `noindex,nofollow`. This is a potential security or privacy risk, as it is recommended to adjust this setting accordingly.\n"], + 'x-robots-missing-noindex' => [['X-Robots-Tag' => 'nofollow'], "- The `X-Robots-Tag` HTTP header is not set to `noindex,nofollow`. This is a potential security or privacy risk, as it is recommended to adjust this setting accordingly.\n"], + 'x-robots-missing-nofollow' => [['X-Robots-Tag' => 'noindex'], "- The `X-Robots-Tag` HTTP header is not set to `noindex,nofollow`. This is a potential security or privacy risk, as it is recommended to adjust this setting accordingly.\n"], + 'x-robots-conflicting-index' => [['X-Robots-Tag' => 'noindex,nofollow,index'], "- The `X-Robots-Tag` HTTP header is not set to `noindex,nofollow`. This is a potential security or privacy risk, as it is recommended to adjust this setting accordingly.\n"], + 'x-robots-conflicting-follow' => [['X-Robots-Tag' => 'noindex,nofollow,follow'], "- The `X-Robots-Tag` HTTP header is not set to `noindex,nofollow`. This is a potential security or privacy risk, as it is recommended to adjust this setting accordingly.\n"], + 'x-robots-unknown-directive' => [['X-Robots-Tag' => 'noindex,nofollow,unknown'], "- The `X-Robots-Tag` HTTP header is not set to `noindex,nofollow`. This is a potential security or privacy risk, as it is recommended to adjust this setting accordingly.\n"], + 'x-robots-duplicate-directive' => [['X-Robots-Tag' => 'noindex,nofollow,nofollow'], "- The `X-Robots-Tag` HTTP header is not set to `noindex,nofollow`. This is a potential security or privacy risk, as it is recommended to adjust this setting accordingly.\n"], + 'x-robots-trailing-comma' => [['X-Robots-Tag' => 'noindex,nofollow,'], "- The `X-Robots-Tag` HTTP header is not set to `noindex,nofollow`. This is a potential security or privacy risk, as it is recommended to adjust this setting accordingly.\n"], + 'x-robots-empty-directive' => [['X-Robots-Tag' => 'noindex,,nofollow'], "- The `X-Robots-Tag` HTTP header is not set to `noindex,nofollow`. This is a potential security or privacy risk, as it is recommended to adjust this setting accordingly.\n"], 'referrer-origin' => [['Referrer-Policy' => 'origin'], "- The `Referrer-Policy` HTTP header is not set to `no-referrer`, `no-referrer-when-downgrade`, `strict-origin`, `strict-origin-when-cross-origin` or `same-origin`. This can leak referer information. See the {w3c-recommendation}.\n"], 'referrer-origin-when-cross-origin' => [['Referrer-Policy' => 'origin-when-cross-origin'], "- The `Referrer-Policy` HTTP header is not set to `no-referrer`, `no-referrer-when-downgrade`, `strict-origin`, `strict-origin-when-cross-origin` or `same-origin`. This can leak referer information. See the {w3c-recommendation}.\n"], 'referrer-unsafe-url' => [['Referrer-Policy' => 'unsafe-url'], "- The `Referrer-Policy` HTTP header is not set to `no-referrer`, `no-referrer-when-downgrade`, `strict-origin`, `strict-origin-when-cross-origin` or `same-origin`. This can leak referer information. See the {w3c-recommendation}.\n"],