{"record":{"id":"2431038b01e56615","repo":"pydantic/monty","slug":"file-name-too-long","errorCode":null,"errorMessage":"File name too long","messagePattern":"File name too long","errorType":"exception","errorClass":"MountError","httpStatus":null,"severity":"warning","filePath":"crates/monty-fs/src/mount_table.rs","lineNumber":113,"sourceCode":"    }\n\n    /// Handles an OS call using the mount table.\n    ///\n    /// Consumes the call so a covered write's payload is *moved* into the\n    /// backend (overlay storage retains it without a copy). Routing happens\n    /// on a borrow first, so [`MountCallOutcome::NotHandled`] hands the call\n    /// back untouched for the caller's fallback handler (a host callback or\n    /// [`OsFunctionCall::on_no_handler`]).\n    ///\n    /// Path length and null bytes are checked before anything else touches the\n    /// path, so both apply whether or not a mount covers it — as in CPython,\n    /// where neither reaches a syscall.\n    pub fn handle_os_call(&mut self, call: OsFunctionCall) -> MountCallOutcome {\n        if let Some(primary_path) = call.fs_primary_path() {\n            // Length first: it is the only check that stays O(1) on a hostile\n            // path, so a null scan must not run ahead of it. A path that is\n            // both reports its length, where CPython reports the null byte.\n            let rejection = reject_overlong_path(primary_path).err().or_else(|| {\n                contains_null_byte(primary_path)\n                    .then(|| MountError::EmbeddedNullByte(call.embedded_null_message(false)))\n            });\n            if let Some(e) = rejection {\n                // Both make CPython's predicates answer `False` rather than\n                // raise — `pathlib` swallows `OSError` and `ValueError` alike.\n                MountCallOutcome::Handled(if call.is_existence_check() {\n                    Ok(MontyObject::Bool(false))\n                } else {\n                    Err(e)\n                })\n            } else {\n                match self.route_call(primary_path, &call) {\n                    Some(Ok(index)) => MountCallOutcome::Handled(self.mounts[index].execute(call)),\n                    Some(Err(err)) => MountCallOutcome::Handled(Err(err)),\n                    None => MountCallOutcome::NotHandled(call),\n                }\n            }","sourceCodeStart":95,"sourceCodeEnd":131,"githubUrl":"https://github.com/pydantic/monty/blob/adc986b362e3961f407868cb118a99fe831b9e61/crates/monty-fs/src/mount_table.rs#L95-L131","documentation":"`MountTable::handle_os_call` rejects OS calls whose primary virtual path exceeds the filesystem's maximum name/path length with a 'File name too long' error (ENAMETOOLONG), matching CPython's OSError for the same condition. The length check runs first and is O(1) so hostile paths cannot make the null-byte scan do unbounded work. Unlike some rejections, this one surfaces as an error rather than a `False` predicate, because CPython raises `OSError` for overlong paths.","triggerScenarios":"Calling any file operation (open, rename, stat, list, mkdir, etc.) through a mount whose virtual path component or full path exceeds the host maximum (commonly NAME_MAX=255 bytes per component or PATH_MAX=4096 total) — e.g. `open('a'*300)` inside the sandbox, or `rename` with an extremely long destination.","commonSituations":"Generated filenames from untrusted input (hashes concatenated, templated names), deep directory trees pushing total path length past PATH_MAX, or rename operations with one side of the path outside the mount being validated first.","solutions":["Shorten the file or directory names your sandboxed code uses (keep components under ~255 bytes and total paths well under 4096).","Truncate or hash long logical names before writing (e.g. store a short digest as the filename and keep the long name in content/metadata).","Restructure deep output trees into shallower directories to reduce total path length."],"exampleFix":"// before\nname = 'log-' + '-'.join(entries)  # hundreds of chars\nopen(name, 'w')\n// after\nimport hashlib\nname = 'log-' + hashlib.sha256('-'.join(entries).encode()).hexdigest()[:32]\nopen(name, 'w')","handlingStrategy":"validation","validationCode":"import os\n\ndef path_fits(path: str, max_total: int = 4096, max_component: int = 255) -> bool:\n    return len(path.encode()) <= max_total and all(\n        len(part.encode()) <= max_component for part in path.split('/')\n    )\n\nif not path_fits(my_virtual_path):\n    raise ValueError('path too long for mounted filesystem')","typeGuard":"def is_safe_mounted_path(path: str) -> bool:\n    return (\n        '\\x00' not in path\n        and len(path.encode()) <= 4096\n        and all(len(p.encode()) <= 255 for p in path.split('/'))\n    )","tryCatchPattern":"try:\n    result = session.feed_run(f\"open({virtual_path!r})\")\nexcept MontyRuntimeError as exc:\n    if 'File name too long' in str(exc):\n        # shorten/hash the filename and retry\n        ...\n    raise","preventionTips":["Hash or truncate generated filenames instead of concatenating long identifiers.","Keep output directory trees shallow; compute the full virtual path length before writing.","Sanitize untrusted input used as filenames (length cap, no null bytes).","When renaming, ensure both source and destination are inside the mount and within length limits."],"tags":["filesystem","path-length","oserror","mounts"],"backgroundTag":"file-name-too-long","analyzedSha":"adc986b362e3961f407868cb118a99fe831b9e61","analyzedAt":"2026-09-13T19:19:18.698Z","contentChangedAt":"2026-09-13T19:19:18.698Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}