mirror of
https://github.com/NNTmux/newznab-tmux.git
synced 2026-08-29 01:08:56 +00:00
Add horizon nonce
This commit is contained in:
@@ -7,6 +7,7 @@ namespace App\Http\Middleware;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Vite;
|
||||
use Laravel\Horizon\Horizon;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class ContentSecurityPolicy
|
||||
@@ -19,20 +20,10 @@ class ContentSecurityPolicy
|
||||
// Tell Vite to add the CSP nonce to generated script/style tags
|
||||
$nonce = function_exists('csp_nonce') ? csp_nonce() : base64_encode(random_bytes(16));
|
||||
Vite::useCspNonce($nonce);
|
||||
Horizon::cspNonce($nonce);
|
||||
|
||||
$response = $next($request);
|
||||
|
||||
// Skip strict CSP for Horizon dashboard (admin-only). Horizon uses inline
|
||||
// scripts and does not support nonces; it is already protected by auth/role.
|
||||
$horizonPath = trim(config('horizon.path', 'horizon'), '/');
|
||||
if ($horizonPath !== '' && $request->is($horizonPath, $horizonPath.'/*')) {
|
||||
$response->headers->set('X-Frame-Options', 'SAMEORIGIN');
|
||||
$response->headers->set('X-Content-Type-Options', 'nosniff');
|
||||
$response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin');
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
// Check if Turnstile is enabled
|
||||
$turnstileEnabled = config('captcha.provider') === 'turnstile'
|
||||
&& config('captcha.turnstile.enabled') === true;
|
||||
@@ -63,9 +54,9 @@ class ContentSecurityPolicy
|
||||
"script-src 'self' 'nonce-{$nonce}' 'unsafe-eval' 'strict-dynamic' https://challenges.cloudflare.com https://cdn.tiny.cloud https://cdn.jsdelivr.net/ https://static.cloudflareinsights.com/ https://cdnjs.cloudflare.com/ https://unpkg.com/ https://cdn.tailwindcss.com/ https://code.jquery.com https://apis.google.com https://www.google.com https://www.gstatic.com https://ajax.cloudflare.com blob:",
|
||||
"script-src-elem 'self' 'nonce-{$nonce}' https://challenges.cloudflare.com https://cdn.tiny.cloud https://cdn.jsdelivr.net/ https://static.cloudflareinsights.com/ https://cdnjs.cloudflare.com/ https://unpkg.com/ https://cdn.tailwindcss.com/ https://code.jquery.com https://apis.google.com https://www.google.com https://www.gstatic.com https://ajax.cloudflare.com",
|
||||
"script-src-attr 'none'",
|
||||
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://cdn.jsdelivr.net/ https://cdnjs.cloudflare.com/ https://cdn.tiny.cloud",
|
||||
"style-src-elem 'self' 'unsafe-inline' https://fonts.googleapis.com https://cdn.jsdelivr.net/ https://cdnjs.cloudflare.com/ https://cdn.tiny.cloud",
|
||||
"font-src 'self' https://fonts.gstatic.com https://cdnjs.cloudflare.com/ https://cdn.tiny.cloud data:",
|
||||
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://fonts.bunny.net https://cdn.jsdelivr.net/ https://cdnjs.cloudflare.com/ https://cdn.tiny.cloud",
|
||||
"style-src-elem 'self' 'unsafe-inline' https://fonts.googleapis.com https://fonts.bunny.net https://cdn.jsdelivr.net/ https://cdnjs.cloudflare.com/ https://cdn.tiny.cloud",
|
||||
"font-src 'self' https://fonts.gstatic.com https://fonts.bunny.net https://cdnjs.cloudflare.com/ https://cdn.tiny.cloud data:",
|
||||
"img-src 'self' data: https: blob:",
|
||||
"connect-src 'self' https://www.google.com https://cdn.tiny.cloud https://sp.tinymce.com",
|
||||
"frame-src 'self' https://www.google.com https://www.gstatic.com https://challenges.cloudflare.com https://cdn.tiny.cloud data: blob:",
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit;
|
||||
|
||||
use App\Http\Middleware\ContentSecurityPolicy;
|
||||
use Illuminate\Config\Repository;
|
||||
use Illuminate\Container\Container;
|
||||
use Illuminate\Foundation\Vite;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Facade;
|
||||
use Laravel\Horizon\Horizon;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class ContentSecurityPolicyTest extends TestCase
|
||||
{
|
||||
private ?Container $originalContainer;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->originalContainer = Container::getInstance();
|
||||
|
||||
$container = new Container;
|
||||
$container->instance('config', new Repository([
|
||||
'captcha' => [
|
||||
'provider' => null,
|
||||
'turnstile' => ['enabled' => false],
|
||||
],
|
||||
]));
|
||||
$container->instance(Vite::class, new Vite);
|
||||
|
||||
Container::setInstance($container);
|
||||
Facade::setFacadeApplication($container);
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
Horizon::$nonceAttribute = '';
|
||||
Container::setInstance($this->originalContainer);
|
||||
Facade::setFacadeApplication($this->originalContainer);
|
||||
|
||||
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.'"'));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user