From 9db444467b0673c1a4c8a3bf95d96ea55b56092d Mon Sep 17 00:00:00 2001 From: Aydin Hassan Date: Thu, 22 Oct 2026 17:21:35 +0200 Subject: [PATCH] Strip the inherited env via getenv() and keep PATH/HOME The env was stripped using $_ENV, which is empty when variables_order lacks E, so secrets leaked to the process. When it was populated, PATH was removed too, so composer could not find php. Input env now also overrides the defaults. Co-Authored-By: Claude Opus 5.5 --- src/Process/HostProcessFactory.php | 21 +++++++++++++++++---- test/Process/HostProcessFactoryTest.php | 18 +++++++++++++++++- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/src/Process/HostProcessFactory.php b/src/Process/HostProcessFactory.php index 7271f27e..af7196d8 100644 --- a/src/Process/HostProcessFactory.php +++ b/src/Process/HostProcessFactory.php @@ -9,6 +9,11 @@ final class HostProcessFactory implements ProcessFactory { + /** + * Inherited env vars that are kept, everything else is unset so we don't leak secrets to the process + */ + private const ALLOWED_ENV = ['PATH', 'HOME', 'COMPOSER_HOME']; + private ExecutableFinder $executableFinder; public function __construct(?ExecutableFinder $executableFinder = null) @@ -28,19 +33,27 @@ public function create(ProcessInput $processInput): Process return new Process( [$executablePath, ...$processInput->getArgs()], $processInput->getWorkingDirectory(), - $this->getDefaultEnv() + $processInput->getEnv(), + array_merge($this->getDefaultEnv(), $processInput->getEnv()), $processInput->getInput(), 10, ); } /** - * @return array + * Symfony Process inherits the parent env, so we unset every inherited var by setting it to false. + * We read from getenv() rather than $_ENV, as $_ENV is empty when variables_order does not include E. + * + * @return array */ private function getDefaultEnv(): array { - $env = array_map(fn() => false, $_ENV); - $env + array_map(fn() => false, $_SERVER); + $env = []; + + foreach (array_keys(getenv() + $_ENV) as $name) { + if (!in_array($name, self::ALLOWED_ENV, true)) { + $env[(string) $name] = false; + } + } return $env; } diff --git a/test/Process/HostProcessFactoryTest.php b/test/Process/HostProcessFactoryTest.php index e2172e59..8e4c6cce 100644 --- a/test/Process/HostProcessFactoryTest.php +++ b/test/Process/HostProcessFactoryTest.php @@ -68,7 +68,23 @@ public function testCreateWithEnv(): void $input = new ProcessInput('composer', ['one', 'two'], __DIR__, ['SOME_VAR' => 'value']); $process = $factory->create($input); - static::assertSame(['SOME_VAR' => 'value'], $process->getEnv()); + static::assertSame('value', $process->getEnv()['SOME_VAR']); + } + + public function testInheritedEnvIsRemovedExceptAllowed(): void + { + putenv('PHPSCHOOL_SECRET=secret'); + + try { + $process = (new HostProcessFactory())->create( + new ProcessInput('php', ['-r', 'echo json_encode([getenv("PHPSCHOOL_SECRET"), getenv("PATH") !== false]);'], __DIR__, []), + ); + $process->mustRun(); + + static::assertSame('[false,true]', $process->getOutput()); + } finally { + putenv('PHPSCHOOL_SECRET'); + } } public function testWithInput(): void