Update blocking of abusive services

This commit is contained in:
DariusIII
2026-06-30 17:28:09 +02:00
parent 2b12bd8d0c
commit 5b927b4581
3 changed files with 89 additions and 0 deletions
+4
View File
@@ -109,6 +109,10 @@ CLOUDFLARE_IPS_RETRY_SLEEP_MS=250
CLOUDFLARE_IPS_STORAGE_PATH=storage/app/cloudflare/trusted-proxies.json
CLOUDFLARE_TRUST_REMOTE_ADDR_FALLBACK=true
# Block configured indexer apps only when they proxy NZB downloads instead of redirecting to the downloader.
BLOCK_PROXY_INDEXER_APPS=false
BLOCK_PROXY_INDEXER_APP_USER_AGENTS=Prowlarr/,NZBHydra2
APP_LOCALE=en
APP_FALLBACK_LOCALE=en
APP_FAKER_LOCALE=en_US
@@ -17,6 +17,7 @@ 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)
* - Oracle Cloud (ASN: AS31898)
* - Cloudflare WARP (ASN: AS13335)
*/
@@ -71,6 +72,16 @@ class BlockAbusiveServices
return $this->blockedResponse('Access denied: Streaming services are not allowed.');
}
if ($this->shouldBlockProxyIndexerApp($request, $userAgent)) {
Log::warning('Blocked proxy indexer app request', [
'ip' => $ip,
'user_agent' => $userAgent,
'uri' => $request->getRequestUri(),
]);
return $this->blockedResponse('Access denied: Proxy indexer app access is not allowed.');
}
// Check for blocked ASNs
$asnInfo = $this->getAsnInfo($ip);
if ($asnInfo !== null && isset($this->blockedAsns[$asnInfo['asn']])) {
@@ -107,6 +118,78 @@ class BlockAbusiveServices
return false;
}
/**
* Check whether an opt-in proxy indexer app block should apply.
*/
protected function shouldBlockProxyIndexerApp(Request $request, string $userAgent): bool
{
if (! (bool) config('nntmux.block_proxy_indexer_apps', false)) {
return false;
}
if (! $this->isPublicIndexerEndpoint($request)) {
return false;
}
return $this->matchesAnyUserAgentPattern(
$userAgent,
$this->configuredProxyIndexerAppUserAgents()
);
}
/**
* Detect public indexer API/RSS endpoints while leaving unrelated requests alone.
*/
protected function isPublicIndexerEndpoint(Request $request): bool
{
if ($request->is('api/v1/api') || $request->is('api/v2/*')) {
return true;
}
return $request->is('rss/*') && ! $request->is('rss/health');
}
/**
* @return array<int, string>
*/
protected function configuredProxyIndexerAppUserAgents(): array
{
$configured = config('nntmux.block_proxy_indexer_app_user_agents', []);
if (is_string($configured)) {
$configured = preg_split('/[\r\n,]+/', $configured) ?: [];
}
if (! is_array($configured)) {
Log::warning('Ignoring invalid proxy indexer app User-Agent configuration.', [
'configured_type' => get_debug_type($configured),
]);
return [];
}
return array_values(array_filter(array_map(
static fn (mixed $pattern): string => is_string($pattern) ? trim($pattern) : '',
$configured,
)));
}
/**
* @param array<int, string> $patterns
*/
protected function matchesAnyUserAgentPattern(string $userAgent, array $patterns): bool
{
$lowerUserAgent = strtolower($userAgent);
foreach ($patterns as $pattern) {
if (str_contains($lowerUserAgent, strtolower($pattern))) {
return true;
}
}
return false;
}
/**
* Get ASN information for an IP address.
* Uses ip-api.com with caching to minimize API calls.
+2
View File
@@ -21,6 +21,8 @@ return [
'purge_inactive_users' => env('PURGE_INACTIVE_USERS', false),
'purge_inactive_users_days' => env('PURGE_INACTIVE_USERS_DAYS', 180),
'mysql_search_fallback' => env('MYSQL_SEARCH_FALLBACK', false), // Disable MySQL LIKE fallback when Manticore/Elasticsearch return no results
'block_proxy_indexer_apps' => (bool) env('BLOCK_PROXY_INDEXER_APPS', false),
'block_proxy_indexer_app_user_agents' => env('BLOCK_PROXY_INDEXER_APP_USER_AGENTS', 'Prowlarr/,NZBHydra2'),
/*
|--------------------------------------------------------------------------