Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

[Dotenv] Don't truncate external env vars containing $ when referenced via ${...} indirection#64386

Merged
nicolas-grekas merged 1 commit into
symfony:6.4symfony/symfony:6.4from
nicolas-grekas:dotenv-fixnicolas-grekas/symfony:dotenv-fixCopy head branch name to clipboard
May 28, 2026
Merged

[Dotenv] Don't truncate external env vars containing $ when referenced via ${...} indirection#64386
nicolas-grekas merged 1 commit into
symfony:6.4symfony/symfony:6.4from
nicolas-grekas:dotenv-fixnicolas-grekas/symfony:dotenv-fixCopy head branch name to clipboard

Conversation

@nicolas-grekas
Copy link
Copy Markdown
Member

@nicolas-grekas nicolas-grekas commented May 28, 2026

Q A
Branch? 6.4
Bug fix? yes
New feature? no
Deprecations? no
Issues Fix #64385
License MIT

Follow-up to #64148, covering the indirect-propagation variant of the same regression family.

When a Dotenv-loaded var references an OS-provided var via ${VAR} indirection (e.g. LOCK_DSN=${REDIS_DSN} with REDIS_DSN=secret$word in the OS environment), the substituted value is silently truncated at the first literal $. resolveLoadedVars() is a multi-pass loop: pass 1 substitutes ${REDIS_DSN} and writes secret$word back to $_ENV[LOCK_DSN]; pass 2 re-scans the value, treats $word as an undefined reference, substitutes the empty string, and the final value becomes secret.

#64148 prevented externally-present host vars from being added to loadedRawVars (so they are not themselves re-scanned). It did not protect external values pulled in via substitution. This patch closes that gap in resolveVariables(): when the looked-up name is not in $loadedVars, the substituted value's $ chars are converted to the existing \x00 literal marker before being spliced in. The terminal str_replace at the end of resolveLoadedVars() (and the equivalents in resolveValue() and resolveEnvKey()) turns them back into $. The gate on !isset($loadedVars[$name]) preserves the multi-pass design for chained Dotenv refs like A=${B}, B=${C}, C=literal.

Two adjacent issues on the same code path are fixed in the same patch rather than as follow-ups.

The first: with .env containing FOO=${FOO:-default}, OS-provided FOO='value$(id)', and the user calling Dotenv::overload() (or loadEnv(..., overrideExistingVars: true)), the self-referencing branch of resolveLoadedVars() temporarily restores the OS value back into $_ENV[FOO] so the :-default lookup can see it. Pass 1 substitutes it into FOO; pass 2 reads $_ENV[FOO]='value$(id)', resolveCommands() matches $(id) and runs it through Process::fromShellCommandline(). The regression test below demonstrates this concretely: pre-fix, id is actually invoked and its real output ends up in $_ENV[FOO]. The fix applies the same \x00 protection to the OS value when exposing it back to $_ENV / $_SERVER.

The second: $this->overriddenValues = [] (and the parser cursor properties) is reset only in the success path of resolveLoadedVars(). On a VariableCircularReferenceException, the stale overriddenValues carries into the next load() call on the same Dotenv instance and can resurrect a value the OS no longer provides (concretely, when the next .env uses a self-referencing default for the same name). The fix moves the cleanup into a finally, and also resets $this->pendingPutenv for symmetry.

Best reviewed ignoring whitespaces.

@carsonbot carsonbot added this to the 6.4 milestone May 28, 2026
@nicolas-grekas nicolas-grekas merged commit 3370a63 into symfony:6.4 May 28, 2026
12 of 13 checks passed
@fabpot fabpot mentioned this pull request May 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

Morty Proxy This is a proxified and sanitized view of the page, visit original site.