From 12ffb906dc529ffcf2bdb6edaebb5aef9ad31da6 Mon Sep 17 00:00:00 2001 From: Vincent Gao Date: Wed, 24 Jun 2026 11:42:58 +0200 Subject: [PATCH] fix: escape backslashes in set_key() single-quoted values set_key() writes values wrapped in single quotes, and the parser decodes \\ as a literal backslash via decode_escapes(). But set_key() only escaped single-quote characters (\'), not backslashes themselves. This meant any value containing two or more consecutive backslashes lost one during round-trip: set_key(path, 'K', 'a\\\\b') dotenv_values(path)['K'] # -> 'a\\b' (lost one backslash) The parser's _single_quote_escapes regex matches both \\' and \\\\, so backslashes MUST be doubled when writing to preserve them. Fix by escaping backslashes before single-quotes in the value quoting step. - src/dotenv/main.py: +2/-0 - tests/test_main.py: +23 (parametrized round-trip regression test) --- src/dotenv/main.py | 4 +++- tests/test_main.py | 23 +++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/dotenv/main.py b/src/dotenv/main.py index 3c4608d5..5711543a 100644 --- a/src/dotenv/main.py +++ b/src/dotenv/main.py @@ -216,7 +216,9 @@ def set_key( ) if quote: - value_out = "'{}'".format(value_to_set.replace("'", "\\'")) + value_out = "'{}'".format( + value_to_set.replace("\\", "\\\\").replace("'", "\\'") + ) else: value_out = value_to_set if export: diff --git a/tests/test_main.py b/tests/test_main.py index 1c33c808..ea2f9739 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -54,6 +54,29 @@ def test_set_key(dotenv_path, before, key, value, expected, after): mock_warning.assert_not_called() +@pytest.mark.parametrize( + "key,value", + [ + ("a", "no backslash"), + ("b", r"one \ backslash"), + ("c", r"two \\ backslashes"), + ("d", r"three \\\ backslashes"), + ("e", r"path C:\Users\test"), + ("f", r"mix ' quote and \\ backslashes"), + ], +) +def test_set_key_backslash_roundtrip(tmp_path, key, value): + """set_key must round-trip values containing backslashes (gh-issue).""" + dotenv_path = tmp_path / ".env" + dotenv_path.write_text("") + + dotenv.set_key(dotenv_path, key, value) + result = dotenv.dotenv_values(dotenv_path) + assert result.get(key) == value, ( + f"Round-trip failed: {value!r} -> {result.get(key)!r}" + ) + + def test_set_key_encoding(dotenv_path): encoding = "latin-1"