Skip to content

Fix PEP 768 code injection SyntaxError when temp path contains backslashes#2038

Merged
rchiodo merged 7 commits into
mainfrom
copilot/fix-pep-768-code-injection
Jun 15, 2026
Merged

Fix PEP 768 code injection SyntaxError when temp path contains backslashes#2038
rchiodo merged 7 commits into
mainfrom
copilot/fix-pep-768-code-injection

Conversation

Copilot AI commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

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 the os.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 the repr output, 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 retarget os.remove to a different file than sys.remote_exec receives.
# Before — SyntaxError on Windows due to \U, \A, \T etc. being invalid escapes
"""import os;os.remove("{tmp_file_path}");""".format(tmp_file_path=tmp_file_path)

# After — !r produces a valid Python string literal on all platforms
"""import os;os.remove({tmp_file_path!r});""".format(tmp_file_path=tmp_file_path)
  • tests/debugpy/server/test_cli.py: Added test_pep_768_remote_exec_called_with_backslash_path — calls attach_to_pid() directly with mocked sys.remote_exec and a fake NamedTemporaryFile whose .name is a Windows-style backslash path. Asserts that sys.remote_exec is invoked with the correct path, the bytes written to the temp file are syntactically valid Python, and the injected os.remove() call matches the expected repr()-formatted literal exactly.

Copilot AI added 2 commits June 15, 2026 16:55
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 AI changed the title [WIP] Fix code injection failure with backslashes in temp path Fix PEP 768 code injection SyntaxError when temp path contains backslashes Jun 15, 2026
Copilot AI requested a review from rchiodo June 15, 2026 16:56
@rchiodo

rchiodo commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

@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)

Copilot AI commented Jun 15, 2026

Copy link
Copy Markdown
Contributor Author

@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?

Added test_pep_768_remote_exec_called_with_backslash_path (commit $(git -C /home/runner/work/debugpy/debugpy/microsoft/debugpy rev-parse --short HEAD)). It:

  • Configures cli.options with a valid connect setup
  • Mocks tempfile.NamedTemporaryFile to return a fake file whose .name is a Windows-style backslash path (C:\Users\test\AppData\Local\Temp\tmp0_vuee4s)
  • Mocks sys.remote_exec and calls attach_to_pid() directly
  • Asserts sys.remote_exec was called with that path
  • Asserts the bytes written to the file compile as valid Python and contain C:/Users (forward slashes) rather than raw backslashes in the os.remove() call

@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?

Added test_pep_768_remote_exec_called_with_backslash_path in commit a3b90e5. It:

  • Configures cli.options with a valid connect setup
  • Mocks tempfile.NamedTemporaryFile to return a fake file whose .name is a Windows-style backslash path (C:\Users\test\AppData\Local\Temp\tmp0_vuee4s)
  • Mocks sys.remote_exec and calls attach_to_pid() directly
  • Asserts sys.remote_exec was called with the correct path
  • Asserts the bytes written to the file compile as valid Python and contain C:/Users (forward slashes) rather than raw backslashes in the os.remove() call

@rchiodo

rchiodo commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@rchiodo rchiodo marked this pull request as ready for review June 15, 2026 17:39
@rchiodo rchiodo requested a review from a team as a code owner June 15, 2026 17:39

assert "--parent-session-pid requires --connect" in str(ex.value)


Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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]

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deleted test_pep_768_code_injection_path_with_backslashes — it never called into cli.py so it provided false coverage.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 843c0d16test_pep_768_code_injection_path_with_backslashes has been deleted.

Comment thread tests/debugpy/server/test_cli.py Outdated
# 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"
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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]

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaced the loose substring checks with the exact assertion:

assert 'import os;os.remove("C:/Users/test/AppData/Local/Temp/tmp0_vuee4s");' in injected_code

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread src/debugpy/server/cli.py Outdated
tmp_file.write(
"""import os;os.remove("{tmp_file_path}");""".format(
tmp_file_path=tmp_file_path
tmp_file_path=tmp_file_path.replace("\\", "/")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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]

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@rchiodo

rchiodo commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

@copilot, I think the code change in this PR is more robust:
#2039

Can you make that change to the server/cli.py instead?

Comment thread src/debugpy/server/cli.py
@@ -465,9 +465,7 @@ def attach_to_pid():
)
tmp_file.write(python_code.encode())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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]

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@rchiodo

rchiodo commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

Copilot AI commented Jun 15, 2026

Copy link
Copy Markdown
Contributor Author

@copilot, I think the code change in this PR is more robust:
#2039

...

Updated cli.py to use {tmp_file_path!r} exactly as in PR #2039.

@rchiodo

rchiodo commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@rchiodo rchiodo merged commit 51f0f80 into main Jun 15, 2026
25 of 28 checks passed
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.

3 participants