{"record":{"id":"8474c54ae7d65351","repo":"ComposioHQ/composio","slug":"refusing-to-write-label-that-is-a-reserved-devic","errorCode":null,"errorMessage":"Refusing to write {label} that is a reserved device name: {name!r}","messagePattern":"Refusing to write (.+?) that is a reserved device name: (.+?)","errorType":"exception","errorClass":"UnsafePathComponentError","httpStatus":null,"severity":"critical","filePath":"python/composio/utils/safe_path.py","lineNumber":207,"sourceCode":"\n    basename = raw_basename.strip()\n    try:\n        encoded_length = len(os.fsencode(basename))\n    except UnicodeEncodeError as e:\n        raise UnsafePathComponentError(\n            f\"Refusing to write {label} containing invalid Unicode: {name!r}\"\n        ) from e\n    if encoded_length > MAX_COMPONENT_LENGTH:\n        raise UnsafePathComponentError(\n            f\"Refusing to write {label} longer than {MAX_COMPONENT_LENGTH} bytes: \"\n            f\"{basename[:32]!r}... ({encoded_length} bytes)\"\n        )\n    # Compare everything before the first dot: on Windows `NUL.tar.gz` opens\n    # the null device just as `NUL` does, so any number of extensions provides\n    # no protection.\n    device_name = basename.split(\".\", 1)[0].rstrip(\" \").upper()\n    if device_name in WINDOWS_RESERVED_NAMES:\n        raise UnsafePathComponentError(\n            f\"Refusing to write {label} that is a reserved device name: {name!r}\"\n        )\n    return basename\n\n\ndef resolve_root(root: t.Union[str, Path]) -> Path:\n    \"\"\"Normalize a trusted root to an absolute, symlink-resolved path.\n\n    Every containment check must derive its anchor through this one function.\n    When two call sites normalize differently — one expanding ``~`` and one not\n    — the check compares mismatched paths and rejects legitimate writes while\n    reporting them as attacks. Sharing the normalization is what keeps the two\n    ends of a containment check comparable.\n    \"\"\"\n    expanded = Path(root).expanduser()\n    try:\n        return expanded.resolve(strict=False)\n    except OSError:","sourceCodeStart":189,"sourceCodeEnd":225,"githubUrl":"https://github.com/ComposioHQ/composio/blob/64b1b85502b1beeb2379e6c9e8bf1104504fa637/python/composio/utils/safe_path.py#L189-L225","documentation":"safe_basename rejects basenames whose stem before the first dot (trailing spaces stripped, uppercased) is a Windows reserved device name — e.g. 'NUL.tar.gz' or 'com1.txt' — because Windows opens the device, not a file, no matter how many extensions follow.","triggerScenarios":"secure_basename_join(base, 'NUL.tar.gz'), (base, 'COM1.json'), (base, 'aux .bak') — any filename whose first dot-delimited segment matches a reserved device name.","commonSituations":"Downloads named after devices (logs named 'prn.log', ports named 'com1.csv'); crafted filenames targeting Windows device files.","solutions":["Prefix the filename (e.g. 'download-NUL.tar.gz') so the stem no longer matches","Reject the untrusted download carrying the device name"],"exampleFix":"# before\nsecure_basename_join(base, 'nul.tar.gz')\n# after\nsecure_basename_join(base, 'download-nul.tar.gz')","handlingStrategy":"validation","validationCode":"from composio.utils.safe_path import WINDOWS_RESERVED_NAMES\ndef stem_not_device(v):\n    return PureWindowsPath(v).name.split('.', 1)[0].rstrip(' ').upper() not in WINDOWS_RESERVED_NAMES","typeGuard":"from composio.utils.safe_path import WINDOWS_RESERVED_NAMES\ndef is_not_device_filename(v: str) -> bool:\n    return v.split('.', 1)[0].rstrip(' ').upper() not in WINDOWS_RESERVED_NAMES","tryCatchPattern":"from composio.exceptions import UnsafePathComponentError\ntry:\n    p = secure_basename_join(base, name)\nexcept UnsafePathComponentError:\n    p = secure_basename_join(base, 'file-' + name)","preventionTips":["Prefix filenames whose stem could match CON/PRN/AUX/NUL/COMn/LPTn","Remember extensions don't help: 'NUL.tar.gz' still opens the device"],"tags":["windows","reserved-names","python","security"],"backgroundTag":"windows-reserved-device-name","analyzedSha":"64b1b85502b1beeb2379e6c9e8bf1104504fa637","analyzedAt":"2026-08-28T15:39:33.623Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}