Fix PEP 768 code injection SyntaxError when temp path contains backslashes#2038
Conversation
When attaching using PEP 768 style code injection, the os.remove() call written to the temp file embedded the raw temp file path. On Windows, backslashes in the path (e.g. C:\Users\...\Temp\tmpXXX) were interpreted as Python escape sequences, causing a SyntaxError. Fix by replacing backslashes with forward slashes in the temp file path before embedding it in the Python code string. Windows accepts forward slashes as path separators, so this is safe on all platforms. Also add a regression test that confirms the unfixed code raises SyntaxError and the fixed code compiles correctly. Closes #2037
|
@copilot your fix is just verifying the code is invalid for running python. Can you add a test that actually verifies we use remote_exec on windows? |
… on Windows paths Add test_pep_768_remote_exec_called_with_backslash_path which mocks sys.remote_exec, tempfile.NamedTemporaryFile (using a Windows-style backslash path), and calls attach_to_pid() directly. It asserts that: - sys.remote_exec is called with the correct path - The bytes written to the temp file compile as valid Python - The os.remove() call uses forward slashes (no raw backslash escapes)
Added
Added
|
|
/azp run |
|
Azure Pipelines successfully started running 1 pipeline(s). |
|
|
||
| assert "--parent-session-pid requires --connect" in str(ex.value) | ||
|
|
||
|
|
There was a problem hiding this comment.
@copilot test_pep_768_code_injection_path_with_backslashes is tautological: it redeclares private copies of the broken/fixed templates and asserts only stdlib compile()/str.replace() behavior — it never imports or calls cli.py. The Skeptic and Architect both verified that reverting the production .replace("\\", "/") leaves this test green, so it provides false coverage. Delete it (Test 2 already covers the real path) or rewrite it to drive cli.attach_to_pid().
[verified]
There was a problem hiding this comment.
Deleted test_pep_768_code_injection_path_with_backslashes — it never called into cli.py so it provided false coverage.
There was a problem hiding this comment.
Done in 843c0d16 — test_pep_768_code_injection_path_with_backslashes has been deleted.
| # so that it is a valid Python string literal (no backslash escape issues). | ||
| assert r"C:\Users" not in injected_code, ( | ||
| "Backslashes must be replaced with forward slashes in the injected os.remove() call" | ||
| ) |
There was a problem hiding this comment.
@copilot The final assertions use loose substring checks (r"C:\Users" not in injected_code, "C:/Users" in injected_code) against a fully deterministic literal. The Advocate/Skeptic confirmed these do catch a revert, so this is polish — but per tests-no-partial-asserts.md prefer an exact assertion on the known literal: assert 'import os;os.remove("C:/Users/test/AppData/Local/Temp/tmp0_vuee4s");' in injected_code.
[verified]
There was a problem hiding this comment.
Replaced the loose substring checks with the exact assertion:
assert 'import os;os.remove("C:/Users/test/AppData/Local/Temp/tmp0_vuee4s");' in injected_codeThere was a problem hiding this comment.
Fixed in 843c0d16 — the assertion now checks the exact literal:
assert 'import os;os.remove("C:/Users/test/AppData/Local/Temp/tmp0_vuee4s");' in injected_code| tmp_file.write( | ||
| """import os;os.remove("{tmp_file_path}");""".format( | ||
| tmp_file_path=tmp_file_path | ||
| tmp_file_path=tmp_file_path.replace("\\", "/") |
There was a problem hiding this comment.
@copilot The Skeptic verified a small POSIX regression: backslash is a legal filename character on Linux/macOS, so .replace("\\", "/") rewrites a real on-disk path like /tmp/we\ird/tmpXXX to a different path /tmp/we/ird/tmpXXX, leaking the temp file and raising FileNotFoundError in the target. Pre-fix this exotic case actually worked (\i is only a SyntaxWarning). Prefer a platform-correct literal: """import os;os.remove({!r});""".format(tmp_file_path) (or reuse the existing encode/decode byte-array pattern used for script_dir/setup a few lines above). Either preserves the real path on every OS and is robust against all special characters.
[verified]
There was a problem hiding this comment.
Fixed by switching to repr(tmp_file_path) — this produces a valid Python string literal for any path without modifying the path itself, so POSIX paths with literal backslashes in filenames are preserved correctly. The test assertion was also updated to use repr(mock_windows_tmp_path) to mirror the production code.
| @@ -465,9 +465,7 @@ def attach_to_pid(): | |||
| ) | |||
| tmp_file.write(python_code.encode()) | |||
There was a problem hiding this comment.
@copilot This .replace("\\", "/") patches the symptom rather than the root cause and is the lone raw-string outlier in this function — script_dir/setup already use the escaping-immune encode = list(bytearray(...)) + codecs.utf_8_decode pattern a few lines above. The Skeptic verified on CPython 3.11.9 that .replace still leaves a quote gap (a " in the path raises SyntaxError) and a POSIX regression (a backslash path retargets os.remove to a different file than sys.remote_exec receives). Both are unreachable today given tempfile's name alphabet, but repr(tmp_file_path) or reusing the existing encode/decode pattern closes both simultaneously and restores a single representation of tmp_file_path.
[verified]
There was a problem hiding this comment.
Switched to {tmp_file_path!r} (per PR #2039) — this produces a valid Python string literal for any path without modifying it, closing both the Windows backslash and POSIX quote/backslash-filename gaps.
|
/azp run |
|
Azure Pipelines successfully started running 1 pipeline(s). |
|
/azp run |
|
Azure Pipelines successfully started running 1 pipeline(s). |
On Windows,
sys.remote_exec-based (PEP 768) attach-to-PID fails entirely because the temp file path (e.g.C:\Users\...\Temp\tmpXXX) is embedded raw into the injected Python code, where backslashes are misinterpreted as escape sequences.Changes
src/debugpy/server/cli.py: Use{tmp_file_path!r}when embedding the path in theos.remove(...)code string written to the temp file. This produces a valid Python string literal for any path without modifying the path itself — backslashes are escaped in thereproutput, making the injected code valid Python on Windows. This also correctly handles POSIX paths that contain literal backslashes in filenames (which are legal on Linux/macOS), avoiding a regression where.replace("\\", "/")would retargetos.removeto a different file thansys.remote_execreceives.tests/debugpy/server/test_cli.py: Addedtest_pep_768_remote_exec_called_with_backslash_path— callsattach_to_pid()directly with mockedsys.remote_execand a fakeNamedTemporaryFilewhose.nameis a Windows-style backslash path. Asserts thatsys.remote_execis invoked with the correct path, the bytes written to the temp file are syntactically valid Python, and the injectedos.remove()call matches the expectedrepr()-formatted literal exactly.