@@ -65,6 +65,90 @@ def test_must_unzip(self, verify_external_attributes):
6565
6666 self ._verify_file_count (verify_external_attributes )
6767
68+ def test_must_block_absolute_symlink_by_default (self ):
69+ """Test that absolute symlinks are blocked when mount_symlinks=False"""
70+ files_with_absolute_symlink = {
71+ "1.txt" : {"file_type" : 0o10 , "contents" : b"foo" , "permissions" : 0o644 },
72+ "absolute_link" : {"file_type" : 0o12 , "contents" : b"/tmp/external" , "permissions" : 0o644 },
73+ }
74+
75+ with self ._create_zip (files_with_absolute_symlink ) as zip_file_name :
76+ with self ._temp_dir () as extract_dir :
77+ with self .assertRaises (ValueError ) as context :
78+ unzip (zip_file_name , extract_dir , mount_symlinks = False )
79+
80+ self .assertIn ("absolute target" , str (context .exception ).lower ())
81+
82+ def test_must_block_relative_escape_symlink_by_default (self ):
83+ """Test that relative symlinks escaping the directory are blocked when mount_symlinks=False"""
84+ files_with_escape_symlink = {
85+ "1.txt" : {"file_type" : 0o10 , "contents" : b"foo" , "permissions" : 0o644 },
86+ "escape_link" : {"file_type" : 0o12 , "contents" : b"../../external" , "permissions" : 0o644 },
87+ }
88+
89+ with self ._create_zip (files_with_escape_symlink ) as zip_file_name :
90+ with self ._temp_dir () as extract_dir :
91+ with self .assertRaises (ValueError ) as context :
92+ unzip (zip_file_name , extract_dir , mount_symlinks = False )
93+
94+ self .assertIn ("outside" , str (context .exception ).lower ())
95+
96+ def test_must_allow_regular_symlink_by_default (self ):
97+ """Test that symlinks within the extraction directory are allowed by default"""
98+ with self ._create_zip (self .files_with_external_attr ) as zip_file_name :
99+ with self ._temp_dir () as extract_dir :
100+ unzip (zip_file_name , extract_dir , mount_symlinks = False )
101+
102+ # Verify that symlink was created
103+ regular_path = os .path .join (extract_dir , "symlinkToF2" )
104+ self .assertTrue (os .path .islink (regular_path ))
105+ self .assertEqual (os .readlink (regular_path ), "1.txt" )
106+
107+ def test_must_block_subdirectory_symlink_escaping_extraction_dir (self ):
108+ """Test that a symlink nested in a subdirectory escaping via relative path is blocked"""
109+ files_with_nested_escape = {
110+ "1.txt" : {"file_type" : 0o10 , "contents" : b"foo" , "permissions" : 0o644 },
111+ "subdir/link" : {"file_type" : 0o12 , "contents" : b"../../../external" , "permissions" : 0o644 },
112+ }
113+
114+ with self ._create_zip (files_with_nested_escape ) as zip_file_name :
115+ with self ._temp_dir () as extract_dir :
116+ with self .assertRaises (ValueError ) as context :
117+ unzip (zip_file_name , extract_dir , mount_symlinks = False )
118+
119+ self .assertIn ("outside" , str (context .exception ).lower ())
120+
121+ def test_must_allow_relative_escape_symlink_with_mount_symlinks (self ):
122+ """Test that relative symlinks escaping the directory are allowed when mount_symlinks=True"""
123+ files_with_escape_symlink = {
124+ "1.txt" : {"file_type" : 0o10 , "contents" : b"foo" , "permissions" : 0o644 },
125+ "escape_link" : {"file_type" : 0o12 , "contents" : b"../../external" , "permissions" : 0o644 },
126+ }
127+
128+ with self ._create_zip (files_with_escape_symlink ) as zip_file_name :
129+ with self ._temp_dir () as extract_dir :
130+ unzip (zip_file_name , extract_dir , mount_symlinks = True )
131+
132+ link_path = os .path .join (extract_dir , "escape_link" )
133+ self .assertTrue (os .path .islink (link_path ))
134+ self .assertEqual (os .readlink (link_path ), "../../external" )
135+
136+ def test_must_allow_absolute_symlink_with_mount_symlinks (self ):
137+ """Test that absolute symlinks are allowed when mount_symlinks=True"""
138+ files_with_absolute_symlink = {
139+ "1.txt" : {"file_type" : 0o10 , "contents" : b"foo" , "permissions" : 0o644 },
140+ "external_link" : {"file_type" : 0o12 , "contents" : b"/tmp/external" , "permissions" : 0o644 },
141+ }
142+
143+ with self ._create_zip (files_with_absolute_symlink ) as zip_file_name :
144+ with self ._temp_dir () as extract_dir :
145+ unzip (zip_file_name , extract_dir , mount_symlinks = True )
146+
147+ # Verify the symlink was created
148+ link_path = os .path .join (extract_dir , "external_link" )
149+ self .assertTrue (os .path .islink (link_path ))
150+ self .assertEqual (os .readlink (link_path ), "/tmp/external" )
151+
68152 @contextmanager
69153 def _reset (self , verify_external_attributes ):
70154 self .expected_files = 0
0 commit comments