originalContainer = Container::getInstance(); $facadeApplication = Facade::getFacadeApplication(); $this->originalFacadeApplication = $facadeApplication instanceof Application ? $facadeApplication : null; $application = new Application(dirname(__DIR__, 2)); $application->instance('config', new Repository([ 'app' => ['env' => 'production'], 'captcha' => [ 'provider' => null, 'turnstile' => ['enabled' => false], ], ])); $application->instance(Vite::class, new Vite); Container::setInstance($application); Facade::setFacadeApplication($application); Facade::clearResolvedInstances(); $this->hotFile = sys_get_temp_dir().'/nntmux-csp-hot-'.bin2hex(random_bytes(8)); app(Vite::class)->useHotFile($this->hotFile); } protected function tearDown(): void { if (is_file($this->hotFile)) { unlink($this->hotFile); } Horizon::$nonceAttribute = ''; Facade::clearResolvedInstances(); Container::setInstance($this->originalContainer); Facade::setFacadeApplication($this->originalFacadeApplication); parent::tearDown(); } public function test_horizon_inline_assets_receive_the_csp_nonce(): void { $response = (new ContentSecurityPolicy)->handle( Request::create('/horizon'), static fn (): Response => new Response(Horizon::css().Horizon::js()), ); $nonce = app('csp_nonce'); $this->assertStringContainsString("script-src 'self' 'nonce-{$nonce}'", (string) $response->headers->get('Content-Security-Policy')); $this->assertStringContainsString('https://fonts.bunny.net', (string) $response->headers->get('Content-Security-Policy')); $this->assertSame(4, substr_count((string) $response->getContent(), 'nonce="'.$nonce.'"')); } public function test_local_vite_hot_reload_sources_are_added_to_the_csp(): void { config()->set('app.env', 'local'); file_put_contents($this->hotFile, 'http://localhost:5173'); $response = (new ContentSecurityPolicy)->handle( Request::create('/'), static fn (): Response => new Response, ); $csp = (string) $response->headers->get('Content-Security-Policy'); $this->assertStringContainsString("style-src-elem 'self' 'unsafe-inline'", $csp); $this->assertStringContainsString('https://cdn.tiny.cloud http://localhost:5173', $csp); $this->assertStringContainsString('https://sp.tinymce.com http://localhost:5173 ws://localhost:5173', $csp); } public function test_vite_hot_reload_sources_are_not_added_outside_local_environment(): void { file_put_contents($this->hotFile, 'http://localhost:5173'); $response = (new ContentSecurityPolicy)->handle( Request::create('/'), static fn (): Response => new Response, ); $this->assertStringNotContainsString( 'http://localhost:5173', (string) $response->headers->get('Content-Security-Policy'), ); } public function test_non_loopback_vite_hot_reload_sources_are_rejected(): void { config()->set('app.env', 'local'); file_put_contents($this->hotFile, 'https://assets.example.com:5173'); $response = (new ContentSecurityPolicy)->handle( Request::create('/'), static fn (): Response => new Response, ); $this->assertStringNotContainsString( 'https://assets.example.com:5173', (string) $response->headers->get('Content-Security-Policy'), ); } }