Skip to content

Commit 085f21b

Browse files
Merge pull request #1662 from Kirtan-pc/fix/path-containment
Fix: SafeTarExtractor path containment check can be bypassed due to naive string prefix matching
2 parents e345677 + 9ee4a7c commit 085f21b

2 files changed

Lines changed: 35 additions & 21 deletions

File tree

security/tar_safe.py

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -130,30 +130,24 @@ def _validate_member(
130130

131131
target_path = self._get_safe_path(member, extract_path)
132132

133-
# Check for path traversal
133+
# Check for path traversal using Path.relative_to()
134134
try:
135135
target_abs = target_path.resolve()
136136
extract_abs = extract_path.resolve()
137-
if os.path.commonpath([str(target_abs), str(extract_abs)]) != str(extract_abs):
138-
raise UnsafeTarError(
139-
f"Path traversal detected: {member.name} -> {target_path}"
140-
)
141-
except (OSError, RuntimeError):
142-
target_str = str(target_path.absolute())
143-
extract_str = str(extract_path.absolute())
144-
try:
145-
if os.path.commonpath([target_str, extract_str]) != extract_str:
146-
raise UnsafeTarError(
147-
f"Path traversal detected: {member.name} -> {target_path}"
148-
)
149-
except ValueError:
150-
raise UnsafeTarError(
151-
f"Path traversal detected (different drives): {member.name} -> {target_path}"
152-
)
153-
except ValueError:
154-
# os.path.commonpath raises ValueError on Windows for different drives
137+
target_abs.relative_to(extract_abs)
138+
except (ValueError, OSError, RuntimeError):
155139
raise UnsafeTarError(
156-
f"Path traversal detected (different drives): {member.name} -> {target_path}"
140+
f"Path traversal detected: {member.name} -> {target_path}"
141+
)
142+
143+
if target_path.exists() and not overwrite:
144+
raise UnsafeTarError(
145+
f"File already exists and overwrite=False: {target_path}"
146+
)
147+
148+
if member.type in self.blocked_types:
149+
raise UnsafeTarError(
150+
f"Blocked file type for {member.name}: {member.type}"
157151
)
158152

159153
if target_path.exists() and not overwrite:

tests/test_security.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,27 @@ def test_sibling_directory_traversal(self, tmp_path):
220220
with pytest.raises(UnsafeTarError):
221221
extractor.extract(tar_file, extract_dir)
222222

223-
def test_absolute_path_traversal(self, tmp_path):
223+
def test_prefix_bypass_path_traversal(self, tmp_path):
224+
"""Test that prefix-based bypass (e.g., /tmp/a vs /tmp/ab) is caught."""
225+
tar_file = tmp_path / "prefix_bypass.tar"
226+
227+
with tarfile.open(tar_file, 'w') as tar:
228+
info = tarfile.TarInfo(name='../ab/evil.txt')
229+
info.size = 10
230+
info.type = tarfile.REGTYPE
231+
content = b'x' * 10
232+
fileobj = io.BytesIO(content)
233+
tar.addfile(info, fileobj)
234+
235+
extract_dir = tmp_path / "a"
236+
extract_dir.mkdir(exist_ok=True)
237+
sibling_dir = tmp_path / "ab"
238+
sibling_dir.mkdir(exist_ok=True)
239+
240+
extractor = SafeTarExtractor()
241+
242+
with pytest.raises(UnsafeTarError):
243+
extractor.extract(tar_file, extract_dir)
224244
"""Test absolute path traversal prevention."""
225245
tar_file = tmp_path / "absolute_traversal.tar"
226246

0 commit comments

Comments
 (0)