From 538f0ebaf64bf0b896ca94c49ed78d3e415f1a52 Mon Sep 17 00:00:00 2001 From: Neko Asakura Date: Fri, 30 Oct 2026 10:42:08 -0400 Subject: [PATCH 1/3] gh-160782: reject symlinked subdirectories in the venv directory --- Lib/test/test_venv.py | 17 +++++++++++++++++ Lib/venv/__init__.py | 6 ++++++ ...26-10-30-10-36-30.gh-issue-160782.n6JCUU.rst | 2 ++ 3 files changed, 25 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2026-10-30-10-36-30.gh-issue-160782.n6JCUU.rst diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py index 678615ea172aef..bc57a9e0361d01 100644 --- a/Lib/test/test_venv.py +++ b/Lib/test/test_venv.py @@ -663,6 +663,23 @@ def test_unoverwritable_fails(self): self.assertRaises((ValueError, OSError), venv.create, self.env_dir) self.clear_directory(self.env_dir) + @unittest.skipUnless(can_symlink(), 'Needs symlinks') + def test_symlinked_directory_error(self): + """ + Test that venv creation fails when a directory inside the target + directory is a symlink. + """ + outside = tempfile.mkdtemp() + self.addCleanup(rmtree, outside) + for subdirs in self.ENV_SUBDIRS: + with self.subTest(subdirs=subdirs): + rmtree(self.env_dir) + os.makedirs(os.path.join(self.env_dir, *subdirs[:-1])) + os.symlink(outside, os.path.join(self.env_dir, *subdirs), + target_is_directory=True) + self.assertRaises(ValueError, venv.create, self.env_dir) + self.assertEqual(os.listdir(outside), []) + def test_upgrade(self): """ Test upgrading an existing environment directory. diff --git a/Lib/venv/__init__.py b/Lib/venv/__init__.py index 620168be16862e..60fe070b1f9f4b 100644 --- a/Lib/venv/__init__.py +++ b/Lib/venv/__init__.py @@ -141,6 +141,12 @@ def ensure_directories(self, env_dir): """ def create_if_needed(d): + path = env_dir + for part in os.path.relpath(d, env_dir).split(os.sep)[:-1]: + path = os.path.join(path, part) + if os.path.islink(path) or os.path.isjunction(path): + raise ValueError(f'Refusing to create a venv in {env_dir} ' + f'because {path} is a link.') if not os.path.exists(d): os.makedirs(d) elif os.path.islink(d) or os.path.isfile(d): diff --git a/Misc/NEWS.d/next/Library/2026-10-30-10-36-30.gh-issue-160782.n6JCUU.rst b/Misc/NEWS.d/next/Library/2026-10-30-10-36-30.gh-issue-160782.n6JCUU.rst new file mode 100644 index 00000000000000..d4da24bdca0b5e --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-10-30-10-36-30.gh-issue-160782.n6JCUU.rst @@ -0,0 +1,2 @@ +:mod:`venv`: a symbolic link or junction in place of a directory inside the +environment now raises :exc:`ValueError` instead of being followed. From cbb31c8825c49fae0cb95067ecbdf1557f103623 Mon Sep 17 00:00:00 2001 From: Neko Asakura Date: Fri, 30 Oct 2026 11:50:02 -0400 Subject: [PATCH 2/3] gh-160782: fix the symlink test on free-threaded builds --- Lib/test/test_venv.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py index bc57a9e0361d01..43c2a552ee90b7 100644 --- a/Lib/test/test_venv.py +++ b/Lib/test/test_venv.py @@ -671,7 +671,8 @@ def test_symlinked_directory_error(self): """ outside = tempfile.mkdtemp() self.addCleanup(rmtree, outside) - for subdirs in self.ENV_SUBDIRS: + for n in range(1, len(self.lib) + 1): + subdirs = self.lib[:n] with self.subTest(subdirs=subdirs): rmtree(self.env_dir) os.makedirs(os.path.join(self.env_dir, *subdirs[:-1])) From 957f3d96ce95b3cef9b81e270d79467937be836d Mon Sep 17 00:00:00 2001 From: Neko Asakura Date: Fri, 30 Oct 2026 13:15:58 -0400 Subject: [PATCH 3/3] gh-160782: retrigger CI for flaky failure