Skip to content

Commit 6a2395e

Browse files
committed
Fix: Forward COMPOSER_AUDIT_BLOCK_INSECURE and other env vars to Composer subprocess
This fixes a bug where COMPOSER_AUDIT_BLOCK_INSECURE=0 (and other Composer-related environment variables) were not being forwarded to Composer subprocesses during plugin installation, causing installations to fail with security advisory errors even when users explicitly opted out via environment variables. Changes: - Modified LocalMachineHelper::getProcess() to forward environment variables to subprocesses - Added getForwardedEnvironment() method that: * Automatically forwards Composer-related environment variables (COMPOSER_AUDIT_BLOCK_INSECURE, COMPOSER_ALLOW_SUPERUSER, etc.) * Supports TERMINUS_FORWARD_ENV for forwarding additional variables - Added unit tests (PluginEnvForwardingTest) to verify env forwarding - Added functional test using terminus-build-tools-plugin (which has known security advisories) to verify end-to-end behavior - Set up unit test infrastructure (TerminusTestCase, phpunit.unit.xml, autoload config) This fix applies to all commands that use LocalMachineHelper, ensuring consistent behavior across plugin install, update, and other operations. Fixes: [Issue number if applicable] Related: Composer 2.9.1+ security audit blocking behavior
1 parent 48dd6a5 commit 6a2395e

File tree

7 files changed

+456
-1
lines changed

7 files changed

+456
-1
lines changed

composer.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@
5757
"psr-4": {
5858
"Pantheon\\Terminus\\Tests\\Functional\\": "tests/Functional/",
5959
"Pantheon\\Terminus\\FeatureTests\\": "tests/features/bootstrap/",
60-
"Pantheon\\Terminus\\Scripts\\": "scripts/"
60+
"Pantheon\\Terminus\\Scripts\\": "scripts/",
61+
"Pantheon\\Terminus\\UnitTests\\": "tests/unit_tests/"
6162
},
6263
"classmap": [
6364
"scripts/UpdateClassLists.php"
@@ -110,6 +111,12 @@
110111
"test:behat": [
111112
"SHELL_INTERACTIVE=true TERMINUS_TEST_MODE=1 behat --colors --config tests/config/behat.yml --stop-on-failure --suite=default"
112113
],
114+
"test:unit": [
115+
"vendor/bin/phpunit --colors=always -c ./phpunit.unit.xml --debug --do-not-cache-result --verbose --stop-on-failure"
116+
],
117+
"tests:unit": [
118+
"@test:unit"
119+
],
113120
"test:short": [
114121
"XDEBUG_MODE=coverage vendor/bin/phpunit --colors=always -c ./phpunit.xml --debug --group=short --do-not-cache-result --verbose --stop-on-failure"
115122
],

phpunit.unit.xml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
4+
bootstrap="tests/unit_tests/bootstrap.php"
5+
colors="true">
6+
<testsuites>
7+
<testsuite name="unit">
8+
<directory suffix="Test.php">tests/unit_tests/</directory>
9+
</testsuite>
10+
</testsuites>
11+
<php>
12+
<server name="TERMINUS_CACHE_DIR" value="${TERMINUS_CACHE_DIR}" />
13+
</php>
14+
</phpunit>
15+

src/Helpers/LocalMachineHelper.php

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,9 +180,93 @@ protected function getProcess(string $cmd)
180180
$config = $this->getConfig();
181181
$process->setTimeout($config->get('timeout'));
182182

183+
// Forward environment variables to the subprocess.
184+
// This allows users to control Composer behavior (e.g., COMPOSER_AUDIT_BLOCK_INSECURE)
185+
// and other subprocess behavior via TERMINUS_FORWARD_ENV.
186+
$forwardedVars = $this->getForwardedEnvironment();
187+
if (!empty($forwardedVars)) {
188+
// Merge forwarded vars with the current process environment.
189+
// We need to get all environment variables and merge with forwarded ones.
190+
// Use getenv() to get actual environment variables (not all $_SERVER keys are env vars).
191+
$currentEnv = [];
192+
// Get all environment variables using getenv() for each known env var from $_SERVER
193+
// This ensures we only get actual environment variables, not other $_SERVER keys.
194+
foreach ($_SERVER as $key => $value) {
195+
if (is_string($key) && is_string($value)) {
196+
// Verify this is actually an environment variable by checking getenv()
197+
$envValue = getenv($key);
198+
if ($envValue !== false) {
199+
$currentEnv[$key] = $envValue;
200+
}
201+
}
202+
}
203+
// Also check $_ENV for any additional variables
204+
foreach ($_ENV as $key => $value) {
205+
if (is_string($key) && is_string($value) && !isset($currentEnv[$key])) {
206+
$currentEnv[$key] = $value;
207+
}
208+
}
209+
// Merge: start with current env, then override with forwarded vars
210+
$env = array_merge($currentEnv, $forwardedVars);
211+
$process->setEnv($env);
212+
}
213+
183214
return $process;
184215
}
185216

