diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index ec4cf3e94..cf11253e2 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -676,6 +676,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 90380629f..d10ed132e 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.