Fix blocking issue

This commit is contained in:
DariusIII
2026-07-01 14:44:16 +02:00
parent 95fed139b3
commit 3faeb1d63a
3 changed files with 101 additions and 27 deletions
+22 -8
View File
@@ -17,7 +17,11 @@ use Symfony\Component\HttpFoundation\Response;
* Blocks:
* - AIOStreams (User-Agent based)
* - UsenetStreamer (User-Agent based)
* - Configured indexer apps on public indexer API/RSS endpoints (User-Agent based, opt-in)
* - Configured indexer apps proxying NZB downloads (User-Agent based, opt-in).
* Only NZB download/grab requests are blocked, so proxied searches and RSS
* feeds keep working. Redirected grabs (Prowlarr/NZBHydra2 "Redirect" setting)
* arrive directly from the download client, whose User-Agent does not match
* the configured proxy patterns, and are therefore allowed.
* - Oracle Cloud (ASN: AS31898)
* - Cloudflare WARP (ASN: AS13335)
*/
@@ -83,13 +87,13 @@ class BlockAbusiveServices
}
if ($this->shouldBlockProxyIndexerApp($request, $userAgent)) {
Log::warning('Blocked proxy indexer app request', [
Log::warning('Blocked proxied NZB download from indexer app', [
'ip' => $ip,
'user_agent' => $userAgent,
'uri' => $this->safeRequestUri($request),
]);
return $this->blockedResponse('Access denied: Proxy indexer app access is not allowed.');
return $this->blockedResponse('Access denied: Proxying NZB downloads through indexer apps is not allowed.');
}
// Check for blocked ASNs
@@ -137,7 +141,7 @@ class BlockAbusiveServices
return false;
}
if (! $this->isPublicIndexerEndpoint($request)) {
if (! $this->isNzbDownloadRequest($request)) {
return false;
}
@@ -148,15 +152,25 @@ class BlockAbusiveServices
}
/**
* Detect public indexer API/RSS endpoints while leaving unrelated requests alone.
* Detect NZB download/grab requests so only proxied downloads are blocked.
*
* Searches, caps and RSS feeds are intentionally out of scope so proxied
* searches keep working. Redirected grabs arrive directly from the download
* client, whose User-Agent does not match the configured proxy patterns.
*/
protected function isPublicIndexerEndpoint(Request $request): bool
protected function isNzbDownloadRequest(Request $request): bool
{
if ($request->is('api/v1/api') || $request->is('api/v2/*')) {
if ($request->is('api/v2/getnzb')) {
return true;
}
return $request->is('rss/*') && ! $request->is('rss/health');
if (! $request->is('api/v1/api')) {
return false;
}
$type = strtolower(trim((string) $request->input('t', '')));
return in_array($type, ['get', 'g'], true);
}
/**
@@ -320,4 +320,62 @@ final class BlockAbusiveServicesTest extends TestCase
$this->assertArrayHasKey('message', $json);
$this->assertTrue($json['error']);
}
/**
* Test that a proxied NZB download is blocked when the block is enabled.
*/
public function test_blocks_proxied_download_from_indexer_app(): void
{
config()->set('nntmux.block_proxy_indexer_apps', true);
config()->set('nntmux.block_proxy_indexer_app_user_agents', 'Prowlarr/,NZBHydra2');
$request = Request::create('/api/v1/api?t=get&id=release-guid', 'GET');
$request->headers->set('User-Agent', 'Prowlarr/2.0.0');
$request->server->set('REMOTE_ADDR', '127.0.0.1');
$response = $this->middleware->handle($request, function ($req) {
return new Response('OK', 200);
});
$this->assertEquals(403, $response->getStatusCode());
$this->assertStringContainsString('Proxying NZB downloads through indexer apps is not allowed', $response->getContent());
}
/**
* Test that a proxied search is allowed even when the block is enabled.
*/
public function test_allows_proxied_search_from_indexer_app(): void
{
config()->set('nntmux.block_proxy_indexer_apps', true);
config()->set('nntmux.block_proxy_indexer_app_user_agents', 'Prowlarr/,NZBHydra2');
$request = Request::create('/api/v1/api?t=search&q=linux', 'GET');
$request->headers->set('User-Agent', 'Prowlarr/2.0.0');
$request->server->set('REMOTE_ADDR', '127.0.0.1');
$response = $this->middleware->handle($request, function ($req) {
return new Response('OK', 200);
});
$this->assertEquals(200, $response->getStatusCode());
}
/**
* Test that a redirected download (download-client UA) is allowed.
*/
public function test_allows_redirected_download_from_download_client(): void
{
config()->set('nntmux.block_proxy_indexer_apps', true);
config()->set('nntmux.block_proxy_indexer_app_user_agents', 'Prowlarr/,NZBHydra2');
$request = Request::create('/api/v1/api?t=get&id=release-guid', 'GET');
$request->headers->set('User-Agent', 'SABnzbd/4.3.3');
$request->server->set('REMOTE_ADDR', '127.0.0.1');
$response = $this->middleware->handle($request, function ($req) {
return new Response('OK', 200);
});
$this->assertEquals(200, $response->getStatusCode());
}
}
+21 -19
View File
@@ -76,40 +76,42 @@ class BlockAbusiveServicesTest extends TestCase
$this->assertSame(Response::HTTP_OK, $response->getStatusCode());
}
public function test_enabled_proxy_indexer_app_block_denies_configured_user_agent_on_newznab_endpoints(): void
public function test_enabled_proxy_indexer_app_block_denies_proxied_downloads(): void
{
config()->set('nntmux.block_proxy_indexer_apps', true);
config()->set('nntmux.block_proxy_indexer_app_user_agents', 'Prowlarr/,NZBHydra2');
foreach ([
'/api/v1/api?t=caps',
'/api/v1/api?t=search&q=linux',
'/api/v1/api?t=get&id=release-guid',
] as $uri) {
$response = $this->handleRequest($uri, 'Prowlarr/2.0.0');
'/api/v1/api?t=get&id=release-guid' => 'Prowlarr/2.0.0',
'/api/v1/api?t=g&id=release-guid' => 'Prowlarr/2.0.0',
'/api/v2/getnzb?id=release-guid' => 'NZBHydra2 8.3.0',
] as $uri => $userAgent) {
$response = $this->handleRequest($uri, $userAgent);
$this->assertSame(Response::HTTP_FORBIDDEN, $response->getStatusCode(), $uri);
$this->assertStringContainsString('Proxy indexer app access is not allowed', (string) $response->getContent());
$this->assertStringContainsString('Proxying NZB downloads through indexer apps is not allowed', (string) $response->getContent());
}
}
public function test_enabled_proxy_indexer_app_block_denies_configured_user_agent_on_v2_endpoints(): void
public function test_enabled_proxy_indexer_app_block_allows_proxied_searches(): void
{
config()->set('nntmux.block_proxy_indexer_apps', true);
config()->set('nntmux.block_proxy_indexer_app_user_agents', 'Prowlarr/,NZBHydra2');
foreach ([
'/api/v2/capabilities',
'/api/v2/search?q=linux',
'/api/v2/getnzb?id=release-guid',
] as $uri) {
$response = $this->handleRequest($uri, 'NZBHydra2 8.3.0');
'/api/v1/api?t=caps' => 'Prowlarr/2.0.0',
'/api/v1/api?t=search&q=linux' => 'Prowlarr/2.0.0',
'/api/v1/api?t=tvsearch&q=linux' => 'Prowlarr/2.0.0',
'/api/v2/capabilities' => 'NZBHydra2 8.3.0',
'/api/v2/search?q=linux' => 'NZBHydra2 8.3.0',
] as $uri => $userAgent) {
$response = $this->handleRequest($uri, $userAgent);
$this->assertSame(Response::HTTP_FORBIDDEN, $response->getStatusCode(), $uri);
$this->assertSame(Response::HTTP_OK, $response->getStatusCode(), $uri);
}
}
public function test_enabled_proxy_indexer_app_block_denies_configured_user_agent_on_rss_feed_endpoints(): void
public function test_enabled_proxy_indexer_app_block_allows_proxied_rss_feeds(): void
{
config()->set('nntmux.block_proxy_indexer_apps', true);
config()->set('nntmux.block_proxy_indexer_app_user_agents', 'Prowlarr/,NZBHydra2');
@@ -120,7 +122,7 @@ class BlockAbusiveServicesTest extends TestCase
] as $uri) {
$response = $this->handleRequest($uri, 'Prowlarr/2.0.0');
$this->assertSame(Response::HTTP_FORBIDDEN, $response->getStatusCode(), $uri);
$this->assertSame(Response::HTTP_OK, $response->getStatusCode(), $uri);
}
}
@@ -167,13 +169,13 @@ class BlockAbusiveServicesTest extends TestCase
config()->set('nntmux.block_proxy_indexer_app_user_agents', 'Prowlarr/,NZBHydra2');
$response = $this->handleRequest(
'/api/v1/api?t=search&apikey=secret-api-key&api_token=secret-token&passkey=secret-passkey&q=linux',
'/api/v1/api?t=get&id=release-guid&apikey=secret-api-key&api_token=secret-token&passkey=secret-passkey',
'Prowlarr/2.0.0'
);
$this->assertSame(Response::HTTP_FORBIDDEN, $response->getStatusCode());
Log::shouldHaveReceived('warning')
->with('Blocked proxy indexer app request', \Mockery::on(function (mixed $context): bool {
->with('Blocked proxied NZB download from indexer app', \Mockery::on(function (mixed $context): bool {
if (! is_array($context)) {
return false;
}
@@ -183,7 +185,7 @@ class BlockAbusiveServicesTest extends TestCase
return str_contains($uri, 'apikey=%5Bredacted%5D')
&& str_contains($uri, 'api_token=%5Bredacted%5D')
&& str_contains($uri, 'passkey=%5Bredacted%5D')
&& str_contains($uri, 'q=linux')
&& str_contains($uri, 'id=release-guid')
&& ! str_contains($uri, 'secret-api-key')
&& ! str_contains($uri, 'secret-token')
&& ! str_contains($uri, 'secret-passkey');