diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py index 2f30d3108021dc6..ad11b7c5f854cc5 100644 --- a/Lib/test/test_venv.py +++ b/Lib/test/test_venv.py @@ -663,6 +663,34 @@ 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_clear_symlink_not_followed(self): + # gh-158068: venv --clear must not wipe the contents of a directory + # that env_dir merely points to via a symlink. + target = os.path.join(self.env_dir, 'target') + os.mkdir(target) + keep = os.path.join(target, 'keep') + with open(keep, 'wb') as f: + f.write(b'Still here?') + link = os.path.join(self.env_dir, 'link') + os.symlink(target, link) + + with self.assertRaises(ValueError): + venv.create(link, clear=True) + # The symlink target and its content are left untouched. + self.assertTrue(os.path.islink(link)) + self.assertTrue(os.path.exists(keep)) + + def test_clear_file_not_removed(self): + # gh-158068: venv --clear must not treat a regular file as env_dir. + target = os.path.join(self.env_dir, 'afile') + with open(target, 'wb') as f: + f.write(b'Still here?') + + with self.assertRaises(ValueError): + venv.create(target, clear=True) + self.assertTrue(os.path.isfile(target)) + def test_upgrade(self): """ Test upgrading an existing environment directory. diff --git a/Lib/venv/__init__.py b/Lib/venv/__init__.py index 38e1bfe0c5fdb9d..0759d572f995ed3 100644 --- a/Lib/venv/__init__.py +++ b/Lib/venv/__init__.py @@ -149,8 +149,11 @@ def create_if_needed(d): if os.pathsep in os.fspath(env_dir): raise ValueError(f'Refusing to create a venv in {env_dir} because ' f'it contains the PATH separator {os.pathsep}.') - if os.path.exists(env_dir) and self.clear: - self.clear_directory(env_dir) + if self.clear: + if os.path.islink(env_dir) or os.path.isfile(env_dir): + raise ValueError('Unable to create directory %r' % env_dir) + if os.path.exists(env_dir): + self.clear_directory(env_dir) context = types.SimpleNamespace() context.env_dir = env_dir context.env_name = os.path.split(env_dir)[1] diff --git a/Misc/NEWS.d/next/Library/2026-09-24-14-00-00.gh-issue-158068.Xf2ogr.rst b/Misc/NEWS.d/next/Library/2026-09-24-14-00-00.gh-issue-158068.Xf2ogr.rst new file mode 100644 index 000000000000000..29b2f4ef11ec933 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-09-24-14-00-00.gh-issue-158068.Xf2ogr.rst @@ -0,0 +1,4 @@ +:mod:`venv` no longer clears the target directory when it is a symlink or a +regular file. Previously ``venv --clear`` followed a symlink and could delete +the contents of the linked directory. It now raises :exc:`ValueError` before +removing anything.