ComposioHQ/composio · critical · UnsafePathComponentError
Path traversal detected: {label} {name!r} leaves no usable b
Error message
Path traversal detected: {label} {name!r} leaves no usable basename to write to. What it means
safe_basename found no usable basename after taking PureWindowsPath(name).name — the value was empty, whitespace-only, all dots, or pure separators. Such a name would resolve to the base directory itself and surface as IsADirectoryError at write time, so it is treated as path traversal.
Source
Thrown at python/composio/utils/safe_path.py:172
SDK runs on POSIX, where ``Path(...).name`` would return it intact.
Names that leave no usable basename are refused rather than replaced with a
generated one: a response that cannot name its own file is malformed or
hostile, and inventing a name would hide that. ``.`` and the empty string
both basename to ``""``, which makes an output path equal to its own
directory and surfaces as ``IsADirectoryError`` at write time.
:raises UnsafePathComponentError: when ``name`` yields no usable basename or
is unsafe to write.
"""
if not isinstance(name, str):
raise UnsafePathComponentError(
f"Refusing to write a non-string {label}: {name!r}"
)
raw_basename = PureWindowsPath(name).name
if not raw_basename or not raw_basename.strip() or set(raw_basename) == {"."}:
raise UnsafePathComponentError(
f"Path traversal detected: {label} {name!r} leaves no usable "
"basename to write to."
)
if "\x00" in raw_basename:
raise UnsafePathComponentError(
f"Refusing to write {label} containing a NUL byte: {name!r}"
)
if any(ord(char) < 32 or char in '<>:"|?*' for char in raw_basename):
raise UnsafePathComponentError(
f"Refusing to write {label} containing characters reserved by "
f"Windows: {name!r}"
)
if raw_basename.endswith((" ", ".")):
raise UnsafePathComponentError(
f"Refusing to write {label} ending in a space or dot: {name!r}"
)
basename = raw_basename.strip()View on GitHub (pinned to 64b1b85502)
Solutions
- Fall back to a server-generated filename when extraction yields nothing
- Reject the download/request carrying the empty name
Example fix
# before
secure_basename_join(base, fname) # fname == '..'
# after
if not fname or set(fname) <= {'.', ' ', '/', '\\'}:
fname = f"download-{uuid.uuid4().hex}"
secure_basename_join(base, fname) Defensive patterns
Strategy: validation
Validate before calling
from pathlib import PureWindowsPath
def has_usable_basename(v):
b = PureWindowsPath(v).name if isinstance(v, str) else ''
return bool(b.strip()) and set(b) != {'.'} Type guard
def is_writable_name(v: str) -> bool:
b = PureWindowsPath(v).name
return bool(b) and bool(b.strip()) and set(b) != {'.'} Try / catch
from composio.exceptions import UnsafePathComponentError
from uuid import uuid4
try:
p = secure_basename_join(base, name)
except UnsafePathComponentError:
p = secure_basename_join(base, f'file-{uuid4().hex}') Prevention
- Never accept empty/dot-only filenames; substitute a generated name
- Treat separator-only names as traversal attempts
When it happens
Trigger: secure_basename_join(base, ''), (base, ' '), (base, '...'), (base, '/'), (base, '..') — the basename extraction yields nothing writable.
Common situations: Empty filename fields from APIs; values that are entirely separator/dot characters crafted to escape to a parent or root.
Related errors
- Unsafe path component: {e}
- 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} 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/bd51014d997540bd.
Report an issue: GitHub.