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.
This commit is contained in:
KodeStar
2026-07-08 18:22:49 +01:00
parent 5dcf462542
commit 9a9877a0dc
2 changed files with 57 additions and 0 deletions
+19
View File
@@ -38,4 +38,23 @@ class TrustHosts extends Middleware
return $hosts; 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());
}
} }
+38
View File
@@ -105,4 +105,42 @@ class TrustHostsTest extends TestCase
$this->assertContains(TrustHosts::class, $globalMiddleware); $this->assertContains(TrustHosts::class, $globalMiddleware);
$this->assertNotContains(\Illuminate\Http\Middleware\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());
}
} }