[Bug]: Tar path traversal (Zip Slip) in decompress_to_cache — arbitrary file write outside cache directory
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 3.2k
- Forks
- 248
- Avg merge
- 4d 8h
- Merged PRs (30d)
- 4
Description
Summary
The decompress_to_cache method uses tarfile.extractall() without sanitizing member paths, making it vulnerable to Zip Slip / Tar Slip (CVE-2007-4559 class). A malicious tar archive can write files to arbitrary locations outside cache_dir.
Note: This is related to but distinct from #327, which tracks the DeprecationWarning/platform compatibility aspect. This issue specifically addresses the security vulnerability — path traversal allowing arbitrary file writes.
Affected Code
File: fastembed/common/model_management.py, lines 304–311
@classmethod
def decompress_to_cache(cls, targz_path: str, cache_dir: str) -> str:
# ...
with tarfile.open(targz_path, "r:gz") as tar:
tar.extractall(
path=cache_dir, # No filter, no member sanitization
)
Reproduction
import tarfile, os, tempfile, io
# Create a malicious tar that writes outside the intended directory
with tempfile.NamedTemporaryFile(suffix='.tar.gz', delete=False) as f:
evil_tar = f.name
with tarfile.open(evil_tar, 'w:gz') as tar:
payload = b"PWNED"
info = tarfile.TarInfo(name="../../tmp/fastembed_pwned.txt")
info.size = len(payload)
tar.addfile(info, io.BytesIO(payload))
# Call decompress_to_cache with this tar
cache_dir = tempfile.mkdtemp()
from fastembed.common.model_management import ModelManagement
ModelManagement.decompress_to_cache(evil_tar, cache_dir)
# File written outside cache_dir:
print(os.path.exists("/tmp/fastembed_pwned.txt")) # True
Attack Surface
- Custom model URLs via
add_custom_model()pointing to attacker-controlled servers - Compromised HuggingFace repos or GCS buckets (supply chain attack)
- MITM on HTTP redirects
Impact
- Arbitrary file write to any path writable by the process (SSH keys, cron jobs, Python packages, shell configs)
- On Python 3.14, the default
filterchanges to'data', which will silently change extraction behavior and may break existing archives
Suggested Fix
Add path filtering to block traversal:
@classmethod
def decompress_to_cache(cls, targz_path: str, cache_dir: str) -> str:
with tarfile.open(targz_path, "r:gz") as tar:
# Python 3.12+: use filter='data' to block traversal
# Python 3.11 and earlier: manual sanitization
try:
tar.extractall(path=cache_dir, filter='data')
except TypeError:
# Python < 3.12 fallback
for member in tar.getmembers():
member_path = os.path.realpath(os.path.join(cache_dir, member.name))
if not member_path.startswith(os.path.realpath(cache_dir) + os.sep):
raise ValueError(f"Unsafe tar member path: {member.name}")
tar.extractall(path=cache_dir)
Found via automated codebase analysis. Confirmed independently by three reviewers (Claude, Codex, Gemini).
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in fastembed/common/model_management.py at decompress_to_cache, around lines 304–311, and run the tar traversal reproduction from the issue. Check the supported Python versions and the existing extraction behavior before applying the suggested filtering approach. Done means a malicious member cannot write outside cache_dir while valid archives still extract correctly.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- security
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 58/100