From 2303b0aeb2a657e002e14d7ae7455c919923471c Mon Sep 17 00:00:00 2001 From: tonghuaroot Date: Wed, 29 Jul 2026 12:22:19 +0800 Subject: [PATCH] gh-127172: Honor umask and default ACL when installing venv activation scripts venv wrote the activation scripts with a fixed mode copied from the packaged templates, so they ignored the process umask and any default ACL, unlike every other file venv creates. Copy only the data and let the new files inherit the umask and ACL. --- Lib/test/test_venv.py | 21 +++++++++++++++++++ Lib/venv/__init__.py | 3 +-- ...-07-29-12-20-39.gh-issue-127172.Kf3Xa9.rst | 4 ++++ 3 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-07-29-12-20-39.gh-issue-127172.Kf3Xa9.rst diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py index e98e52c2ea2045..bcf7f7f3fd1e1f 100644 --- a/Lib/test/test_venv.py +++ b/Lib/test/test_venv.py @@ -603,6 +603,27 @@ def test_install_scripts_selinux(self): venv.create(self.env_dir) listxattr_mock.assert_not_called() + @unittest.skipIf(os.name == 'nt', 'POSIX-specific permission test') + def test_install_scripts_honors_umask(self): + """ + gh-127172: Activation scripts must inherit the process umask (and any + default ACL) like every other created file, rather than a fixed mode + copied from the source template. + """ + old_umask = os.umask(0o002) + try: + venv.create(self.env_dir, with_pip=False) + bindir = os.path.join(self.env_dir, self.bindir) + control = os.path.join(bindir, 'control') + with open(control, 'w'): + pass + expected = os.stat(control).st_mode & 0o777 + for name in ('activate', 'activate.csh', 'activate.fish'): + mode = os.stat(os.path.join(bindir, name)).st_mode & 0o777 + self.assertEqual(mode, expected) + finally: + os.umask(old_umask) + def test_overwrite_existing(self): """ Test creating environment in an existing directory. diff --git a/Lib/venv/__init__.py b/Lib/venv/__init__.py index bd2762d55ef696..86d980dbbcc408 100644 --- a/Lib/venv/__init__.py +++ b/Lib/venv/__init__.py @@ -592,11 +592,10 @@ def skip_file(f): 'may be binary: %s', srcfile, e) continue if new_data == data: - shutil.copy(srcfile, dstfile) + shutil.copyfile(srcfile, dstfile) else: with open(dstfile, 'wb') as f: f.write(new_data) - shutil.copymode(srcfile, dstfile) def upgrade_dependencies(self, context): logger.debug( diff --git a/Misc/NEWS.d/next/Library/2026-07-29-12-20-39.gh-issue-127172.Kf3Xa9.rst b/Misc/NEWS.d/next/Library/2026-07-29-12-20-39.gh-issue-127172.Kf3Xa9.rst new file mode 100644 index 00000000000000..c4bf1f07a2ea57 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-07-29-12-20-39.gh-issue-127172.Kf3Xa9.rst @@ -0,0 +1,4 @@ +Creating a virtual environment with :mod:`venv` now writes the activation +scripts using permissions derived from the process umask (and any default +ACL), consistent with the other files it creates, instead of a fixed mode +copied from the packaged templates. Patch by tonghuaroot.