Răsfoiți Sursa

Fix R01-N25: X-Permitted-Cross-Domain-Policies: none

Adobe Flash and Acrobat are dead, but the cross-domain-policies header
is a single-line, defence-in-depth lockout against any tooling that
still inspects the file. Added to the FatalErrorHandler::securityHeaders
single source of truth so both the happy path and the 500 fallback get
it for free, on HTTP and HTTPS alike. FatalErrorHandlerTest gains the
drift fence (header in the production-500 emit + present in the helper
on both HTTPS and plain HTTP).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
chiappa 2 zile în urmă
părinte
comite
f6ce13f5c7
2 a modificat fișierele cu 16 adăugiri și 4 ștergeri
  1. 10 4
      src/Http/FatalErrorHandler.php
  2. 6 0
      tests/Http/FatalErrorHandlerTest.php

+ 10 - 4
src/Http/FatalErrorHandler.php

@@ -169,10 +169,16 @@ final class FatalErrorHandler
     public static function securityHeaders(bool $isHttps): array
     {
         $h = [
-            'X-Content-Type-Options'  => 'nosniff',
-            'X-Frame-Options'         => 'DENY',
-            'Referrer-Policy'         => 'strict-origin-when-cross-origin',
-            'Content-Security-Policy' => self::CSP,
+            'X-Content-Type-Options'              => 'nosniff',
+            'X-Frame-Options'                     => 'DENY',
+            'Referrer-Policy'                     => 'strict-origin-when-cross-origin',
+            // R01-N25: defence-in-depth for legacy Adobe Flash / Acrobat
+            // cross-origin policy lookups. Cheap header; tells those clients
+            // (and any tooling that still inspects the file) that no
+            // crossdomain.xml / clientaccesspolicy.xml is honoured under this
+            // origin.
+            'X-Permitted-Cross-Domain-Policies'   => 'none',
+            'Content-Security-Policy'             => self::CSP,
         ];
         if ($isHttps) {
             $h['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains';

+ 6 - 0
tests/Http/FatalErrorHandlerTest.php

@@ -50,6 +50,8 @@ final class FatalErrorHandlerTest extends TestCase
         self::assertContains('X-Content-Type-Options: nosniff', $this->headers);
         self::assertContains('X-Frame-Options: DENY', $this->headers);
         self::assertContains('Referrer-Policy: strict-origin-when-cross-origin', $this->headers);
+        // R01-N25: Flash / Acrobat cross-domain policy lockout.
+        self::assertContains('X-Permitted-Cross-Domain-Policies: none', $this->headers);
         self::assertContains(
             'Strict-Transport-Security: max-age=31536000; includeSubDomains',
             $this->headers,
@@ -196,11 +198,15 @@ final class FatalErrorHandlerTest extends TestCase
         self::assertSame('nosniff',                                $h['X-Content-Type-Options']);
         self::assertSame('DENY',                                   $h['X-Frame-Options']);
         self::assertSame('strict-origin-when-cross-origin',        $h['Referrer-Policy']);
+        self::assertSame('none',                                   $h['X-Permitted-Cross-Domain-Policies']);
         self::assertSame('max-age=31536000; includeSubDomains',    $h['Strict-Transport-Security']);
         self::assertArrayHasKey('Content-Security-Policy', $h);
 
         $hPlain = FatalErrorHandler::securityHeaders(false);
         self::assertArrayNotHasKey('Strict-Transport-Security', $hPlain);
+        // R01-N25: the new header is unconditional — must ride along on
+        // both HTTP and HTTPS responses, unlike HSTS.
+        self::assertSame('none', $hPlain['X-Permitted-Cross-Domain-Policies']);
     }
 
     /** @return callable(string,bool):void */