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 aView on GitHub (pinned to 64b1b85502)
Solutions
- If you control the naming, ensure the uploaded filename is a bare filename (no directories, no ..).
- 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.
- 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
- Upload files with plain basenames (no directories, no ..).
- Treat this error as a security signal; investigate unexpected sources.
- Sanitize user-supplied filenames before creating remote files.
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
- Refusing to build a path from an empty or non-string {label}
- Refusing to build a path from a {label} containing path sepa
- Path traversal detected: {label} {name!r} leaves no usable b
- Path traversal detected: {label} {name!r} resolves to {candi
- Refusing to write outside the configured directory: {compone
AI-assisted analysis of ComposioHQ/composio@64b1b85502 (2026-08-28).
Data as JSON: /api/errors/1016cd00d0f75d19.
Report an issue: GitHub.