oraios/serena · critical · SolidLSPException
Unsafe archive member '{member_name}': path traversal is not
Error message
Unsafe archive member '{member_name}': path traversal is not allowed What it means
Raised by _validate_extraction_path when an archive member name contains a '..' path component. This blocks Zip-Slip / path-traversal attacks where a malicious archive writes files outside the extraction directory.
Source
Thrown at src/solidlsp/ls_utils.py:596
"""
if not allowed_hosts:
return
hostname = urlparse(url).hostname
normalized_allowed_hosts = {host.lower() for host in allowed_hosts}
if hostname is None or hostname.lower() not in normalized_allowed_hosts:
raise SolidLSPException(
f"Refusing to download from host '{hostname or '<unknown>'}'; allowed hosts: {sorted(normalized_allowed_hosts)}"
)
@staticmethod
def _validate_extraction_path(member_name: str, target_path: str) -> str:
"""
Validates that an archive member stays within the extraction root and returns its destination path.
"""
normalized_parts = Path(member_name).parts
if any(part == ".." for part in normalized_parts):
raise SolidLSPException(f"Unsafe archive member '{member_name}': path traversal is not allowed")
absolute_target_path = os.path.abspath(target_path)
absolute_member_path = os.path.abspath(os.path.join(target_path, member_name))
if not (absolute_member_path.startswith(absolute_target_path + os.sep) or absolute_member_path == absolute_target_path):
raise SolidLSPException(f"Unsafe archive member '{member_name}': path escapes extraction directory")
return absolute_member_path
@staticmethod
def _extract_zip_archive(archive_path: str, target_path: str) -> None:
"""
Extracts a ZIP archive safely while preserving Unix permissions when available.
"""
with zipfile.ZipFile(archive_path, "r") as zip_ref:
for zip_info in zip_ref.infolist():
extracted_path = FileUtils._validate_extraction_path(zip_info.filename, target_path)
if zip_info.is_dir():View on GitHub (pinned to 7fcbca7e62)
Solutions
- Obtain the archive from a trusted, official source
- Inspect the archive's member names (unzip -l / tar -tf) and repackage or reject archives containing '..' entries
- Report the malicious/mispackaged artifact to its maintainer
Example fix
// before member = "../../outside/evil.so" // after member = "lib/evil.so" # repackage archive with relative-only paths
Defensive patterns
Strategy: validation
Validate before calling
import zipfile
with zipfile.ZipFile(path) as z:
bad = [n for n in z.namelist() if ".." in n.split("/")]
if bad:
raise ValueError(f"archive contains traversal members: {bad}") Type guard
def archive_is_safe(member_names: list[str]) -> bool:
return all(".." not in n.split("/") and not n.startswith("/") for n in member_names) Try / catch
try:
download_and_extract_archive_verified(url, target, archive_type="zip")
except SolidLSPException as e:
if "path traversal" in str(e):
raise SecurityError("refusing malicious archive from " + url) from e
raise Prevention
- Only download archives from official, allowlisted sources
- Pre-download inspect member names for '..' or absolute paths
- Never weaken the validation to 'fix' this error — reject the archive
- Verify checksums so archives cannot be swapped in transit
When it happens
Trigger: Extracting a zip or tar archive (via _extract_zip_archive/_extract_tar_archive during download_and_extract_archive_verified) whose member entry names include '..' segments, e.g. '../../etc/cron.d/evil'.
Common situations: Downloading a language-server archive from an untrusted or compromised source; a mis-packaged archive using '../' in entry names.
Related errors
- Unsafe archive member '{member_name}': path escapes extracti
- Cannot edit external file: {relative_path}
- Memory name resolves outside the memories directory. Got: {'
- Memory name cannot contain '..' segments. Got: {name}
- Memory name cannot be absolute or contain empty path segment
AI-assisted analysis of oraios/serena@7fcbca7e62 (2026-08-29).
Data as JSON: /api/errors/d2e45acd521e720b.
Report an issue: GitHub.