From 9a9877a0dc264a908c161d96898864df88014868 Mon Sep 17 00:00:00 2001 From: KodeStar Date: Wed, 8 Jul 2026 18:22:49 +0100 Subject: [PATCH] Enforce TRUSTED_HOSTS allow-list regardless of APP_ENV The custom TrustHosts middleware only overrode hosts(), so it inherited the parent's shouldSpecifyTrustedHosts() gate, which skips enforcement whenever the app runs in the local environment or under the test runner. Heimdall ships APP_ENV=local by default (.env.example, copied to .env on install), so the TRUSTED_HOSTS allow-list a user configures per the .env.example guidance was never actually applied. Override shouldSpecifyTrustedHosts() to tie enforcement to configuration instead of environment: apply the allow-list whenever TRUSTED_HOSTS is set, in any environment; when it is unset hosts() is empty and enforcement stays off, preserving the historic no-restriction behaviour. Add handle()-driven tests covering both the configured and unconfigured cases. --- app/Http/Middleware/TrustHosts.php | 19 +++++++++++++++ tests/Feature/TrustHostsTest.php | 38 ++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/app/Http/Middleware/TrustHosts.php b/app/Http/Middleware/TrustHosts.php index f4190332d..cde8d8f43 100644 --- a/app/Http/Middleware/TrustHosts.php +++ b/app/Http/Middleware/TrustHosts.php @@ -38,4 +38,23 @@ class TrustHosts extends Middleware return $hosts; } + + /** + * Determine if the application should specify trusted hosts. + * + * The parent implementation skips enforcement whenever the app runs in the + * "local" environment (Heimdall's shipped default, see .env.example) or + * under the test runner, which would leave the TRUSTED_HOSTS allow-list + * silently unenforced for almost every real deployment. Instead we tie + * enforcement directly to configuration: apply the allow-list whenever one + * has actually been provided, in any environment. When TRUSTED_HOSTS is + * unset hosts() is empty and this returns false, preserving the historic + * no-restriction behaviour. + * + * @return bool + */ + protected function shouldSpecifyTrustedHosts() + { + return ! empty($this->hosts()); + } } diff --git a/tests/Feature/TrustHostsTest.php b/tests/Feature/TrustHostsTest.php index efa297f39..afc1b51e3 100644 --- a/tests/Feature/TrustHostsTest.php +++ b/tests/Feature/TrustHostsTest.php @@ -105,4 +105,42 @@ class TrustHostsTest extends TestCase $this->assertContains(TrustHosts::class, $globalMiddleware); $this->assertNotContains(\Illuminate\Http\Middleware\TrustHosts::class, $globalMiddleware); } + + public function test_handle_enforces_trusted_hosts_even_in_local_environment(): void + { + // The app runs as APP_ENV=local under the test runner; the parent + // middleware would skip enforcement entirely. Confirm handle() still + // applies the allow-list once TRUSTED_HOSTS is configured. + $this->setTrustedHostsEnv('example.com'); + + $request = Request::create('http://example.com/', 'GET'); + + $reachedNext = false; + $this->makeMiddleware()->handle($request, function ($req) use (&$reachedNext) { + $reachedNext = true; + + return $req; + }); + + $this->assertTrue($reachedNext); + + // The configured host is now accepted and any other Host is rejected. + $this->assertSame('example.com', Request::create('http://example.com/', 'GET')->getHost()); + + $this->expectException(SuspiciousOperationException::class); + Request::create('http://evil.com/', 'GET')->getHost(); + } + + public function test_handle_does_not_restrict_hosts_when_env_unset(): void + { + putenv('TRUSTED_HOSTS'); + unset($_ENV['TRUSTED_HOSTS'], $_SERVER['TRUSTED_HOSTS']); + + $request = Request::create('http://anything.example/', 'GET'); + + $this->makeMiddleware()->handle($request, fn ($req) => $req); + + // No allow-list configured -> arbitrary hosts still accepted. + $this->assertSame('anything.example', Request::create('http://anything.example/', 'GET')->getHost()); + } }