diff --git a/.env.example b/.env.example index 702c98dee6..ab98bd3acd 100644 --- a/.env.example +++ b/.env.example @@ -51,3 +51,4 @@ CUSTOM_EXECUTORS=false CACHE_SETTING_DRIVER=cache_settings CACHE_SETTING_PREFIX=settings: AI_ENABLE_RAG_COLLECTIONS=false +AI_ENABLE_AGENTS=false diff --git a/ProcessMaker/Http/Kernel.php b/ProcessMaker/Http/Kernel.php index c7d6d7ab8c..8331c0ade8 100644 --- a/ProcessMaker/Http/Kernel.php +++ b/ProcessMaker/Http/Kernel.php @@ -19,6 +19,7 @@ class Kernel extends HttpKernel \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class, Middleware\TrimStrings::class, \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class, + Middleware\TrustHosts::class, Middleware\TrustProxies::class, Middleware\BrowserCache::class, ServerTimingMiddleware::class, @@ -87,7 +88,7 @@ class Kernel extends HttpKernel 'no-cache' => Middleware\NoCache::class, 'admin' => Middleware\IsAdmin::class, 'etag' => Middleware\Etag\HandleEtag::class, - 'file_size_check' => Middleware\FileSizeCheck::class, + 'file_size_check' => Middleware\FileSizeCheck::class ]; /** diff --git a/ProcessMaker/Http/Middleware/TrustHosts.php b/ProcessMaker/Http/Middleware/TrustHosts.php new file mode 100644 index 0000000000..68330a6e89 --- /dev/null +++ b/ProcessMaker/Http/Middleware/TrustHosts.php @@ -0,0 +1,39 @@ +allSubdomainsOfApplicationUrl(); + return [$trustedHost]; + } + + public function handle(Request $request, $next) + { + if ($request->hasHeader('X-Forwarded-Host')) { + $forwardedHost = $request->header('X-Forwarded-Host'); + $trustedPattern = $this->allSubdomainsOfApplicationUrl(); + + if (!$this->hostIsValid($forwardedHost, $trustedPattern)) { + \Log::warning('Rejected request with untrusted X-Forwarded-Host', [ + 'forwarded_host' => $forwardedHost, + 'trusted_pattern' => $trustedPattern + ]); + abort(400, 'Invalid Host Header'); + } + } + + return parent::handle($request, $next); + } + + protected function hostIsValid(string $host, string $pattern): bool + { + return preg_match('/' . str_replace('/', '\/', $pattern) . '/', $host) === 1; + } +} \ No newline at end of file diff --git a/tests/Feature/Http/Middleware/TrustHostsTest.php b/tests/Feature/Http/Middleware/TrustHostsTest.php new file mode 100644 index 0000000000..262fab49eb --- /dev/null +++ b/tests/Feature/Http/Middleware/TrustHostsTest.php @@ -0,0 +1,61 @@ +middleware = new TrustHosts($this->app); + } + + public function test_valid_trusted_host() + { + // Set app URL for testing + Config::set('app.url', 'https://example.processmaker.net'); + + $request = Request::create('https://subdomain.example.processmaker.net'); + $request->headers->set('X-Forwarded-Host', 'subdomain.example.processmaker.net'); + + $response = $this->middleware->handle($request, function ($req) { + return response()->json(['status' => 'success']); + }); + + $this->assertEquals(200, $response->status()); + } + + public function test_invalid_trusted_host() + { + // Set app URL for testing + Config::set('app.url', 'https://example.processmaker.net'); + + $request = Request::create('https://malicious-site.com'); + $request->headers->set('X-Forwarded-Host', 'malicious-site.com'); + + $this->expectException(\Symfony\Component\HttpKernel\Exception\HttpException::class); + $this->expectExceptionMessage('Invalid Host Header'); + + $this->middleware->handle($request, function ($req) { + return response()->json(['status' => 'success']); + }); + } + + public function test_missing_forwarded_host() + { + $request = Request::create('https://example.processmaker.net'); + + $response = $this->middleware->handle($request, function ($req) { + return response()->json(['status' => 'success']); + }); + + $this->assertEquals(200, $response->status()); + } +} \ No newline at end of file