From e990a0e53c5034d4d71c3964474ebc07671569bf Mon Sep 17 00:00:00 2001 From: Kevin Pfeifer Date: Thu, 12 Mar 2026 22:14:35 +0100 Subject: [PATCH 1/2] update with current cakephp/app state --- composer.json | 12 ++--- config/.env.example | 2 +- config/app.php | 2 +- config/bootstrap.php | 27 +++--------- phpunit.xml.dist | 1 - src/Application.php | 11 ++++- src/Middleware/HostHeaderMiddleware.php | 58 +++++++++++++++++++++++++ tests/TestCase/ApplicationTest.php | 5 ++- 8 files changed, 87 insertions(+), 31 deletions(-) create mode 100644 src/Middleware/HostHeaderMiddleware.php diff --git a/composer.json b/composer.json index fb27d14..72d4ce4 100644 --- a/composer.json +++ b/composer.json @@ -6,17 +6,17 @@ "homepage": "https://cakephp.org", "require": { "php": ">=8.1", - "cakephp/cakephp": "5.2.*", - "cakephp/migrations": "^4.0.0", + "cakephp/cakephp": "5.3.*", + "cakephp/migrations": "^5.0", "cakephp/plugin-installer": "^2.0", "mobiledetect/mobiledetectlib": "^4.8.03" }, "require-dev": { - "cakephp/bake": "^3.0.0", - "cakephp/cakephp-codesniffer": "^5.0", - "cakephp/debug_kit": "^5.0.0", + "cakephp/bake": "^3.6", + "cakephp/cakephp-codesniffer": "^5.3", + "cakephp/debug_kit": "^5.2", "josegonzalez/dotenv": "^4.0", - "phpunit/phpunit": "^10.5.5 || ^11.1.3 || ^12.1" + "phpunit/phpunit": "^11.1.3 || ^12.1 || ^13.0" }, "suggest": { "cakephp/repl": "Console tools for a REPL interface for CakePHP applications.", diff --git a/config/.env.example b/config/.env.example index ceab392..793f83b 100644 --- a/config/.env.example +++ b/config/.env.example @@ -20,7 +20,7 @@ export APP_DEFAULT_LOCALE="en_US" export APP_DEFAULT_TIMEZONE="UTC" # SECURITY: Set this to your domain to prevent Host Header Injection attacks # This is REQUIRED in production for password resets and other security features -export APP_FULL_BASE_URL="https://yourdomain.com" +export APP_FULL_BASE_URL="https://example.com" export SECURITY_SALT="__SALT__" # Uncomment these to define cache configuration via environment variables. diff --git a/config/app.php b/config/app.php index 81d15e1..9876cf4 100644 --- a/config/app.php +++ b/config/app.php @@ -40,7 +40,7 @@ * IMPORTANT: This MUST be set in production to prevent Host Header Injection attacks * that can compromise password reset and other security-critical features. * Set this via APP_FULL_BASE_URL environment variable or directly in config. - * Example: 'https://yourdomain.com' + * Example: 'https://example.com' * When not set, the application will throw an exception in production mode. * - imageBaseUrl - Web path to the public images/ directory under webroot. * - cssBaseUrl - Web path to the public css/ directory under webroot. diff --git a/config/bootstrap.php b/config/bootstrap.php index ce29326..82a92c6 100644 --- a/config/bootstrap.php +++ b/config/bootstrap.php @@ -35,7 +35,6 @@ use Cake\Cache\Cache; use Cake\Core\Configure; use Cake\Core\Configure\Engine\PhpConfig; -use Cake\Core\Exception\CakeException; use Cake\Datasource\ConnectionManager; use Cake\Error\ErrorTrap; use Cake\Error\ExceptionTrap; @@ -146,37 +145,25 @@ } /* - * SECURITY: Validate and set the full base URL. - * This URL is used as the base of all absolute links. + * Set the full base URL for the application. * - * IMPORTANT: In production, App.fullBaseUrl MUST be explicitly configured to prevent - * Host Header Injection attacks. Relying on the HTTP_HOST header can allow attackers - * to hijack password reset tokens and other security-critical operations. + * SECURITY: In production, App.fullBaseUrl MUST be explicitly configured to prevent + * Host Header Injection attacks. The HostHeaderMiddleware enforces this requirement + * and validates incoming Host headers against the configured value. * * Set APP_FULL_BASE_URL in your environment variables or configure App.fullBaseUrl * in config/app.php or config/app_local.php * - * Example: APP_FULL_BASE_URL=https://yourdomain.com + * Example: APP_FULL_BASE_URL=https://example.com */ $fullBaseUrl = Configure::read('App.fullBaseUrl'); if (!$fullBaseUrl) { $httpHost = env('HTTP_HOST'); - /* - * Only enforce fullBaseUrl requirement when we're in a web request context. - * This allows CLI tools (like PHPStan) to load the bootstrap without throwing. - */ - if (!Configure::read('debug') && $httpHost) { - throw new CakeException( - 'SECURITY: App.fullBaseUrl is not configured. ' . - 'This is required in production to prevent Host Header Injection attacks. ' . - 'Set APP_FULL_BASE_URL environment variable or configure App.fullBaseUrl in config/app.php', - ); - } - /* * Development mode fallback: Use HTTP_HOST for convenience. - * WARNING: This is ONLY safe in development. Never use this pattern in production! + * WARNING: This is ONLY safe in development. In production, the + * HostHeaderMiddleware will reject requests when fullBaseUrl is not configured. */ if ($httpHost) { $s = null; diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 4dfd186..38c7d78 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -8,7 +8,6 @@ xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.1/phpunit.xsd"> - diff --git a/src/Application.php b/src/Application.php index d2213e9..9ca8b6b 100644 --- a/src/Application.php +++ b/src/Application.php @@ -16,6 +16,7 @@ */ namespace App; +use App\Middleware\HostHeaderMiddleware; use Cake\Core\Configure; use Cake\Core\ContainerInterface; use Cake\Datasource\FactoryLocator; @@ -51,7 +52,10 @@ public function bootstrap(): void if (PHP_SAPI !== 'cli') { // The bake plugin requires fallback table classes to work properly - FactoryLocator::add('Table', (new TableLocator())->allowFallbackClass(false)); + FactoryLocator::add( + 'Table', + (new TableLocator())->allowFallbackClass(false), // @phpstan-ignore argument.type + ); } } @@ -68,6 +72,11 @@ public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue // and make an error page/response ->add(new ErrorHandlerMiddleware(Configure::read('Error'), $this)) + // Validate Host header to prevent Host Header Injection attacks. + // In production, ensures App.fullBaseUrl is configured and validates + // the incoming Host header against it. + ->add(new HostHeaderMiddleware()) + // Handle plugin/theme assets like CakePHP normally does. ->add(new AssetMiddleware([ 'cacheTime' => Configure::read('Asset.cacheTime'), diff --git a/src/Middleware/HostHeaderMiddleware.php b/src/Middleware/HostHeaderMiddleware.php new file mode 100644 index 0000000..e6ab578 --- /dev/null +++ b/src/Middleware/HostHeaderMiddleware.php @@ -0,0 +1,58 @@ +handle($request); + } + + $fullBaseUrl = Configure::read('App.fullBaseUrl'); + if (!$fullBaseUrl) { + throw new InternalErrorException( + 'SECURITY: App.fullBaseUrl is not configured. ' . + 'This is required in production to prevent Host Header Injection attacks. ' . + 'Set APP_FULL_BASE_URL environment variable or configure App.fullBaseUrl in config/app.php', + ); + } + + $configuredHost = parse_url($fullBaseUrl, PHP_URL_HOST); + $requestHost = $request->getUri()->getHost(); + + if ($configuredHost && $requestHost && strtolower($configuredHost) !== strtolower($requestHost)) { + throw new BadRequestException( + 'Invalid Host header. Request host does not match configured application host.', + ); + } + + return $handler->handle($request); + } +} diff --git a/tests/TestCase/ApplicationTest.php b/tests/TestCase/ApplicationTest.php index 6a65c52..63bc3d1 100644 --- a/tests/TestCase/ApplicationTest.php +++ b/tests/TestCase/ApplicationTest.php @@ -17,6 +17,7 @@ namespace App\Test\TestCase; use App\Application; +use App\Middleware\HostHeaderMiddleware; use Cake\Core\Configure; use Cake\Error\Middleware\ErrorHandlerMiddleware; use Cake\Http\MiddlewareQueue; @@ -78,8 +79,10 @@ public function testMiddleware() $this->assertInstanceOf(ErrorHandlerMiddleware::class, $middleware->current()); $middleware->seek(1); - $this->assertInstanceOf(AssetMiddleware::class, $middleware->current()); + $this->assertInstanceOf(HostHeaderMiddleware::class, $middleware->current()); $middleware->seek(2); + $this->assertInstanceOf(AssetMiddleware::class, $middleware->current()); + $middleware->seek(3); $this->assertInstanceOf(RoutingMiddleware::class, $middleware->current()); } } From 741266ae63456069c1ffa025d741058843da7a99 Mon Sep 17 00:00:00 2001 From: Kevin Pfeifer Date: Thu, 12 Mar 2026 22:18:03 +0100 Subject: [PATCH 2/2] adjust CI --- .github/workflows/ci.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 10fcbdc..27cfea8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,10 +21,12 @@ jobs: fail-fast: false matrix: include: - - php-version: '8.1' + - php-version: '8.2' dependencies: 'lowest' - php-version: '8.4' dependencies: 'highest' + - php-version: '8.5' + dependencies: 'highest' steps: - uses: actions/checkout@v5 @@ -61,7 +63,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '8.1' + php-version: '8.2' extensions: mbstring, intl coverage: none tools: cs2pr, phpstan:1.12