ComposioHQ/composio · error · UnsafePathComponentError
Refusing to build a path from an unsafe {label}: {value!r}.
Error message
Refusing to build a path from an unsafe {label}: {value!r}. Expected only letters, digits, underscores, and hyphens (pattern {SAFE_COMPONENT_REGEX.pattern}). What it means
assert_safe_path_component only accepts letters, digits, underscores, and hyphens (^[A-Za-z0-9_-]+$ via fullmatch). Anything else — dots, spaces, unicode, trailing newline — is rejected. fullmatch (not match) is used so 'GMAIL\n' can't sneak a control character past a trailing-$ match.
Source
Thrown at python/composio/utils/safe_path.py:130
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)"
)
# `.` and `..` are excluded by the regex (no `.` in the character class),
# as are NUL bytes and every separator. The explicit checks above exist to
# produce a precise error message rather than a generic pattern mismatch.
#
# `fullmatch`, not `match`: in a `match`, `$` also matches just before a
# single trailing newline, so `"GMAIL\n"` would satisfy `^[A-Za-z0-9_-]+$`
# and reach the filesystem with a control character in the name.
if not SAFE_COMPONENT_REGEX.fullmatch(value):
raise UnsafePathComponentError(
f"Refusing to build a path from an unsafe {label}: {value!r}. "
f"Expected only letters, digits, underscores, and hyphens "
f"(pattern {SAFE_COMPONENT_REGEX.pattern})."
)
if value.upper() in WINDOWS_RESERVED_NAMES:
raise UnsafePathComponentError(
f"Refusing to build a path from a reserved device name as {label}: {value!r}"
)
return value
def safe_basename(name: str, *, label: str = "filename") -> str:
"""Collapse an untrusted filename to a bare, writable basename.
Filenames need their own validator: :func:`assert_safe_path_component`
forbids ``.``, which nearly every real filename contains. This applies theView on GitHub (pinned to 64b1b85502)
Solutions
- Normalize the identifier: strip whitespace and replace disallowed characters with '_'
- If the value is user-supplied, enforce the slug pattern at input time
- Use safe_basename/secure_basename_join instead when the value is a filename that legitimately contains dots
Example fix
# before secure_join(root, 'gmail.send_email') # after import re slug = re.sub(r'[^A-Za-z0-9_-]', '_', 'gmail.send_email') secure_join(root, slug)
Defensive patterns
Strategy: validation
Validate before calling
import re
SAFE = re.compile(r'^[A-Za-z0-9_-]+$')
def is_clean_component(v):
return isinstance(v, str) and bool(SAFE.fullmatch(v)) Type guard
import re
def is_slug_safe(v: str) -> bool:
return bool(re.fullmatch(r'[A-Za-z0-9_-]+', v)) Try / catch
from composio.exceptions import UnsafePathComponentError
import re
try:
p = secure_join(root, slug)
except UnsafePathComponentError:
p = secure_join(root, re.sub(r'[^A-Za-z0-9_-]', '_', slug)) Prevention
- Enforce the slug pattern on user input at the boundary
- Use safe_basename for filenames that legitimately contain dots
When it happens
Trigger: secure_join(root, 'my.tool'), secure_join(root, 'café'), or any slug with punctuation/whitespace/control characters.
Common situations: Backend tool slugs containing dots or non-ASCII characters; user-supplied names used as directory components; values with trailing newlines from sloppy string handling.
Related errors
- Refusing to write a non-string {label}: {name!r}
- Refusing to write {label} containing a NUL byte: {name!r}
- 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
AI-assisted analysis of ComposioHQ/composio@64b1b85502 (2026-08-28).
Data as JSON: /api/errors/10e228da74a06e0a.
Report an issue: GitHub.