https://github.com/python/cpython/commit/78bdd3ddaa9339c5a2c780c300f571ed86ceb9ab
commit: 78bdd3ddaa9339c5a2c780c300f571ed86ceb9ab
branch: 3.14
author: Miss Islington (bot) <[email protected]>
committer: terryjreedy <[email protected]>
date: 2026-09-23T19:22:40-04:00
summary:

[3.14] gh-70331: Protect IDLE's imports from user files in the current 
directory (GH-157643) (#158035)

gh-70331: Protect IDLE's imports from user files in the current directory 
(GH-157643)

Start the user process with -P, so that the current directory is not on
sys.path while idlelib.run and its dependencies are imported.  sys.path
is set later by transfer_path().  Protect __main__, idle, and pyshell entry 
points.
(cherry picked from commit 6893326350024d0ed3a6fa4ff59e4139b2647411)

Co-authored-by: Serhiy Storchaka <[email protected]>

files:
A Misc/NEWS.d/next/IDLE/2026-09-16-22-00-00.gh-issue-70331.shadow.rst
M Lib/idlelib/__main__.py
M Lib/idlelib/idle.py
M Lib/idlelib/idle_test/test_pyshell.py
M Lib/idlelib/pyshell.py

diff --git a/Lib/idlelib/__main__.py b/Lib/idlelib/__main__.py
index ec3915b265f665..4dde0d6a5acbb5 100644
--- a/Lib/idlelib/__main__.py
+++ b/Lib/idlelib/__main__.py
@@ -3,5 +3,12 @@
 
 Run IDLE as python -m idlelib
 """
+import sys
+
+if not sys.flags.safe_path:
+    # Remove the current directory, prepended by "python -m", so that
+    # user files do not shadow IDLE's imports (gh-70331).
+    del sys.path[0]
+
 import idlelib.pyshell
 idlelib.pyshell.main()
diff --git a/Lib/idlelib/idle.py b/Lib/idlelib/idle.py
index 485d5a75a29c1a..d9faa12cc2ba45 100644
--- a/Lib/idlelib/idle.py
+++ b/Lib/idlelib/idle.py
@@ -1,6 +1,12 @@
-import os.path
 import sys
 
+if __spec__ is not None and not sys.flags.safe_path:
+    # Remove the current directory, prepended by "python -m", so that
+    # user files do not shadow IDLE's imports (gh-70331).
+    del sys.path[0]
+
+import os.path
+
 
 # Enable running IDLE with idlelib in a non-standard location.
 # This was once used to run development versions of IDLE.
diff --git a/Lib/idlelib/idle_test/test_pyshell.py 
b/Lib/idlelib/idle_test/test_pyshell.py
index 4aa0ba3a90158a..907606a4b1ee49 100644
--- a/Lib/idlelib/idle_test/test_pyshell.py
+++ b/Lib/idlelib/idle_test/test_pyshell.py
@@ -3,8 +3,11 @@
 
 from idlelib import pyshell
 import os
+import sys
 import unittest
-from test.support import requires
+from unittest import mock
+from test.support import os_helper, requires
+from test.support.script_helper import assert_python_ok
 from tkinter import Tk
 
 
@@ -37,6 +40,24 @@ def test_fix_user_path(self):
         eq(pyshell.fix_user_path(['/a', '/b']), ['/a', '/b'])
         eq(pyshell.fix_user_path([idlelib_dir]), [])
 
+    def test_shadowed_stdlib(self):
+        # gh-70331: user files in the current directory must not shadow
+        # the stdlib modules imported by IDLE.
+        with os_helper.temp_dir() as cwd:
+            for name in ('os', 'random', 'tkinter'):
+                os_helper.create_empty_file(os.path.join(cwd, f'{name}.py'))
+            for module in 'idlelib', 'idlelib.idle', 'idlelib.pyshell':
+                with self.subTest(module=module):
+                    assert_python_ok('-m', module, '-h',
+                                     __isolated=False, __cwd=cwd)
+
+    def test_build_subprocess_arglist(self):
+        interp = mock.Mock(port=1234)
+        args = pyshell.ModifiedInterpreter.build_subprocess_arglist(interp)
+        # gh-70331: -P keeps the current directory out of sys.path.
+        self.assertEqual(args[:2], [sys.executable, '-P'])
+        self.assertEqual(args[-1], '1234')
+
 
 class PyShellFileListTest(unittest.TestCase):
 
diff --git a/Lib/idlelib/pyshell.py b/Lib/idlelib/pyshell.py
index 953394a870c8d3..7527163d8b60eb 100755
--- a/Lib/idlelib/pyshell.py
+++ b/Lib/idlelib/pyshell.py
@@ -3,6 +3,10 @@
 import sys
 if __name__ == "__main__":
     sys.modules['idlelib.pyshell'] = sys.modules['__main__']
+    if __spec__ is not None and not sys.flags.safe_path:
+        # Remove the current directory, prepended by "python -m", so that
+        # user files do not shadow IDLE's imports (gh-70331).
+        del sys.path[0]
 
 try:
     from tkinter import *
@@ -455,7 +459,10 @@ def build_subprocess_arglist(self):
         del_exitf = idleConf.GetOption('main', 'General', 'delete-exitfunc',
                                        default=False, type='bool')
         command = f"__import__('idlelib.run').run.main({del_exitf!r})"
-        return [sys.executable] + w + ["-c", command, str(self.port)]
+        # -P keeps the current directory off sys.path, so that user files
+        # do not shadow run's imports (gh-70331).  transfer_path() sets
+        # sys.path later.
+        return [sys.executable, '-P'] + w + ["-c", command, str(self.port)]
 
     def start_subprocess(self):
         addr = (HOST, self.port)
diff --git 
a/Misc/NEWS.d/next/IDLE/2026-09-16-22-00-00.gh-issue-70331.shadow.rst 
b/Misc/NEWS.d/next/IDLE/2026-09-16-22-00-00.gh-issue-70331.shadow.rst
new file mode 100644
index 00000000000000..1a7124e76cd49f
--- /dev/null
+++ b/Misc/NEWS.d/next/IDLE/2026-09-16-22-00-00.gh-issue-70331.shadow.rst
@@ -0,0 +1,4 @@
+IDLE no longer fails to start with ``python -m idlelib``, and its user process
+no longer fails to start, when the current directory contains user files with
+the same names as standard library modules that IDLE imports, such as
+``random.py`` or ``tkinter.py``.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to