ComposioHQ/composio · error · ErrorDownloadingFile

Unsafe path component: {e}

Error message

Unsafe path component: {e}

What it means

When downloading a FileModel, the remote-supplied filename (self.name) is joined into the output directory via secure_basename_join, which rejects path traversal and unsafe components. A violation (../ sequences, absolute paths, sibling-prefix attacks like output_evil/foo) is re-raised as ErrorDownloadingFile.

Source

Thrown at python/composio/core/models/_files.py:714

            input, provided it was produced by :func:`secure_join`.
        :param root: Trusted containment anchor, configured locally and never
            derived from an API response. Required rather than defaulted:
            checking containment against a directory that untrusted input has
            already relocated is not a check at all, and ``outdir`` may be
            exactly such a directory. Callers must name the anchor explicitly.
        :param max_size: Maximum number of bytes to write to disk. ``s3url`` is
            an API-response field, so the body behind it is untrusted: the
            streamed byte count is authoritative because ``Content-Length`` can
            be absent or dishonest.
        """
        # SEC-316: `self.name` also comes from the (potentially compromised or
        # MITM'd) API response. Collapsed to a bare filename and checked against
        # `root` — not `outdir` — so a name like `output_evil/foo`
        # (sibling-prefix attack) is rejected too.
        try:
            outfile = secure_basename_join(outdir, self.name, root=root)
        except UnsafePathComponentError as e:
            raise ErrorDownloadingFile(str(e)) from e
        try:
            response = safe_get(
                self.s3url,
                stream=True,
                timeout=(_CONNECT_TIMEOUT, _READ_TIMEOUT),
            )
        except requests.exceptions.RequestException as e:
            raise ErrorDownloadingFile(
                "Error downloading file: "
                f"{_sanitize_url_for_logging(self.s3url)}. Error: {type(e).__name__}"
            ) from e
        if response.status_code != 200:
            response.close()
            raise ErrorDownloadingFile(
                f"Error downloading file: {_sanitize_url_for_logging(self.s3url)}"
            )

        # Early abort for a self-declared oversized body. The header is only a

View on GitHub (pinned to 64b1b85502)

Solutions

  1. If you control the naming, ensure the uploaded filename is a bare filename (no directories, no ..).
  2. Treat this defensively: it protects you from path traversal — do not try to bypass it; report it if the name looks legitimately benign (slash-containing names) so the SDK/backend sanitizes.
  3. As a workaround, download the raw bytes yourself from s3url and choose your own filename.

Example fix

# before
await model.download(outdir=Path('/tmp/out'))  # model.name = 'a/../evil.txt' -> raises
# after: fetch content yourself with a safe name
import requests
safe = Path('/tmp/out') / 'downloaded.bin'
safe.write_bytes(requests.get(model.s3url, timeout=60).content)
Defensive patterns

Strategy: try-catch

Validate before calling

from pathlib import PurePosixPath

def safe_name(name: str) -> bool:
    return bool(name) and '/' not in name and '\\' not in name and name not in {'.', '..'}

Type guard

def is_safe_basename(name: str) -> bool:
    p = PurePosixPath(name)
    return p.name == name and name not in {'.', '..'} and not name.startswith('/')

Try / catch

from composio.core.models._files import ErrorDownloadingFile

try:
    path = model.download(outdir=outdir)
except ErrorDownloadingFile as e:
    if 'Unsafe path' in str(e) or 'component' in str(e):
        import requests
        safe = outdir / 'downloaded.bin'
        safe.write_bytes(requests.get(model.s3url, timeout=120).content)
        path = safe
    else:
        raise

Prevention

When it happens

Trigger: FileModel.download (or _download_file_value) where the API-provided file name contains slashes, .., or otherwise tries to escape the output root — usually a compromised/misbehaving backend response, since the name comes from the server.

Common situations: Rare in practice; indicates a tampered or buggy API response, a filename containing unexpected separators (e.g. 'reports/2024/q1.pdf' stored verbatim), or a middlebox/CDN altering responses.

Related errors


AI-assisted analysis of ComposioHQ/composio@64b1b85502 (2026-08-28). Data as JSON: /api/errors/1016cd00d0f75d19. Report an issue: GitHub.