Preventing Symfony Process From Passing Laravel Environment Variables

Recently when working on an internal tool using Laravel, we encountered an issue where calling artisan commands in another application was using the wrong environment variables. After debugging, we determined that this was default behavior of the Symfony process class.

The Symfony process class for PHP is able to call system processes from within your PHP application. Symfony process is included as a dependency in Laravel which allows easy access to be able to use it from within your Laravel application.

One feature of the process class is it passes all environment variables from the PHP environment to the child process that you are calling. This is useful in many cases so the child process is able to run in the same environment as the parent. However, one side effect of this is the Laravel environment variables also get passed through to the child process — including secrets that you might not want another application to have access to. For most child processes, this is not an issue at all so it is safe to use as is.

One case that introduces an issue when using a Laravel artisan command is it becomes impossible to run a Laravel artisan command from one application inside of another. You run the process command in Application 1 calling Application 2’s artisan command. This causes the command to run with the code from Application 2 but the environment and database from Application 1. For example, if you were to run migrate:refresh on a second Laravel application from the first application it would cause the first application’s database to be overwritten with the tables from the second application.

Example code to demonstrate that.

$process = new \Symfony\Component\Process\Process(['php', '/path/to/application/two/artisan', 'migrate:refresh']);
$process->run();

If you also run into this problem you may use this sample class that extends the Symfony process class and will automatically exclude all Laravel environment variables that are set in your .env file from being passed to the child process. Using this class is a drop-in replacement which so you won’t need to change your code in any other way.

<?php

namespace App\Support;

use Dotenv\Dotenv;
use Illuminate\Support\Facades\File;
use Symfony\Component\Process\Process as SymfonyProcess;

class Process extends SymfonyProcess
{
    private static $laravel_env = null;

    public function __construct(array $command, string $cwd = null, array $env = null, mixed $input = null, ?float $timeout = 60)
    {
        if (is_null(self::$laravel_env)) {
            self::$laravel_env = array_map(function () {
                return false;
            }, Dotenv::parse(File::get(base_path('.env'))));
        }
        if (is_null($env)) {
            $env = self::$laravel_env;
        } else {
            $env = array_merge(self::$laravel_env, $env);
        }
        parent::__construct($command, $cwd, $env, $input, $timeout);
    }
}

While this issue does leak data to other applications, we brought this up with the Laravel team in issue #44278. It was determined that it does not cause a significant enough threat that it should be addressed and if it does affect you there are multiple workarounds to the problem including our workaround above.

For more Laravel and web development assistance, contact the experts at Hall.

See how Hall can help increase your demand.