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

  1. Obtain the archive from a trusted, official source
  2. Inspect the archive's member names (unzip -l / tar -tf) and repackage or reject archives containing '..' entries
  3. 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

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


AI-assisted analysis of oraios/serena@7fcbca7e62 (2026-08-29). Data as JSON: /api/errors/d2e45acd521e720b. Report an issue: GitHub.