From 0de27fa73b2dc85be9c9cad11bf032bd3c9563a5 Mon Sep 17 00:00:00 2001 From: esadomer <54475303+esadomer@users.noreply.github.com> Date: Tue, 13 Oct 2026 19:52:46 +0300 Subject: [PATCH 1/2] gh-159354: Reject main when copying an existing zipapp archive --- Lib/test/test_zipapp.py | 13 +++++++++++++ Lib/zipapp.py | 2 ++ .../2026-10-13-21-20-01.gh-issue-159354.zIpMai.rst | 2 ++ 3 files changed, 17 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2026-10-13-21-20-01.gh-issue-159354.zIpMai.rst diff --git a/Lib/test/test_zipapp.py b/Lib/test/test_zipapp.py index 8fb0a68deba535..8051d098a6035b 100644 --- a/Lib/test/test_zipapp.py +++ b/Lib/test/test_zipapp.py @@ -328,6 +328,19 @@ def test_read_from_fileobj(self): zipapp.create_archive(temp_archive, new_target, interpreter='python2.7') self.assertStartsWith(new_target.getvalue(), b'#!python2.7\n') + def test_main_with_existing_archive(self): + archive = self.tmpdir / 'source.pyz' + with zipfile.ZipFile(archive, 'w') as z: + z.writestr('__main__.py', '') + target = self.tmpdir / 'target.pyz' + for main in ('pkg.mod:fn', ''): + for source in (archive, io.BytesIO(archive.read_bytes())): + with self.subTest(main=main, source=source): + target.write_bytes(b'original content') + with self.assertRaises(zipapp.ZipAppError): + zipapp.create_archive(source, target, main=main) + self.assertEqual(target.read_bytes(), b'original content') + def test_remove_shebang(self): # Test that we can remove the shebang from a file. source = self.tmpdir / 'source' diff --git a/Lib/zipapp.py b/Lib/zipapp.py index a1cef18ada9d05..acd1ee42859173 100644 --- a/Lib/zipapp.py +++ b/Lib/zipapp.py @@ -103,6 +103,8 @@ def create_archive(source, target=None, interpreter=None, main=None, source_is_file = True if source_is_file: + if main is not None: + raise ZipAppError("Cannot change the main function when copying") _copy_archive(source, target, interpreter) return diff --git a/Misc/NEWS.d/next/Library/2026-10-13-21-20-01.gh-issue-159354.zIpMai.rst b/Misc/NEWS.d/next/Library/2026-10-13-21-20-01.gh-issue-159354.zIpMai.rst new file mode 100644 index 00000000000000..e38f438fade9b6 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-10-13-21-20-01.gh-issue-159354.zIpMai.rst @@ -0,0 +1,2 @@ +Fix :func:`zipapp.create_archive` to reject a ``main`` argument when copying +an existing archive, instead of silently ignoring it. From f27946aae61efaffaf317df6111da51192ffaf22 Mon Sep 17 00:00:00 2001 From: esadomer <54475303+esadomer@users.noreply.github.com> Date: Tue, 13 Oct 2026 21:07:16 +0300 Subject: [PATCH 2/2] gh-159354: Validate empty main arguments consistently --- Lib/test/test_zipapp.py | 26 +++++++++++++------ Lib/zipapp.py | 8 +++--- ...-10-13-21-20-01.gh-issue-159354.zIpMai.rst | 3 ++- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/Lib/test/test_zipapp.py b/Lib/test/test_zipapp.py index 8051d098a6035b..b40784c185735d 100644 --- a/Lib/test/test_zipapp.py +++ b/Lib/test/test_zipapp.py @@ -185,8 +185,13 @@ def test_main_and_main_py(self): source.mkdir() (source / '__main__.py').touch() target = self.tmpdir / 'source.pyz' - with self.assertRaises(zipapp.ZipAppError): - zipapp.create_archive(str(source), str(target), main='pkg.mod:fn') + for main in ('pkg.mod:fn', ''): + with self.subTest(main=main): + target.write_bytes(b'original content') + with self.assertRaisesRegex(zipapp.ZipAppError, + 'Cannot specify entry point'): + zipapp.create_archive(str(source), str(target), main=main) + self.assertEqual(target.read_bytes(), b'original content') def test_main_written(self): # Test that the __main__.py is written correctly. @@ -227,8 +232,10 @@ def test_main_validation(self): ] for main in problems: with self.subTest(main=main): - with self.assertRaises(zipapp.ZipAppError): + with self.assertRaisesRegex(zipapp.ZipAppError, + 'Invalid entry point:'): zipapp.create_archive(str(source), str(target), main=main) + self.assertFalse(target.exists()) def test_default_no_shebang(self): # Test that no shebang line is written to the target by default. @@ -441,11 +448,14 @@ def test_cmdline_copy_change_main(self): # Test copying an archive doesn't allow changing __main__.py. original = self.make_archive() target = self.tmpdir / 'target.pyz' - args = [str(original), '-o', str(target), '-m', 'foo:bar'] - with self.assertRaises(SystemExit) as cm: - zipapp.main(args) - # Program should exit with a non-zero return code. - self.assertTrue(cm.exception.code) + for main in ('foo:bar', ''): + with self.subTest(main=main): + args = [str(original), '-o', str(target), '-m', main] + with self.assertRaises(SystemExit) as cm: + zipapp.main(args) + # Program should exit with a non-zero return code. + self.assertTrue(cm.exception.code) + self.assertFalse(target.exists()) @patch('sys.stdout', new_callable=io.StringIO) def test_info_command(self, mock_stdout): diff --git a/Lib/zipapp.py b/Lib/zipapp.py index acd1ee42859173..cb73c0beb7e4e1 100644 --- a/Lib/zipapp.py +++ b/Lib/zipapp.py @@ -112,14 +112,14 @@ def create_archive(source, target=None, interpreter=None, main=None, if not source.exists(): raise ZipAppError("Source does not exist") has_main = (source / '__main__.py').is_file() - if main and has_main: + if main is not None and has_main: raise ZipAppError( "Cannot specify entry point if the source has __main__.py") - if not (main or has_main): + if main is None and not has_main: raise ZipAppError("Archive has no entry point") main_py = None - if main: + if main is not None: # Check that main has the right format. mod, sep, fn = main.partition(':') mod_ok = all(part.isidentifier() for part in mod.split('.')) @@ -221,7 +221,7 @@ def main(args=None): if args.output is None or (os.path.exists(args.output) and os.path.samefile(args.source, args.output)): raise SystemExit("In-place editing of archives is not supported") - if args.main: + if args.main is not None: raise SystemExit("Cannot change the main function when copying") create_archive(args.source, args.output, diff --git a/Misc/NEWS.d/next/Library/2026-10-13-21-20-01.gh-issue-159354.zIpMai.rst b/Misc/NEWS.d/next/Library/2026-10-13-21-20-01.gh-issue-159354.zIpMai.rst index e38f438fade9b6..8cdc40b1e72c03 100644 --- a/Misc/NEWS.d/next/Library/2026-10-13-21-20-01.gh-issue-159354.zIpMai.rst +++ b/Misc/NEWS.d/next/Library/2026-10-13-21-20-01.gh-issue-159354.zIpMai.rst @@ -1,2 +1,3 @@ Fix :func:`zipapp.create_archive` to reject a ``main`` argument when copying -an existing archive, instead of silently ignoring it. +an existing archive, instead of silently ignoring it. An empty ``main`` is +also rejected when creating an archive from a directory.