fix(deployments): allow legacy runtime-only env var names to deploy

Runtime-only environment variables whose names new variables can no longer
use (e.g. my-var) no longer fail the deployment. They are still passed to
the container through the .env file, and the deployment log now shows a
warning with a suggested valid name.

Build-time variables, and names that would break a .env line (empty or
containing =, newline, carriage return or NUL), are still rejected.
This commit is contained in:
Andras Bacsai
2026-09-25 21:09:35 +02:00
parent 064682c210
commit a39f3f29b6
2 changed files with 66 additions and 3 deletions
+28 -3
View File
@@ -2066,17 +2066,42 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
}
}
/**
* Build-time names go into shell and Docker build commands, so they must be valid. Runtime-only
* variables only go into the .env file: existing ones with names that new variables can no longer
* use (such as my-var) keep working, unless the name would break a .env line.
*/
private function validateDeploymentEnvironmentVariableKeys(): void
{
$environmentVariables = $this->pull_request_id === 0
? $this->application->environment_variables()->get(['key'])
: $this->application->environment_variables_preview()->get(['key']);
? $this->application->environment_variables()->get(['key', 'is_buildtime'])
: $this->application->environment_variables_preview()->get(['key', 'is_buildtime']);
foreach ($environmentVariables as $environmentVariable) {
$this->validatedBuildtimeEnvironmentVariableKey((string) $environmentVariable->key, 'the deployment environment');
$key = (string) $environmentVariable->key;
$isEnvFileSafe = $key !== '' && strpbrk($key, "=\n\r\0") === false;
if ($environmentVariable->is_buildtime || ! $isEnvFileSafe) {
$this->validatedBuildtimeEnvironmentVariableKey($key, 'the deployment environment');
continue;
}
if (! ValidationPatterns::isValidEnvironmentVariableKey($key)) {
$this->logLegacyRuntimeEnvironmentVariableKey($key);
}
}
}
private function logLegacyRuntimeEnvironmentVariableKey(string $key): void
{
$suggestedKey = (string) preg_replace('/[^A-Za-z0-9_]/', '_', $key);
if (preg_match('/\A[0-9]/', $suggestedKey) === 1) {
$suggestedKey = '_'.$suggestedKey;
}
$this->application_deployment_queue->addLogEntry('⚠️ Runtime variable '.ValidationPatterns::displayShellEnvironmentVariableKey($key).' uses a name that new variables cannot use. It is still passed to the container, but shell scripts cannot read it.', 'stderr');
$this->application_deployment_queue->addLogEntry(' Suggested name: '.ValidationPatterns::displayShellEnvironmentVariableKey($suggestedKey), type: 'info');
}
private function logInvalidBuildtimeEnvironmentVariableKey(string $key, string $origin): void
{
$displayKey = ValidationPatterns::displayShellEnvironmentVariableKey($key);
@@ -992,6 +992,44 @@ it('rejects an unsafe stored key before running a deployment command', function
expect($job->recordedCommands)->toBeEmpty();
});
it('keeps deploying existing runtime-only variables whose names new variables cannot use', function () {
[$application, $server] = makeDeploymentControlVarFixture();
$environmentVariable = createApplicationEnvironmentVariable($application, [
'key' => 'SAFE_KEY',
'value' => 'secret',
'is_buildtime' => false,
]);
// Names like my-var were accepted before the current rules; the model no longer allows them.
DB::table('environment_variables')->where('id', $environmentVariable->id)->update(['key' => 'my-var']);
[$job, $reflection] = makeControlVarFilteringJob($application->fresh(), $server);
invokeDeploymentJobMethod($job, $reflection, 'validateDeploymentEnvironmentVariableKeys');
expect(collect($job->recordedLogEntries)->implode("\n"))
->toContain('my-var')
->toContain('Suggested name: my_var');
expect($job->recordedCommands)->toBeEmpty();
});
it('rejects existing variable names that would break the .env file or build commands', function (string $key, bool $isBuildtime) {
[$application, $server] = makeDeploymentControlVarFixture();
$environmentVariable = createApplicationEnvironmentVariable($application, [
'key' => 'SAFE_KEY',
'value' => 'secret',
'is_buildtime' => $isBuildtime,
]);
DB::table('environment_variables')->where('id', $environmentVariable->id)->update(['key' => $key]);
[$job, $reflection] = makeControlVarFilteringJob($application->fresh(), $server);
expect(fn () => invokeDeploymentJobMethod($job, $reflection, 'validateDeploymentEnvironmentVariableKeys'))
->toThrow(DeploymentException::class, 'Invalid environment variable name from the deployment environment');
})->with([
'runtime-only name with =' => ['A=B', false],
'runtime-only name with a newline' => ["A\nB", false],
'build-time name with a hyphen' => ['my-var', true],
]);
it('injects raw escaped remote secrets into Dockerfile args and hashes the same values', function (int $pullRequestId, bool $isPreview) {
[$application, $server] = makeDeploymentControlVarFixture();