From 801deddcd54aab75007c9d2baa91b4d5e1020c33 Mon Sep 17 00:00:00 2001 From: Sahana Bogar Date: Thu, 2 Jul 2026 15:52:08 +0530 Subject: [PATCH 1/2] validate variable names in copyToGlobals debug handler --- ipykernel/debugger.py | 9 +++++++++ tests/test_debugger.py | 18 ++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index 6bcb59114..ebb9c7777 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -681,6 +681,15 @@ async def copyToGlobals(self, message): src_var_name = message["arguments"]["srcVariableName"] src_frame_id = message["arguments"]["srcFrameId"] + if not str.isidentifier(dst_var_name) or not str.isidentifier(src_var_name): + return { + "type": "response", + "request_seq": message["seq"], + "success": False, + "command": message["command"], + "message": "dstVariableName and srcVariableName must be valid identifiers", + } + expression = f"globals()['{dst_var_name}']" seq = message["seq"] return await self._forward_message( diff --git a/tests/test_debugger.py b/tests/test_debugger.py index b7ec123df..f4ec88569 100644 --- a/tests/test_debugger.py +++ b/tests/test_debugger.py @@ -473,6 +473,24 @@ def my_test(): assert global_var["value"] == local_var["value"] and global_var["type"] == local_var["type"] # noqa: PT018 +def test_copy_to_globals_rejects_non_identifier(kernel_with_debug): + # A dstVariableName that is not a valid identifier would break out of the + # single-quoted globals()['...'] expression forwarded to setExpression. + reply = wait_for_debug_request( + kernel_with_debug, + "copyToGlobals", + { + "srcVariableName": "src", + "dstVariableName": "x'] or __import__('os').system('echo pwned') or globals()['y", + "srcFrameId": 0, + }, + ) + # The request is rejected locally instead of being forwarded to + # setExpression, so the response still carries the copyToGlobals command. + assert reply["success"] is False + assert reply["command"] == "copyToGlobals" + + def test_debug_requests_sequential(kernel_with_debug): # Issue https://github.com/ipython/ipykernel/issues/1412 # Control channel requests should be executed sequentially not concurrently. From 5e2f6db10db7ab2ea9d0615b0b647346b9cba8a4 Mon Sep 17 00:00:00 2001 From: Sahana Bogar Date: Fri, 31 Jul 2026 19:12:43 +0530 Subject: [PATCH 2/2] guard copyToGlobals identifier test when debugpy is absent --- tests/test_debugger.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/test_debugger.py b/tests/test_debugger.py index f4ec88569..5e40d7165 100644 --- a/tests/test_debugger.py +++ b/tests/test_debugger.py @@ -487,8 +487,11 @@ def test_copy_to_globals_rejects_non_identifier(kernel_with_debug): ) # The request is rejected locally instead of being forwarded to # setExpression, so the response still carries the copyToGlobals command. - assert reply["success"] is False - assert reply["command"] == "copyToGlobals" + if debugpy: + assert reply["success"] is False + assert reply["command"] == "copyToGlobals" + else: + assert reply == {} def test_debug_requests_sequential(kernel_with_debug):