217+
/**
218+
* Gets the environment variables that should be forwarded to subprocesses.
219+
*
220+
* This method:
221+
* - Always forwards Composer-related environment variables (e.g., COMPOSER_AUDIT_BLOCK_INSECURE)
222+
* to allow users to control Composer's security audit behavior during plugin installation.
223+
* - Forwards any variables specified in TERMINUS_FORWARD_ENV (comma-separated list).
224+
*
225+
* Note: We do not globally disable Composer audits. We only respect explicit user
226+
* instructions via environment variables.
227+
*
228+
* @return array Environment variables to forward (key => value pairs), or empty array if none to forward
229+
*/
230+
protected function getForwardedEnvironment(): array
231+
{
232+
$forwardedVars = [];
233+
234+
// Always forward Composer-related environment variables if they are set.
235+
// This allows users to override Composer's security audit behavior.
236+
$composerEnvVars = [
237+
'COMPOSER_AUDIT_BLOCK_INSECURE',
238+
'COMPOSER_ALLOW_SUPERUSER',
239+
'COMPOSER_DISABLE_XDEBUG_WARN',
240+
'COMPOSER_MEMORY_LIMIT',
241+
'COMPOSER_MIRROR_PATH_REPOS',
242+
'COMPOSER_NO_INTERACTION',
243+
'COMPOSER_PROCESS_TIMEOUT',
244+
];
245+
246+
foreach ($composerEnvVars as $var) {
247+
$value = getenv($var);
248+
if ($value !== false) {
249+
$forwardedVars[$var] = $value;
250+
}
251+
}
252+
253+
// Check for TERMINUS_FORWARD_ENV (comma-separated list of env var names to forward).
254+
$terminusForwardEnv = getenv('TERMINUS_FORWARD_ENV');
255+
if ($terminusForwardEnv !== false && !empty($terminusForwardEnv)) {
256+
$varsToForward = array_map('trim', explode(',', $terminusForwardEnv));
257+
foreach ($varsToForward as $varName) {
258+
if (!empty($varName)) {
259+
$value = getenv($varName);
260+
if ($value !== false) {
261+
$forwardedVars[$varName] = $value;
262+
}
263+
}
264+
}
265+
}
266+
267+
return $forwardedVars;
268+
}
269+
186270
/**
187271
* Clones the Git repository.
188272
*

tests/Functional/PluginManagerCommandsTest.php

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,85 @@ public function testPluginsCommands()
155155
$filesystem->remove($tempPluginFile);
156156
}
157157

158+
/**
159+
* @test
160+
* @covers \Pantheon\Terminus\Commands\Self\Plugin\InstallCommand
161+
*
162+
* Regression test to verify that COMPOSER_AUDIT_BLOCK_INSECURE environment variable
163+
* is properly forwarded to Composer subprocess during plugin installation.
164+
*
165+
* This test verifies the fix for the bug where COMPOSER_AUDIT_BLOCK_INSECURE=0
166+
* was not being forwarded to Composer, causing plugin installations to fail
167+
* with security advisory errors even when the user explicitly opted out.
168+
*
169+
* Uses terminus-build-tools-plugin which has known security advisories in its
170+
* dependencies (e.g., symfony/process v5.4.40, twig/twig v3.11.1) to verify
171+
* that the env var forwarding actually works end-to-end.
172+
*
173+
* @group plugins
174+
* @group long
175+
*/
176+
public function testPluginInstallRespectsComposerAuditBlockInsecureEnv()
177+
{
178+
$filesystem = new Filesystem();
179+
// Use a plugin with known security advisories to test the env forwarding
180+
$testPluginPackage = 'pantheon-systems/terminus-build-tools-plugin';
181+
182+
// Clean up.
183+
$filesystem->remove([
184+
$this->getPluginsDir(),
185+
$this->getPlugins2Dir(),
186+
$this->getDependenciesBaseDir(),
187+
$this->getBaseDir(),
188+
]);
189+
190+
// Uninstall plugin if it exists
191+
$this->terminus('self:plugin:uninstall ' . $testPluginPackage, [], false);
192+
193+
// Test that plugin installation works with COMPOSER_AUDIT_BLOCK_INSECURE=0 set.
194+
// This verifies that the environment variable is properly forwarded to Composer.
195+
// Without this env var, Composer 2.9.1+ would block the installation due to
196+
// security advisories in the plugin's dependencies.
197+
$env = array_merge($this->env, ['COMPOSER_AUDIT_BLOCK_INSECURE' => '0']);
198+
[$output, $exitCode, $error] = static::callTerminus(
199+
sprintf('self:plugin:install %s', $testPluginPackage),
200+
null,
201+
$env
202+
);
203+
204+
// The installation should succeed (exit code 0) when COMPOSER_AUDIT_BLOCK_INSECURE=0 is set.
205+
// If the env var is not forwarded, Composer would fail with security advisory errors.
206+
$this->assertEquals(
207+
0,
208+
$exitCode,
209+
sprintf(
210+
'Plugin installation should succeed with COMPOSER_AUDIT_BLOCK_INSECURE=0. ' .
211+
'If this fails, the env var may not be forwarded to Composer. ' .
212+
'Output: %s, Error: %s',
213+
$output,
214+
$error
215+
)
216+
);
217+
218+
// Verify the plugin was actually installed
219+
$this->assertStringContainsString(
220+
sprintf('Installed %s', $testPluginPackage),
221+
$output . $error,
222+
'Plugin installation output should indicate success.'
223+
);
224+
225+
// Verify no security advisory blocking errors in the output
226+
$combinedOutput = $output . $error;
227+
$this->assertStringNotContainsString(
228+
'are affected by security advisories',
229+
$combinedOutput,
230+
'Plugin installation should not show security advisory blocking errors when COMPOSER_AUDIT_BLOCK_INSECURE=0 is set.'
231+
);
232+
233+
// Cleanup
234+
$this->terminus('self:plugin:uninstall ' . $testPluginPackage, [], false);
235+
}
236+
158237
/**
159238
* Install Terminus 2 plugins.
160239
*

0 commit comments

Comments
 (0)