ComposioHQ/composio · error · UnsafePathComponentError

Refusing to write {label} containing characters reserved by

Error message

Refusing to write {label} containing characters reserved by Windows: {name!r}

What it means

safe_basename rejects filenames containing characters Windows reserves — control characters (ord < 32) or any of <>:"|?*. Catching these on every platform prevents files that would be unwritable or unopenable when the workspace syncs to a Windows machine.

Source

Thrown at python/composio/utils/safe_path.py:181

        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()
    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: "

View on GitHub (pinned to 64b1b85502)

Solutions

  1. Sanitize by replacing reserved characters with '-' or '_' before download
  2. Generate your own filename and keep the original only in metadata

Example fix

# before
secure_basename_join(base, 'report<1>.pdf')
# after
import re
safe = re.sub(r'[<>:"|?*]', '_', 'report<1>.pdf')
secure_basename_join(base, safe)
Defensive patterns

Strategy: validation

Validate before calling

import re
RESERVED = re.compile(r'[<>:"|?*\x00-\x1f]')
def windows_clean(v):
    return isinstance(v, str) and not RESERVED.search(v)

Type guard

import re
def is_windows_safe_name(v: str) -> bool:
    return not re.search(r'[<>:"|?*\x00-\x1f]', v)

Try / catch

from composio.exceptions import UnsafePathComponentError
import re
try:
    p = secure_basename_join(base, name)
except UnsafePathComponentError:
    p = secure_basename_join(base, re.sub(r'[<>:"|?*]', '_', name))

Prevention

When it happens

Trigger: secure_basename_join(base, name) where name contains characters like 'report<1>.pdf', 'file|name', 'q?.txt', or embedded control chars.

Common situations: Filenames from Unix-only sources that never considered Windows; adversarial pipe/colon names; CSV/emoji control characters in filenames.

Related errors


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