diff --git a/.htaccess b/.htaccess index b7ee2318a7d2d..dd0fce231e84b 100644 --- a/.htaccess +++ b/.htaccess @@ -31,7 +31,7 @@ Header always set X-Permitted-Cross-Domain-Policies "none" Header onsuccess unset X-Robots-Tag - Header always set X-Robots-Tag "none" + Header always set X-Robots-Tag "noindex, nofollow" Header onsuccess unset X-XSS-Protection Header always set X-XSS-Protection "1; mode=block" diff --git a/build/integration/features/carddav.feature b/build/integration/features/carddav.feature index da02096ae02ee..e0c11ec8dc19a 100644 --- a/build/integration/features/carddav.feature +++ b/build/integration/features/carddav.feature @@ -46,7 +46,7 @@ Feature: carddav |X-Content-Type-Options |nosniff| |X-Frame-Options|SAMEORIGIN| |X-Permitted-Cross-Domain-Policies|none| - |X-Robots-Tag|none| + |X-Robots-Tag|noindex, nofollow| |X-XSS-Protection|1; mode=block| Scenario: Exporting the picture of ones own contact @@ -60,5 +60,5 @@ Feature: carddav |X-Content-Type-Options |nosniff| |X-Frame-Options|SAMEORIGIN| |X-Permitted-Cross-Domain-Policies|none| - |X-Robots-Tag|none| + |X-Robots-Tag|noindex, nofollow| |X-XSS-Protection|1; mode=block| diff --git a/build/integration/features/dav-v2.feature b/build/integration/features/dav-v2.feature index 9ecce4c6bf90e..269f158574e9b 100644 --- a/build/integration/features/dav-v2.feature +++ b/build/integration/features/dav-v2.feature @@ -27,7 +27,7 @@ Feature: dav-v2 |X-Content-Type-Options |nosniff| |X-Frame-Options|SAMEORIGIN| |X-Permitted-Cross-Domain-Policies|none| - |X-Robots-Tag|none| + |X-Robots-Tag|noindex, nofollow| |X-XSS-Protection|1; mode=block| And Downloaded content should start with "Welcome to your Nextcloud account!" diff --git a/build/integration/features/webdav-related.feature b/build/integration/features/webdav-related.feature index efaea1a43c4ba..21e195af1159d 100644 --- a/build/integration/features/webdav-related.feature +++ b/build/integration/features/webdav-related.feature @@ -53,7 +53,7 @@ Feature: webdav-related And User "user0" moves file "/textfile0.txt" to "/testshare/textfile0.txt" And the HTTP status code should be "403" When Downloading file "/testshare/textfile0.txt" - Then the HTTP status code should be "404" + Then the HTTP status code should be "404" Scenario: Moving a file to overwrite a file in a folder with no permissions Given using old dav path @@ -251,7 +251,7 @@ Feature: webdav-related |X-Content-Type-Options |nosniff| |X-Frame-Options|SAMEORIGIN| |X-Permitted-Cross-Domain-Policies|none| - |X-Robots-Tag|none| + |X-Robots-Tag|noindex, nofollow| |X-XSS-Protection|1; mode=block| And Downloaded content should start with "Welcome to your Nextcloud account!" diff --git a/core/js/setupchecks.js b/core/js/setupchecks.js index 6fd6fce5dcfd2..1d9d822f20b05 100644 --- a/core/js/setupchecks.js +++ b/core/js/setupchecks.js @@ -621,13 +621,13 @@ if (xhr.status === 200) { var securityHeaders = { 'X-Content-Type-Options': ['nosniff'], - 'X-Robots-Tag': ['none'], + 'X-Robots-Tag': ['noindex, nofollow'], 'X-Frame-Options': ['SAMEORIGIN', 'DENY'], 'X-Permitted-Cross-Domain-Policies': ['none'], }; for (var header in securityHeaders) { var option = securityHeaders[header][0]; - if(!xhr.getResponseHeader(header) || xhr.getResponseHeader(header).toLowerCase() !== option.toLowerCase()) { + if(!xhr.getResponseHeader(header) || xhr.getResponseHeader(header).replace(/, /, ',').toLowerCase() !== option.replace(/, /, ',').toLowerCase()) { var msg = t('core', 'The "{header}" HTTP header is not set to "{expected}". This is a potential security or privacy risk, as it is recommended to adjust this setting accordingly.', {header: header, expected: option}); if(xhr.getResponseHeader(header) && securityHeaders[header].length > 1 && xhr.getResponseHeader(header).toLowerCase() === securityHeaders[header][1].toLowerCase()) { msg = t('core', 'The "{header}" HTTP header is not set to "{expected}". Some features might not work correctly, as it is recommended to adjust this setting accordingly.', {header: header, expected: option}); diff --git a/core/js/tests/specs/setupchecksSpec.js b/core/js/tests/specs/setupchecksSpec.js index 8fd4681d4d100..67d0faad4bde0 100644 --- a/core/js/tests/specs/setupchecksSpec.js +++ b/core/js/tests/specs/setupchecksSpec.js @@ -1490,7 +1490,7 @@ describe('OC.SetupChecks tests', function() { msg: 'The "X-Content-Type-Options" HTTP header is not set to "nosniff". This is a potential security or privacy risk, as it is recommended to adjust this setting accordingly.', type: OC.SetupChecks.MESSAGE_TYPE_WARNING }, { - msg: 'The "X-Robots-Tag" HTTP header is not set to "none". This is a potential security or privacy risk, as it is recommended to adjust this setting accordingly.', + msg: '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.', type: OC.SetupChecks.MESSAGE_TYPE_WARNING }, { msg: 'The "X-Frame-Options" HTTP header is not set to "SAMEORIGIN". This is a potential security or privacy risk, as it is recommended to adjust this setting accordingly.', @@ -1517,7 +1517,7 @@ describe('OC.SetupChecks tests', function() { suite.server.requests[0].respond( 200, { - 'X-Robots-Tag': 'none', + 'X-Robots-Tag': 'noindex, nofollow', 'X-Frame-Options': 'SAMEORIGIN', 'Strict-Transport-Security': 'max-age=15768000;preload', 'X-Permitted-Cross-Domain-Policies': 'none', @@ -1548,7 +1548,7 @@ describe('OC.SetupChecks tests', function() { { 'X-XSS-Protection': '1; mode=block', 'X-Content-Type-Options': 'nosniff', - 'X-Robots-Tag': 'none', + 'X-Robots-Tag': 'noindex, nofollow', 'X-Frame-Options': 'SAMEORIGIN', 'Strict-Transport-Security': 'max-age=15768000', 'X-Permitted-Cross-Domain-Policies': 'none', @@ -1562,6 +1562,49 @@ describe('OC.SetupChecks tests', function() { }); }); + describe('check X-Robots-Tag header', function() { + it('should return no message if X-Robots-Tag is set to noindex,nofollow without space', function(done) { + protocolStub.returns('https'); + var result = OC.SetupChecks.checkGeneric(); + suite.server.requests[0].respond(200, { + 'Strict-Transport-Security': 'max-age=15768000', + 'X-XSS-Protection': '1; mode=block', + 'X-Content-Type-Options': 'nosniff', + 'X-Robots-Tag': 'noindex,nofollow', + 'X-Frame-Options': 'SAMEORIGIN', + 'X-Permitted-Cross-Domain-Policies': 'none', + 'Referrer-Policy': 'no-referrer', + }); + result.done(function( data, s, x ){ + expect(data).toEqual([]); + done(); + }); + }); + + it('should return a message if X-Robots-Tag is set to none', function(done) { + protocolStub.returns('https'); + var result = OC.SetupChecks.checkGeneric(); + suite.server.requests[0].respond(200, { + 'Strict-Transport-Security': 'max-age=15768000', + 'X-XSS-Protection': '1; mode=block', + 'X-Content-Type-Options': 'nosniff', + 'X-Robots-Tag': 'none', + 'X-Frame-Options': 'SAMEORIGIN', + 'X-Permitted-Cross-Domain-Policies': 'none', + 'Referrer-Policy': 'no-referrer', + }); + result.done(function( data, s, x ){ + expect(data).toEqual([ + { + msg: '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.', + type: OC.SetupChecks.MESSAGE_TYPE_WARNING + } + ]); + done(); + }); + }); + }); + describe('check X-XSS-Protection header', function() { it('should return no message if X-XSS-Protection is set to 1; mode=block; report=https://example.com', function(done) { protocolStub.returns('https'); @@ -1571,7 +1614,7 @@ describe('OC.SetupChecks tests', function() { 'Strict-Transport-Security': 'max-age=15768000', 'X-XSS-Protection': '1; mode=block; report=https://example.com', 'X-Content-Type-Options': 'nosniff', - 'X-Robots-Tag': 'none', + 'X-Robots-Tag': 'noindex, nofollow', 'X-Frame-Options': 'SAMEORIGIN', 'X-Permitted-Cross-Domain-Policies': 'none', 'Referrer-Policy': 'no-referrer', @@ -1591,7 +1634,7 @@ describe('OC.SetupChecks tests', function() { 'Strict-Transport-Security': 'max-age=15768000', 'X-XSS-Protection': '1; mode=block', 'X-Content-Type-Options': 'nosniff', - 'X-Robots-Tag': 'none', + 'X-Robots-Tag': 'noindex, nofollow', 'X-Frame-Options': 'SAMEORIGIN', 'X-Permitted-Cross-Domain-Policies': 'none', 'Referrer-Policy': 'no-referrer', @@ -1611,7 +1654,7 @@ describe('OC.SetupChecks tests', function() { 'Strict-Transport-Security': 'max-age=15768000', 'X-XSS-Protection': '1', 'X-Content-Type-Options': 'nosniff', - 'X-Robots-Tag': 'none', + 'X-Robots-Tag': 'noindex, nofollow', 'X-Frame-Options': 'SAMEORIGIN', 'X-Permitted-Cross-Domain-Policies': 'none', 'Referrer-Policy': 'no-referrer', @@ -1636,7 +1679,7 @@ describe('OC.SetupChecks tests', function() { 'Strict-Transport-Security': 'max-age=15768000', 'X-XSS-Protection': '0', 'X-Content-Type-Options': 'nosniff', - 'X-Robots-Tag': 'none', + 'X-Robots-Tag': 'noindex, nofollow', 'X-Frame-Options': 'SAMEORIGIN', 'X-Permitted-Cross-Domain-Policies': 'none', 'Referrer-Policy': 'no-referrer', @@ -1663,7 +1706,7 @@ describe('OC.SetupChecks tests', function() { 'Strict-Transport-Security': 'max-age=15768000', 'X-XSS-Protection': '1; mode=block', 'X-Content-Type-Options': 'nosniff', - 'X-Robots-Tag': 'none', + 'X-Robots-Tag': 'noindex, nofollow', 'X-Frame-Options': 'SAMEORIGIN', 'X-Permitted-Cross-Domain-Policies': 'none', 'Referrer-Policy': 'no-referrer', @@ -1683,7 +1726,7 @@ describe('OC.SetupChecks tests', function() { 'Strict-Transport-Security': 'max-age=15768000', 'X-XSS-Protection': '1; mode=block', 'X-Content-Type-Options': 'nosniff', - 'X-Robots-Tag': 'none', + 'X-Robots-Tag': 'noindex, nofollow', 'X-Frame-Options': 'SAMEORIGIN', 'X-Permitted-Cross-Domain-Policies': 'none', 'Referrer-Policy': 'no-referrer-when-downgrade', @@ -1703,7 +1746,7 @@ describe('OC.SetupChecks tests', function() { 'Strict-Transport-Security': 'max-age=15768000', 'X-XSS-Protection': '1; mode=block', 'X-Content-Type-Options': 'nosniff', - 'X-Robots-Tag': 'none', + 'X-Robots-Tag': 'noindex, nofollow', 'X-Frame-Options': 'SAMEORIGIN', 'X-Permitted-Cross-Domain-Policies': 'none', 'Referrer-Policy': 'strict-origin', @@ -1723,7 +1766,7 @@ describe('OC.SetupChecks tests', function() { 'Strict-Transport-Security': 'max-age=15768000', 'X-XSS-Protection': '1; mode=block', 'X-Content-Type-Options': 'nosniff', - 'X-Robots-Tag': 'none', + 'X-Robots-Tag': 'noindex, nofollow', 'X-Frame-Options': 'SAMEORIGIN', 'X-Permitted-Cross-Domain-Policies': 'none', 'Referrer-Policy': 'strict-origin-when-cross-origin', @@ -1743,7 +1786,7 @@ describe('OC.SetupChecks tests', function() { 'Strict-Transport-Security': 'max-age=15768000', 'X-XSS-Protection': '1; mode=block', 'X-Content-Type-Options': 'nosniff', - 'X-Robots-Tag': 'none', + 'X-Robots-Tag': 'noindex, nofollow', 'X-Frame-Options': 'SAMEORIGIN', 'X-Permitted-Cross-Domain-Policies': 'none', 'Referrer-Policy': 'same-origin', @@ -1763,7 +1806,7 @@ describe('OC.SetupChecks tests', function() { 'Strict-Transport-Security': 'max-age=15768000', 'X-XSS-Protection': '1; mode=block', 'X-Content-Type-Options': 'nosniff', - 'X-Robots-Tag': 'none', + 'X-Robots-Tag': 'noindex, nofollow', 'X-Frame-Options': 'SAMEORIGIN', 'X-Permitted-Cross-Domain-Policies': 'none', 'Referrer-Policy': 'origin', @@ -1788,7 +1831,7 @@ describe('OC.SetupChecks tests', function() { 'Strict-Transport-Security': 'max-age=15768000', 'X-XSS-Protection': '1; mode=block', 'X-Content-Type-Options': 'nosniff', - 'X-Robots-Tag': 'none', + 'X-Robots-Tag': 'noindex, nofollow', 'X-Frame-Options': 'SAMEORIGIN', 'X-Permitted-Cross-Domain-Policies': 'none', 'Referrer-Policy': 'origin-when-cross-origin', @@ -1813,7 +1856,7 @@ describe('OC.SetupChecks tests', function() { 'Strict-Transport-Security': 'max-age=15768000', 'X-XSS-Protection': '1; mode=block', 'X-Content-Type-Options': 'nosniff', - 'X-Robots-Tag': 'none', + 'X-Robots-Tag': 'noindex, nofollow', 'X-Frame-Options': 'SAMEORIGIN', 'X-Permitted-Cross-Domain-Policies': 'none', 'Referrer-Policy': 'unsafe-url', @@ -1840,7 +1883,7 @@ describe('OC.SetupChecks tests', function() { { 'X-XSS-Protection': '1; mode=block', 'X-Content-Type-Options': 'nosniff', - 'X-Robots-Tag': 'none', + 'X-Robots-Tag': 'noindex, nofollow', 'X-Frame-Options': 'SAMEORIGIN', 'X-Permitted-Cross-Domain-Policies': 'none', 'Referrer-Policy': 'no-referrer', @@ -1886,7 +1929,7 @@ describe('OC.SetupChecks tests', function() { { 'X-XSS-Protection': '1; mode=block', 'X-Content-Type-Options': 'nosniff', - 'X-Robots-Tag': 'none', + 'X-Robots-Tag': 'noindex, nofollow', 'X-Frame-Options': 'SAMEORIGIN', 'X-Permitted-Cross-Domain-Policies': 'none', 'Referrer-Policy': 'no-referrer', @@ -1911,7 +1954,7 @@ describe('OC.SetupChecks tests', function() { 'Strict-Transport-Security': 'max-age=15551999', 'X-XSS-Protection': '1; mode=block', 'X-Content-Type-Options': 'nosniff', - 'X-Robots-Tag': 'none', + 'X-Robots-Tag': 'noindex, nofollow', 'X-Frame-Options': 'SAMEORIGIN', 'X-Permitted-Cross-Domain-Policies': 'none', 'Referrer-Policy': 'no-referrer', @@ -1936,7 +1979,7 @@ describe('OC.SetupChecks tests', function() { 'Strict-Transport-Security': 'iAmABogusHeader342', 'X-XSS-Protection': '1; mode=block', 'X-Content-Type-Options': 'nosniff', - 'X-Robots-Tag': 'none', + 'X-Robots-Tag': 'noindex, nofollow', 'X-Frame-Options': 'SAMEORIGIN', 'X-Permitted-Cross-Domain-Policies': 'none', 'Referrer-Policy': 'no-referrer', @@ -1960,7 +2003,7 @@ describe('OC.SetupChecks tests', function() { 'Strict-Transport-Security': 'max-age=15768000', 'X-XSS-Protection': '1; mode=block', 'X-Content-Type-Options': 'nosniff', - 'X-Robots-Tag': 'none', + 'X-Robots-Tag': 'noindex, nofollow', 'X-Frame-Options': 'SAMEORIGIN', 'X-Permitted-Cross-Domain-Policies': 'none', 'Referrer-Policy': 'no-referrer', @@ -1980,7 +2023,7 @@ describe('OC.SetupChecks tests', function() { 'Strict-Transport-Security': 'max-age=99999999', 'X-XSS-Protection': '1; mode=block', 'X-Content-Type-Options': 'nosniff', - 'X-Robots-Tag': 'none', + 'X-Robots-Tag': 'noindex, nofollow', 'X-Frame-Options': 'SAMEORIGIN', 'X-Permitted-Cross-Domain-Policies': 'none', 'Referrer-Policy': 'no-referrer', @@ -2000,7 +2043,7 @@ describe('OC.SetupChecks tests', function() { 'Strict-Transport-Security': 'max-age=99999999; includeSubDomains', 'X-XSS-Protection': '1; mode=block', 'X-Content-Type-Options': 'nosniff', - 'X-Robots-Tag': 'none', + 'X-Robots-Tag': 'noindex, nofollow', 'X-Frame-Options': 'SAMEORIGIN', 'X-Permitted-Cross-Domain-Policies': 'none', 'Referrer-Policy': 'no-referrer', @@ -2020,7 +2063,7 @@ describe('OC.SetupChecks tests', function() { 'Strict-Transport-Security': 'max-age=99999999; preload; includeSubDomains', 'X-XSS-Protection': '1; mode=block', 'X-Content-Type-Options': 'nosniff', - 'X-Robots-Tag': 'none', + 'X-Robots-Tag': 'noindex, nofollow', 'X-Frame-Options': 'SAMEORIGIN', 'X-Permitted-Cross-Domain-Policies': 'none', 'Referrer-Policy': 'no-referrer', diff --git a/lib/private/legacy/OC_Response.php b/lib/private/legacy/OC_Response.php index e4525fe9e101b..9440feae3cd13 100644 --- a/lib/private/legacy/OC_Response.php +++ b/lib/private/legacy/OC_Response.php @@ -99,7 +99,7 @@ public static function addSecurityHeaders() { header('X-Content-Type-Options: nosniff'); // Disable sniffing the content type for IE header('X-Frame-Options: SAMEORIGIN'); // Disallow iFraming from other domains header('X-Permitted-Cross-Domain-Policies: none'); // https://www.adobe.com/devnet/adobe-media-server/articles/cross-domain-xml-for-streaming.html - header('X-Robots-Tag: none'); // https://developers.google.com/webmasters/control-crawl-index/docs/robots_meta_tag + header('X-Robots-Tag: noindex, nofollow'); // https://developers.google.com/webmasters/control-crawl-index/docs/robots_meta_tag header('X-XSS-Protection: 1; mode=block'); // Enforce browser based XSS filters } } diff --git a/lib/public/AppFramework/Http/Response.php b/lib/public/AppFramework/Http/Response.php index 6febef838cf8b..c3cde2117f14e 100644 --- a/lib/public/AppFramework/Http/Response.php +++ b/lib/public/AppFramework/Http/Response.php @@ -256,7 +256,7 @@ public function getHeaders() { $this->headers['Content-Security-Policy'] = $this->getContentSecurityPolicy()->buildPolicy(); $this->headers['Feature-Policy'] = $this->getFeaturePolicy()->buildPolicy(); - $this->headers['X-Robots-Tag'] = 'none'; + $this->headers['X-Robots-Tag'] = 'noindex, nofollow'; if ($this->ETag) { $mergeWith['ETag'] = '"' . $this->ETag . '"'; diff --git a/tests/data/setUploadLimit/htaccess b/tests/data/setUploadLimit/htaccess index 53b06d5ae22ae..53f743f45e777 100644 --- a/tests/data/setUploadLimit/htaccess +++ b/tests/data/setUploadLimit/htaccess @@ -11,7 +11,7 @@ # Add security and privacy related headers Header set X-Content-Type-Options "nosniff" Header set X-XSS-Protection "1; mode=block" - Header set X-Robots-Tag "none" + Header set X-Robots-Tag "noindex, nofollow" Header set X-Frame-Options "SAMEORIGIN" SetEnv modHeadersAvailable true diff --git a/tests/lib/AppFramework/Controller/ControllerTest.php b/tests/lib/AppFramework/Controller/ControllerTest.php index 4d36fcadce165..09da5f334c529 100644 --- a/tests/lib/AppFramework/Controller/ControllerTest.php +++ b/tests/lib/AppFramework/Controller/ControllerTest.php @@ -117,7 +117,7 @@ public function testFormatDataResponseJSON() { 'Content-Security-Policy' => "default-src 'none';base-uri 'none';manifest-src 'self';frame-ancestors 'none'", 'Feature-Policy' => "autoplay 'none';camera 'none';fullscreen 'none';geolocation 'none';microphone 'none';payment 'none'", 'X-Request-Id' => $this->request->getId(), - 'X-Robots-Tag' => 'none', + 'X-Robots-Tag' => 'noindex, nofollow', ]; $response = $this->controller->customDataResponse(['hi']); diff --git a/tests/lib/AppFramework/Http/DataResponseTest.php b/tests/lib/AppFramework/Http/DataResponseTest.php index f933b3102b760..fce0fef4dec77 100644 --- a/tests/lib/AppFramework/Http/DataResponseTest.php +++ b/tests/lib/AppFramework/Http/DataResponseTest.php @@ -68,7 +68,7 @@ public function testConstructorAllowsToSetHeaders() { 'Cache-Control' => 'no-cache, no-store, must-revalidate', 'Content-Security-Policy' => "default-src 'none';base-uri 'none';manifest-src 'self';frame-ancestors 'none'", 'Feature-Policy' => "autoplay 'none';camera 'none';fullscreen 'none';geolocation 'none';microphone 'none';payment 'none'", - 'X-Robots-Tag' => 'none', + 'X-Robots-Tag' => 'noindex, nofollow', 'X-Request-Id' => \OC::$server->get(IRequest::class)->getId(), ]; $expectedHeaders = array_merge($expectedHeaders, $headers); diff --git a/tests/lib/AppFramework/Http/ResponseTest.php b/tests/lib/AppFramework/Http/ResponseTest.php index c725e2fb60251..f3fb0077a1333 100644 --- a/tests/lib/AppFramework/Http/ResponseTest.php +++ b/tests/lib/AppFramework/Http/ResponseTest.php @@ -52,7 +52,7 @@ public function testSetHeaders() { 'Last-Modified' => 1, 'ETag' => 3, 'Something-Else' => 'hi', - 'X-Robots-Tag' => 'none', + 'X-Robots-Tag' => 'noindex, nofollow', ]; $this->childResponse->setHeaders($expected);