ComposioHQ/composio · warning · UnsafePathComponentError
Refusing to write {label} ending in a space or dot: {name!r}
Error message
Refusing to write {label} ending in a space or dot: {name!r} What it means
safe_basename rejects filenames ending in a space or a dot ('file ', 'file.'). Windows silently strips trailing spaces/dots, which breaks round-tripping and can collide with other names, so such basenames are refused up front.
Source
Thrown at python/composio/utils/safe_path.py:186
)
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()
try:
encoded_length = len(os.fsencode(basename))
except UnicodeEncodeError as e:
raise UnsafePathComponentError(
f"Refusing to write {label} containing invalid Unicode: {name!r}"
) from e
if encoded_length > MAX_COMPONENT_LENGTH:
raise UnsafePathComponentError(
f"Refusing to write {label} longer than {MAX_COMPONENT_LENGTH} bytes: "
f"{basename[:32]!r}... ({encoded_length} bytes)"
)
# Compare everything before the first dot: on Windows `NUL.tar.gz` opens
# the null device just as `NUL` does, so any number of extensions provides
# no protection.View on GitHub (pinned to 64b1b85502)
Solutions
- Strip trailing spaces and dots from the filename before passing it in (note the SDK itself only strips for internal checks, the write uses raw basename)
- Generate a normalized filename
Example fix
# before
secure_basename_join(base, 'data.')
# after
secure_basename_join(base, 'data.'.rstrip(' .')) Defensive patterns
Strategy: validation
Validate before calling
def no_trailing_space_dot(v):
return isinstance(v, str) and not v.rstrip().rstrip('.').endswith((' ', '.')) and not PureWindowsPath(v).name.endswith((' ', '.')) Type guard
from pathlib import PureWindowsPath
def is_trimmed_name(v: str) -> bool:
return not PureWindowsPath(v).name.endswith((' ', '.')) Try / catch
from composio.exceptions import UnsafePathComponentError
try:
p = secure_basename_join(base, name)
except UnsafePathComponentError:
p = secure_basename_join(base, name.rstrip(' .')) Prevention
- Strip trailing whitespace/dots from user-supplied filenames
- Watch for copy-pasted names with invisible trailing spaces
When it happens
Trigger: secure_basename_join(base, name) where the extracted basename ends with ' ' or '.' — e.g. 'my report ' or 'data.'.
Common situations: Copy-pasted filenames with invisible trailing whitespace; API-supplied names with trailing dots from truncated extensions.
Related errors
- Refusing to write {label} containing characters reserved by
- Refusing to build a path from a reserved device name as {lab
- Refusing to write {label} containing invalid Unicode: {name!
- Refusing to write {label} that is a reserved device name: {n
- Could not determine a home directory to store the Composio c
AI-assisted analysis of ComposioHQ/composio@64b1b85502 (2026-08-28).
Data as JSON: /api/errors/b267492be1583090.
Report an issue: GitHub.