Coverage for /usr/local/lib/python3.10/site-packages/opal_common-0.0.0-py3.10.egg/opal_common/security/tarsafe.py: 24%
50 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 11:54 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 11:54 +0000
1# This file is a copy of tarsafe by Andrew Scott MIT license https://github.com/beatsbears/tarsafe
2import os
3import pathlib
4import tarfile
7class TarSafe(tarfile.TarFile):
8 """A safe subclass of the TarFile class for interacting with tar files."""
10 def __init__(self, *args, **kwargs):
11 super().__init__(*args, **kwargs)
12 self.directory = os.getcwd()
14 @classmethod
15 def open(
16 cls, name=None, mode="r", fileobj=None, bufsize=tarfile.RECORDSIZE, **kwargs
17 ):
18 return super().open(name, mode, fileobj, bufsize, **kwargs)
20 def extract(self, member, path="", set_attrs=True, *, numeric_owner=False):
21 """Override the parent extract method and add safety checks."""
22 self._safetar_check()
23 super().extract(member, path, set_attrs=set_attrs, numeric_owner=numeric_owner)
25 def extractall(self, path=".", members=None, numeric_owner=False):
26 """Override the parent extractall method and add safety checks."""
27 self._safetar_check()
28 super().extractall(path, members, numeric_owner=numeric_owner)
30 def _safetar_check(self):
31 """Runs all necessary checks for the safety of a tarfile."""
32 try:
33 for tarinfo in self.__iter__():
34 if self._is_traversal_attempt(tarinfo=tarinfo):
35 raise TarSafeException(
36 f"Attempted directory traversal for member: {tarinfo.name}"
37 )
38 if self._is_unsafe_symlink(tarinfo=tarinfo):
39 raise TarSafeException(
40 f"Attempted directory traversal via symlink for member: {tarinfo.linkname}"
41 )
42 if self._is_unsafe_link(tarinfo=tarinfo):
43 raise TarSafeException(
44 f"Attempted directory traversal via link for member: {tarinfo.linkname}"
45 )
46 if self._is_device(tarinfo=tarinfo):
47 raise TarSafeException(
48 f"tarfile returns true for isblk() or ischr()"
49 )
50 except Exception as err:
51 raise
53 def _is_traversal_attempt(self, tarinfo):
54 if not os.path.abspath(os.path.join(self.directory, tarinfo.name)).startswith(
55 self.directory
56 ):
57 return True
58 return False
60 def _is_unsafe_symlink(self, tarinfo):
61 if tarinfo.issym():
62 symlink_file = pathlib.Path(
63 os.path.normpath(os.path.join(self.directory, tarinfo.linkname))
64 )
65 if not os.path.abspath(
66 os.path.join(self.directory, symlink_file)
67 ).startswith(self.directory):
68 return True
69 return False
71 def _is_unsafe_link(self, tarinfo):
72 if tarinfo.islnk():
73 link_file = pathlib.Path(
74 os.path.normpath(os.path.join(self.directory, tarinfo.linkname))
75 )
76 if not os.path.abspath(os.path.join(self.directory, link_file)).startswith(
77 self.directory
78 ):
79 return True
80 return False
82 def _is_device(self, tarinfo):
83 return tarinfo.ischr() or tarinfo.isblk()
86class TarSafeException(Exception):
87 pass
90open = TarSafe.open