{{ message }}
Repository navigation
fix(env_config): refuse entries that would split into extra .env lines - #3344
Merged
zhayujie merged 1 commit intoOct 1, 2026
Merged
Conversation
Owner
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Problem
EnvConfig.execute({"action": "set"})accepts avaluecontaining a line breakand writes it verbatim into
~/.cow/.env, which is a one-entry-per-line file. Theentry that reads back is not the entry that was written:
is no longer the credential (
liststill reports it as configured, and the nextrestart loads the truncated one through
load_dotenv);silently creates additional ones. They show up in
list, reachos.environvia_reload_env, and reach every bash subprocess through thedotenv_valuescallin
agent/tools/bash/bash.py.Reproducer in #3343. Measured on this tree:
The same asymmetry covers a
keycontaining the field separator:set(key="A=INJECTED", value="1")writesA=INJECTED=1, which reads back as{"A": "INJECTED=1"}. On the platform measured that call even reports failure(
ValueError: illegal environment variable namefromos.environ[key] = value),because
_write_env_filehas already rewritten the file by the time it raises.Fix
Guard the two shapes the format cannot hold, at the one place a caller-supplied
key or value enters, and return the reason instead of writing broken state. The
check is on line breaks (and
=in a key) rather than a name whitelist, soexisting single-line keys and values are untouched — including
=inside a value(base64 padding) and
#inside a value.Quoting on write was the alternative, and it is the larger change: it would also
have to replace
_read_env_file's own parser, since that one does not understandquotes, and it would newly depend on exactly how python-dotenv unescapes a value.
Refusing what cannot be represented keeps both readers agreeing.
Tests
tests/test_env_config_single_line_entry.py— 5 cases: the multi-line value isrefused and the file is left as it was; neither reader sees the injected variable;
an unrelated key already stored is not disturbed; a key containing
=is refused;and a control that ordinary values (with
=,#, trailing space) still round-tripbyte-for-byte, so the guard cannot over-refuse.
Whole suite, Python 3.11 on this branch:
08f64f70(base)grep '^FAILED' | sorton both runs diffs empty — no test that passed beforefails now, and the 5 additions are exactly the new file. The 32 leftovers are
pre-existing on this machine (no symlink privilege,
WinError 1314; missingtzdata;rgnot installed) and unrelated to these files.ruff check agent/tools/env_config/env_config.py tests/test_env_config_single_line_entry.py --select F→
All checks passed!closes #3343