{"record":{"id":"ee88d989c5e82365","repo":"ComposioHQ/composio","slug":"refusing-to-write-a-non-string-label-name-r","errorCode":null,"errorMessage":"Refusing to write a non-string {label}: {name!r}","messagePattern":"Refusing to write a non-string (.+?): (.+?)","errorType":"validation","errorClass":"UnsafePathComponentError","httpStatus":null,"severity":"error","filePath":"python/composio/utils/safe_path.py","lineNumber":166,"sourceCode":"    forbids ``.``, which nearly every real filename contains. This applies the\n    remaining checks — no separators, no traversal, no NUL, bounded length, no\n    reserved device name — to the one component a server most directly controls.\n\n    ``PureWindowsPath`` treats both ``/`` and ``\\\\`` as separators, so a name\n    crafted for a Windows target (``..\\\\..\\\\evil``) is stripped even when the\n    SDK runs on POSIX, where ``Path(...).name`` would return it intact.\n\n    Names that leave no usable basename are refused rather than replaced with a\n    generated one: a response that cannot name its own file is malformed or\n    hostile, and inventing a name would hide that. ``.`` and the empty string\n    both basename to ``\"\"``, which makes an output path equal to its own\n    directory and surfaces as ``IsADirectoryError`` at write time.\n\n    :raises UnsafePathComponentError: when ``name`` yields no usable basename or\n        is unsafe to write.\n    \"\"\"\n    if not isinstance(name, str):\n        raise UnsafePathComponentError(\n            f\"Refusing to write a non-string {label}: {name!r}\"\n        )\n\n    raw_basename = PureWindowsPath(name).name\n    if not raw_basename or not raw_basename.strip() or set(raw_basename) == {\".\"}:\n        raise UnsafePathComponentError(\n            f\"Path traversal detected: {label} {name!r} leaves no usable \"\n            \"basename to write to.\"\n        )\n    if \"\\x00\" in raw_basename:\n        raise UnsafePathComponentError(\n            f\"Refusing to write {label} containing a NUL byte: {name!r}\"\n        )\n    if any(ord(char) < 32 or char in '<>:\"|?*' for char in raw_basename):\n        raise UnsafePathComponentError(\n            f\"Refusing to write {label} containing characters reserved by \"\n            f\"Windows: {name!r}\"\n        )","sourceCodeStart":148,"sourceCodeEnd":184,"githubUrl":"https://github.com/ComposioHQ/composio/blob/64b1b85502b1beeb2379e6c9e8bf1104504fa637/python/composio/utils/safe_path.py#L148-L184","documentation":"safe_basename refuses to derive a writable filename from a non-string value (None, int, bytes). The filename being sanitized must be a str before basename extraction and validation can proceed.","triggerScenarios":"secure_basename_join(base, name) where name is None (missing Content-Disposition/filename field) or a non-string type from deserialized JSON.","commonSituations":"Download responses missing a filename header; API fields absent rather than empty; strongly-typed IDs passed unconverted.","solutions":["Default to a generated filename when the source field is missing (e.g. f'download-{uuid4().hex}')","Validate the field exists before calling the download/save API","Coerce with str() only when the value is genuinely the filename"],"exampleFix":"# before\nsecure_basename_join(base, resp.filename)  # None\n# after\nimport uuid\nname = resp.filename or f\"download-{uuid.uuid4().hex}\"\nsecure_basename_join(base, name)","handlingStrategy":"type-guard","validationCode":"def has_filename(v):\n    return isinstance(v, str) and bool(v.strip())","typeGuard":"def is_string_filename(v) -> bool:\n    return isinstance(v, str)","tryCatchPattern":"from composio.exceptions import UnsafePathComponentError\nfrom uuid import uuid4\ntry:\n    p = secure_basename_join(base, name)\nexcept UnsafePathComponentError:\n    p = secure_basename_join(base, f'download-{uuid4().hex}')","preventionTips":["Default missing filename fields to generated names before download","Type-check deserialized filename fields"],"tags":["path-validation","python","security"],"backgroundTag":"invalid-filename-type","analyzedSha":"64b1b85502b1beeb2379e6c9e8bf1104504fa637","analyzedAt":"2026-08-28T15:39:33.623Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}