feat: add ALLOWED_HOSTNAMES environment variable support for Docker/Compose (#4544)

Allow configuring allowed hostnames via ALLOWED_HOSTNAMES environment
variable as an alternative to app.allowedHostnames in .env file. This
is more convenient for Docker/Compose deployments where environment
variables are set directly in compose files.

The ALLOWED_HOSTNAMES variable takes precedence over app.allowedHostnames
if both are set, allowing deployment-specific overrides.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Ollama <ollama@steganos.dev>
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
jekkos
2026-05-13 09:03:32 +02:00
committed by GitHub
parent fdd6a408ec
commit 2f5c0130f4
5 changed files with 117 additions and 5 deletions

View File

@@ -16,6 +16,9 @@ CI_ENVIRONMENT = production
# Configure with comma-separated list of domains/subdomains:
# app.allowedHostnames = 'yourdomain.com,www.yourdomain.com'
#
# Or via environment variable (useful for Docker/Compose):
# ALLOWED_HOSTNAMES=yourdomain.com,www.yourdomain.com
#
# For local development:
# app.allowedHostnames = 'localhost'
#

View File

@@ -58,9 +58,9 @@ class App extends BaseConfig
* Allowed Hostnames in the Site URL other than the hostname in the baseURL.
* If you want to accept multiple Hostnames, set this.
*
* E.g.,
* When your site URL ($baseURL) is 'http://example.com/', and your site
* also accepts 'http://media.example.com/' and 'http://accounts.example.com/':
* Or via environment variable (useful for Docker/Compose):
* ALLOWED_HOSTNAMES=example.com,www.example.com
*
* ['media.example.com', 'accounts.example.com']
*
* @var list<string>
@@ -286,7 +286,11 @@ class App extends BaseConfig
// Solution for CodeIgniter 4 limitation: arrays cannot be set from .env
// See: https://github.com/codeigniter4/CodeIgniter4/issues/7311
$envAllowedHostnames = getenv('app.allowedHostnames');
// Support both: app.allowedHostnames (from .env) and ALLOWED_HOSTNAMES (from environment/Docker)
$envAllowedHostnames = getenv('ALLOWED_HOSTNAMES');
if ($envAllowedHostnames === false || trim($envAllowedHostnames) === '') {
$envAllowedHostnames = getenv('app.allowedHostnames');
}
if ($envAllowedHostnames !== false && trim($envAllowedHostnames) !== '') {
$this->allowedHostnames = array_values(array_filter(
array_map('trim', explode(',', $envAllowedHostnames)),
@@ -327,7 +331,7 @@ class App extends BaseConfig
$errorMessage =
'Security: allowedHostnames is not configured. ' .
'Host header injection protection is disabled. ' .
'Set app.allowedHostnames in your .env file. ' .
'Set app.allowedHostnames in your .env file or ALLOWED_HOSTNAMES environment variable. ' .
'Example: app.allowedHostnames = "example.com,www.example.com" ' .
'Received Host: ' . $httpHost;

View File

@@ -46,6 +46,7 @@ services:
- .:/app
environment:
- CI_ENVIRONMENT=development
- ALLOWED_HOSTNAMES=localhost
- MYSQL_USERNAME=admin
- MYSQL_PASSWORD=pointofsale
- MYSQL_DB_NAME=ospos

View File

@@ -16,6 +16,7 @@ services:
- logs:/app/writable/logs
environment:
- CI_ENVIRONMENT=production
- ALLOWED_HOSTNAMES=localhost
- FORCE_HTTPS=false
- PHP_TIMEZONE=UTC
- MYSQL_USERNAME=admin

View File

@@ -18,6 +18,7 @@ class AppTest extends CIUnitTestCase
// Clean up environment
putenv('CI_ENVIRONMENT');
putenv('app.allowedHostnames');
putenv('ALLOWED_HOSTNAMES');
unset($_SERVER['HTTP_HOST']);
}
@@ -281,4 +282,106 @@ class AppTest extends CIUnitTestCase
putenv('app.allowedHostnames');
putenv('CI_ENVIRONMENT');
}
public function testAllowedHostnamesEnvVarParsedAsCommaSeparated(): void
{
// Set ALLOWED_HOSTNAMES environment variable
putenv('ALLOWED_HOSTNAMES=example.com,www.example.com,demo.example.com');
$_SERVER['HTTP_HOST'] = 'www.example.com';
$_SERVER['SCRIPT_NAME'] = '/index.php';
$_SERVER['HTTPS'] = null;
$app = new App();
// Constructor should parse comma-separated values
$this->assertEquals(['example.com', 'www.example.com', 'demo.example.com'], $app->allowedHostnames);
$this->assertStringContainsString('www.example.com', $app->baseURL);
// Clean up
putenv('ALLOWED_HOSTNAMES');
}
public function testAllowedHostnamesEnvVarTakesPrecedenceOverDotEnv(): void
{
// Set both environment variables
putenv('ALLOWED_HOSTNAMES=allowed1.com,allowed2.com');
putenv('app.allowedHostnames=dotenv1.com,dotenv2.com');
$_SERVER['HTTP_HOST'] = 'allowed1.com';
$_SERVER['SCRIPT_NAME'] = '/index.php';
$_SERVER['HTTPS'] = null;
$app = new App();
// ALLOWED_HOSTNAMES should take precedence
$this->assertEquals(['allowed1.com', 'allowed2.com'], $app->allowedHostnames);
$this->assertStringContainsString('allowed1.com', $app->baseURL);
// Clean up
putenv('ALLOWED_HOSTNAMES');
putenv('app.allowedHostnames');
}
public function testAllowedHostnamesEnvVarFallsBackToDotEnv(): void
{
// Only set app.allowedHostnames, not ALLOWED_HOSTNAMES
putenv('app.allowedHostnames=dotenv1.com,dotenv2.com');
$_SERVER['HTTP_HOST'] = 'dotenv1.com';
$_SERVER['SCRIPT_NAME'] = '/index.php';
$_SERVER['HTTPS'] = null;
$app = new App();
// Should fall back to app.allowedHostnames
$this->assertEquals(['dotenv1.com', 'dotenv2.com'], $app->allowedHostnames);
$this->assertStringContainsString('dotenv1.com', $app->baseURL);
// Clean up
putenv('app.allowedHostnames');
}
public function testAllowedHostnamesEnvVarTrimmedWhitespace(): void
{
// Set environment variable with whitespace
putenv('ALLOWED_HOSTNAMES= example.com , www.example.com , demo.example.com ');
$_SERVER['HTTP_HOST'] = 'example.com';
$_SERVER['SCRIPT_NAME'] = '/index.php';
$_SERVER['HTTPS'] = null;
$app = new App();
// Values should be trimmed
$this->assertEquals(['example.com', 'www.example.com', 'demo.example.com'], $app->allowedHostnames);
// Clean up
putenv('ALLOWED_HOSTNAMES');
}
public function testAllowedHostnamesEnvVarFiltersEmptyEntries(): void
{
// Trailing comma should not produce empty entry
putenv('ALLOWED_HOSTNAMES=example.com,');
$_SERVER['HTTP_HOST'] = 'example.com';
$_SERVER['SCRIPT_NAME'] = '/index.php';
$_SERVER['HTTPS'] = null;
$app = new App();
$this->assertEquals(['example.com'], $app->allowedHostnames);
// Clean up
putenv('ALLOWED_HOSTNAMES');
// Whitespace-only entry should be filtered
putenv('ALLOWED_HOSTNAMES=example.com, ,www.example.com');
$_SERVER['HTTP_HOST'] = 'example.com';
$app = new App();
$this->assertEquals(['example.com', 'www.example.com'], $app->allowedHostnames);
// Clean up
putenv('ALLOWED_HOSTNAMES');
}
}