Skip to content

fix(cli/env): preserve values that contain quotes, newlines, or backslashes#208

Open
Sanjays2402 wants to merge 1 commit into
fuzzylabs:mainfrom
Sanjays2402:fix/env-file-roundtrip
Open

fix(cli/env): preserve values that contain quotes, newlines, or backslashes#208
Sanjays2402 wants to merge 1 commit into
fuzzylabs:mainfrom
Sanjays2402:fix/env-file-roundtrip

Conversation

@Sanjays2402

Copy link
Copy Markdown

What

Fixes silent data corruption in the env-file round-trip used by the CLI setup wizard. Values that contain quotes, newlines, or backslashes are no longer mangled when written and read back.

Why

sre_agent.cli.env._escape_env_value wraps values containing whitespace in double quotes, but it never escapes embedded " or \ characters. read_env_file then strips outer quotes unconditionally with .strip("\"'"). This makes several realistic inputs corrupt on the first round-trip, and the corrupted value is persisted the next time the wizard runs:

Input Stored as Read back as
"hello (leading quote) KEY="hello hello
hello" (trailing quote) KEY=hello" hello
"quoted" KEY="quoted" quoted (idempotency broken)
line1\nline2 KEY="line1\nline2" (literal newline) line1 (second line silently dropped)

Repro (on main, before this change):

>>> from sre_agent.cli.env import read_env_file, write_env_file
>>> from pathlib import Path
>>> p = Path("/tmp/t.env")
>>> write_env_file(p, {"API_KEY": '"quoted"'})
>>> read_env_file(p)["API_KEY"]
'quoted'            # data silently mutated

Anthropic / GitHub / Slack tokens, AWS access keys, and proxy URLs can all contain characters that trip this: e.g. a wizard user pasting an accidentally-quoted secret has it silently unquoted on the next read, and the fixed-up value overwrites the original on the next wizard run.

How

  • _escape_env_value now quotes whenever any shell-special char appears (whitespace, ", ', \, leading #) and escapes embedded \, ", \n, \r, and \t.
  • A new _decode_double_quoted helper decodes the body of a double-quoted value in a single pass (unknown escapes are preserved verbatim, matching common .env parsers).
  • read_env_file delegates quote handling to _unescape_env_value, which only unwraps values that are surrounded by matching quotes — unquoted values that happen to contain a leading/trailing quote are preserved.
  • Legacy single-quoted values (KEY='value with space') still read cleanly.

Extra

Adds tests/test_env.py covering:

  • All four corruption cases above
  • write → read → write → read idempotency on "quoted"
  • Round-trip for unmodified unrelated keys
  • Deletion via empty value
  • Legacy single-quoted reads

No new dependencies.

Checklist

  • I have run application tests ensuring nothing has broken. (uv run pytest tests/ → 17 passed)
  • I have updated the documentation if required. (Docstrings updated; user-facing behaviour unchanged for valid values.)
  • I have added tests which cover my changes.

Also verified locally:

  • uv tool run ruff check src/sre_agent/cli/env.py tests/test_env.py → clean
  • uv tool run ruff format --check → clean
  • uv run mypy src/sre_agent/cli/env.pySuccess: no issues found in 1 source file

Type of change

Bug fix.

…lashes

The env file writer wrapped values containing whitespace in double
quotes but never escaped embedded quotes or backslashes, and the
reader stripped outer quote characters even when they were legitimate
parts of the value. This caused silent data corruption on round-trip:

- "value with leading quote  -> read back as value with leading quote
- value with trailing quote"  -> read back as value with trailing quote
- "quoted"                    -> read back as quoted (idempotency broken)
- line1\nline2                 -> read back as line1 (second line dropped)

Any wizard re-run would then persist the corrupted value.

Switches to quote-only-when-needed with proper escaping of quotes,
backslashes, and control characters (\\n, \\r, \\t), and a matching
reader that decodes the escape sequences in a single pass. Values
outside double quotes are returned verbatim so legacy single-quoted
values and unquoted values continue to round-trip unchanged.

Adds tests/test_env.py covering the four corruption cases, idempotency
across repeated writes, legacy single-quoted reads, key preservation,
and empty-value deletion.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant