ComposioHQ/composio · error · UnsafePathComponentError

Refusing to build a path from an empty or non-string {label}

Error message

Refusing to build a path from an empty or non-string {label}: {value!r}

What it means

assert_safe_path_component refuses to build a path from a value that is not a non-empty string (None, int, empty ''). All untrusted path components (slugs, IDs) must be non-empty strings before they can join a filesystem path.

Source

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

        sep = os.sep
        parent_with_sep = parent_str if parent_str.endswith(sep) else parent_str + sep
        return child_str.startswith(parent_with_sep)
    except OSError:
        return False


def assert_safe_path_component(value: str, *, label: str = "path component") -> str:
    """Return ``value`` unchanged if it is safe to use as a single path
    component, else raise.

    Fails closed. Rejects traversal (``..``), separators of either platform,
    absolute paths, drive letters, NUL bytes, reserved device names, and
    anything outside :data:`SAFE_COMPONENT_REGEX`.

    :raises UnsafePathComponentError: when ``value`` is unsafe.
    """
    if not isinstance(value, str) or not value:
        raise UnsafePathComponentError(
            f"Refusing to build a path from an empty or non-string {label}: {value!r}"
        )

    # `PureWindowsPath` treats both `/` and `\` as separators, so a single check
    # catches `../x` and `..\x` regardless of the host platform. A slug crafted
    # for a Windows target must not slip through on a POSIX build machine.
    as_windows_path = PureWindowsPath(value)
    if len(as_windows_path.parts) != 1 or as_windows_path.anchor:
        raise UnsafePathComponentError(
            f"Refusing to build a path from a {label} containing path separators "
            f"or a drive letter: {value!r}"
        )

    if len(value) > MAX_COMPONENT_LENGTH:
        raise UnsafePathComponentError(
            f"Refusing to build a path from a {label} longer than "
            f"{MAX_COMPONENT_LENGTH} characters: {value[:32]!r}... "
            f"({len(value)} characters)"

View on GitHub (pinned to 64b1b85502)

Solutions

  1. Check for None/empty before joining and skip or default the operation
  2. Coerce non-string identifiers with str(...) only if that is genuinely the intended value
  3. Fix the caller that produced None (missing field in request payload)

Example fix

# before
path = secure_join(root, tool.slug)  # slug is None
# after
if not tool.slug:
    raise ValueError("tool slug missing")
path = secure_join(root, tool.slug)
Defensive patterns

Strategy: type-guard

Validate before calling

def usable_component(v):
    return isinstance(v, str) and len(v) > 0

Type guard

def is_safe_component(v) -> bool:
    return isinstance(v, str) and bool(v)

Try / catch

from composio.exceptions import UnsafePathComponentError
try:
    p = secure_join(root, slug)
except UnsafePathComponentError:
    slug = fallback_slug()  # e.g. derived from a hash
    p = secure_join(root, slug)

Prevention

When it happens

Trigger: Calling secure_join(root, component) where component is None (e.g. a missing API field), an empty string, or a non-string like an int ID.

Common situations: API responses with missing/optional slug or ID fields passed straight into secure_join; deserialized JSON where the field is absent rather than empty.

Related errors


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