From a8a2c30646f4ca6d9fc2f2c86de10fe188b60b15 Mon Sep 17 00:00:00 2001 From: robertSt7 Date: Fri, 16 Oct 2026 11:40:18 +0200 Subject: [PATCH 1/4] Disable the debug toolbar and the /_profiler/phpinfo page in the dev environment The profiler route /_profiler/phpinfo prints phpinfo() (environment variables, secrets) to anyone who can reach a dev instance, and it stays reachable when the toolbar is switched off. Turn the toolbar off by default and replace the phpinfo route with one that never matches, so the URL falls through to the profiler token route. The rest of the profiler keeps working. Adds a functional test that boots the dev kernel and checks the route, the response and the toolbar mode. Co-Authored-By: Claude Sonnet 5.5 --- config/packages/dev/config.yaml | 5 ++ config/routes/dev/routes.yaml | 9 ++++ tests/Functional/Dev/ProfilerTest.php | 69 +++++++++++++++++++++++++++ 3 files changed, 83 insertions(+) create mode 100644 tests/Functional/Dev/ProfilerTest.php diff --git a/config/packages/dev/config.yaml b/config/packages/dev/config.yaml index d77183c87..3d89b4a1e 100644 --- a/config/packages/dev/config.yaml +++ b/config/packages/dev/config.yaml @@ -1,2 +1,7 @@ imports: - { resource: ../../config.yaml } + +# The Symfony debug toolbar is off by default, so a dev instance that is reachable from the network does not +# advertise the profiler on every page. Set it to `true` locally when you want it. +web_profiler: + toolbar: false diff --git a/config/routes/dev/routes.yaml b/config/routes/dev/routes.yaml index 0bec5d898..cb01f7785 100644 --- a/config/routes/dev/routes.yaml +++ b/config/routes/dev/routes.yaml @@ -1,2 +1,11 @@ _pimcore_dev: resource: "@PimcoreCoreBundle/config/routing_dev.yaml" + +# Symfony's profiler route `/_profiler/phpinfo` prints phpinfo() - including environment variables and secrets - to +# anyone who can reach the application, and it is not affected by `web_profiler.toolbar`. Redefining the route +# under the same name replaces it with one that never matches, so the URL falls through to the profiler's token +# route, which only reports an unknown token, while the rest of the profiler keeps working. Delete this route to +# get the page back. +_profiler_phpinfo: + path: /_profiler/phpinfo + condition: "false" diff --git a/tests/Functional/Dev/ProfilerTest.php b/tests/Functional/Dev/ProfilerTest.php new file mode 100644 index 000000000..3c7fe5917 --- /dev/null +++ b/tests/Functional/Dev/ProfilerTest.php @@ -0,0 +1,69 @@ + 'dev', 'debug' => true]); + } + + public function testPhpinfoRouteIsNotMatched(): void + { + $match = self::$kernel->getContainer()->get('router')->match('/_profiler/phpinfo'); + + // The URL must fall through to the profiler's token route instead of reaching the phpinfo action. + self::assertNotSame('_profiler_phpinfo', $match['_route']); + self::assertSame('_profiler', $match['_route']); + self::assertSame('phpinfo', $match['token']); + } + + public function testPhpinfoIsNotServed(): void + { + $response = self::$kernel->handle(Request::create('/_profiler/phpinfo')); + $content = (string) $response->getContent(); + + // The profiler's token route answers instead, reporting an unknown token. + self::assertStringNotContainsString('PHP Version', $content); + self::assertStringNotContainsString('PHP Credits', $content); + self::assertStringContainsString('phpinfo', $content); + } + + public function testProfilerRoutesStillWork(): void + { + $router = self::$kernel->getContainer()->get('router'); + + // Only phpinfo is blocked: the profiler itself and its links to the phpinfo route keep working. + self::assertSame('_profiler_search_results', $router->match('/_profiler/abc123/search/results')['_route']); + self::assertSame('/_profiler/phpinfo', $router->generate('_profiler_phpinfo')); + } + + public function testToolbarIsDisabledByDefault(): void + { + $container = self::$kernel->getContainer(); + + // The parameter is only registered while the toolbar (or redirect interception) is enabled. + $enabled = $container->hasParameter('web_profiler.debug_toolbar.mode') + && WebDebugToolbarListener::ENABLED === $container->getParameter('web_profiler.debug_toolbar.mode'); + + self::assertFalse($enabled); + } +} From f2bad2e2fb01991e013da9415d36f0366442c97e Mon Sep 17 00:00:00 2001 From: robertSt7 Date: Fri, 16 Oct 2026 12:17:22 +0200 Subject: [PATCH 2/4] Run the dev profiler test against the test database Booting the dev environment skips the database override of the test environment, so the kernel would use the development database instead of the one the test runner provides. Point DATABASE_URL at PIMCORE_TEST_DB_DSN while the test runs and restore the environment afterwards. Co-Authored-By: Claude Sonnet 5.5 --- tests/Functional/Dev/ProfilerTest.php | 48 +++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/tests/Functional/Dev/ProfilerTest.php b/tests/Functional/Dev/ProfilerTest.php index 3c7fe5917..6e125c3f9 100644 --- a/tests/Functional/Dev/ProfilerTest.php +++ b/tests/Functional/Dev/ProfilerTest.php @@ -13,6 +13,10 @@ */ class ProfilerTest extends KernelTestCase { + private bool $databaseUrlOverridden = false; + + private string|false $originalDatabaseUrl = false; + protected static function getKernelClass(): string { return Kernel::class; @@ -22,10 +26,19 @@ protected function setUp(): void { parent::setUp(); + $this->useTestDatabase(); + // The profiler is only available in the dev environment, so boot that one instead of the test environment. self::bootKernel(['environment' => 'dev', 'debug' => true]); } + protected function tearDown(): void + { + parent::tearDown(); + + $this->restoreDatabaseUrl(); + } + public function testPhpinfoRouteIsNotMatched(): void { $match = self::$kernel->getContainer()->get('router')->match('/_profiler/phpinfo'); @@ -66,4 +79,39 @@ public function testToolbarIsDisabledByDefault(): void self::assertFalse($enabled); } + + /** + * The dev environment does not apply the test database override of config/packages/test, so point the + * default connection at the test database explicitly instead of the development one. + */ + private function useTestDatabase(): void + { + $dsn = $_SERVER['PIMCORE_TEST_DB_DSN'] ?? $_ENV['PIMCORE_TEST_DB_DSN'] ?? getenv('PIMCORE_TEST_DB_DSN'); + if (!is_string($dsn) || '' === $dsn) { + return; + } + + $this->originalDatabaseUrl = getenv('DATABASE_URL'); + $this->databaseUrlOverridden = true; + + $_SERVER['DATABASE_URL'] = $_ENV['DATABASE_URL'] = $dsn; + putenv('DATABASE_URL=' . $dsn); + } + + private function restoreDatabaseUrl(): void + { + if (!$this->databaseUrlOverridden) { + return; + } + + if (false === $this->originalDatabaseUrl) { + unset($_SERVER['DATABASE_URL'], $_ENV['DATABASE_URL']); + putenv('DATABASE_URL'); + } else { + $_SERVER['DATABASE_URL'] = $_ENV['DATABASE_URL'] = $this->originalDatabaseUrl; + putenv('DATABASE_URL=' . $this->originalDatabaseUrl); + } + + $this->databaseUrlOverridden = false; + } } From ad41599b1706a0a801591667cc02b40427e8b1e2 Mon Sep 17 00:00:00 2001 From: robertSt7 Date: Fri, 16 Oct 2026 12:25:23 +0200 Subject: [PATCH 3/4] Restore each DATABASE_URL environment store separately in the profiler test Remember the original value and key presence of $_SERVER, $_ENV and the process environment individually, so teardown puts every store back exactly as it was instead of copying one value to all. Co-Authored-By: Claude Sonnet 5.5 --- tests/Functional/Dev/ProfilerTest.php | 37 ++++++++++++++++++--------- 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/tests/Functional/Dev/ProfilerTest.php b/tests/Functional/Dev/ProfilerTest.php index 6e125c3f9..21abac24a 100644 --- a/tests/Functional/Dev/ProfilerTest.php +++ b/tests/Functional/Dev/ProfilerTest.php @@ -13,9 +13,12 @@ */ class ProfilerTest extends KernelTestCase { - private bool $databaseUrlOverridden = false; - - private string|false $originalDatabaseUrl = false; + /** + * Original DATABASE_URL of each environment store while it is overridden; null means the store had no entry. + * + * @var array{server: array{value: mixed}|null, env: array{value: mixed}|null, process: array{value: string}|null}|null + */ + private ?array $originalDatabaseUrl = null; protected static function getKernelClass(): string { @@ -91,8 +94,12 @@ private function useTestDatabase(): void return; } - $this->originalDatabaseUrl = getenv('DATABASE_URL'); - $this->databaseUrlOverridden = true; + $processValue = getenv('DATABASE_URL'); + $this->originalDatabaseUrl = [ + 'server' => array_key_exists('DATABASE_URL', $_SERVER) ? ['value' => $_SERVER['DATABASE_URL']] : null, + 'env' => array_key_exists('DATABASE_URL', $_ENV) ? ['value' => $_ENV['DATABASE_URL']] : null, + 'process' => false === $processValue ? null : ['value' => $processValue], + ]; $_SERVER['DATABASE_URL'] = $_ENV['DATABASE_URL'] = $dsn; putenv('DATABASE_URL=' . $dsn); @@ -100,18 +107,24 @@ private function useTestDatabase(): void private function restoreDatabaseUrl(): void { - if (!$this->databaseUrlOverridden) { + if (null === $this->originalDatabaseUrl) { return; } - if (false === $this->originalDatabaseUrl) { - unset($_SERVER['DATABASE_URL'], $_ENV['DATABASE_URL']); - putenv('DATABASE_URL'); + if (null === $this->originalDatabaseUrl['server']) { + unset($_SERVER['DATABASE_URL']); } else { - $_SERVER['DATABASE_URL'] = $_ENV['DATABASE_URL'] = $this->originalDatabaseUrl; - putenv('DATABASE_URL=' . $this->originalDatabaseUrl); + $_SERVER['DATABASE_URL'] = $this->originalDatabaseUrl['server']['value']; } - $this->databaseUrlOverridden = false; + if (null === $this->originalDatabaseUrl['env']) { + unset($_ENV['DATABASE_URL']); + } else { + $_ENV['DATABASE_URL'] = $this->originalDatabaseUrl['env']['value']; + } + + putenv('DATABASE_URL' . (null === $this->originalDatabaseUrl['process'] ? '' : '=' . $this->originalDatabaseUrl['process']['value'])); + + $this->originalDatabaseUrl = null; } } From c0eec153160f1afe01c7f30f6c6215ba45b3615b Mon Sep 17 00:00:00 2001 From: robertSt7 Date: Thu, 22 Oct 2026 12:22:02 +0200 Subject: [PATCH 4/4] Document how to activate the profiler toolbar and the phpinfo page in dev Explain the new dev defaults (toolbar off, /_profiler/phpinfo blocked) and the steps to turn each of them back on, including the cache clear that is needed for the change to take effect. Co-Authored-By: Claude Sonnet 5.5 --- README.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/README.md b/README.md index a68344f2b..87757d246 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,39 @@ You don't need to have a PHP environment with composer installed. * Pimcore Studio, using the credentials from `.env` (default `admin` / `admin`): +## Symfony profiler (dev environment) + +In the `dev` environment (`APP_ENV=dev`) the skeleton ships with two safe defaults for the Symfony web profiler: + +* The **debug toolbar is off**. +* The **`/_profiler/phpinfo` page is blocked**. It prints `phpinfo()` - including environment variables and secrets - to + anyone who can reach the instance, and Symfony has no setting to switch it off separately. + +The profiler itself keeps collecting data and stays available at `/_profiler`. + +Only activate the following on instances that untrusted users cannot reach, and never in production (use `APP_ENV=prod` +there). After each change, clear the cache - the change does not show up before that - and reload the page: +`bin/console cache:clear` (with Docker: `docker compose exec php bin/console cache:clear`). + +### Activate the toolbar + +Set `web_profiler.toolbar` to `true` in `config/packages/dev/config.yaml`: + +```yaml +web_profiler: + toolbar: true +``` + +### Activate the `/_profiler/phpinfo` page + +Delete the `_profiler_phpinfo` route (including its comment) at the end of `config/routes/dev/routes.yaml`, so that only +the `_pimcore_dev` import remains: + +```yaml +_pimcore_dev: + resource: "@PimcoreCoreBundle/config/routing_dev.yaml" +``` + ## Pimcore Platform Version By default, Pimcore Platform Version is added as a dependency which ensures installation of compatible and in combination with each other tested versions of additional Pimcore modules. More information about the Platform Version can be found in the