{"record":{"id":"c9261503ea045260","repo":"astrid-runtime/astrid","slug":"private-file-has-no-name","errorCode":null,"errorMessage":"private file has no name: {}","messagePattern":"private file has no name: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-core/src/platform_fs.rs","lineNumber":642,"sourceCode":"        Ok(path.to_path_buf())\n    } else {\n        Ok(std::env::current_dir()?.join(path))\n    }\n}\n\n#[cfg(unix)]\nfn open_file_no_follow_unix(path: &Path) -> io::Result<std::fs::File> {\n    use nix::fcntl::{OFlag, openat};\n    use nix::sys::stat::Mode;\n\n    let parent = path.parent().ok_or_else(|| {\n        io::Error::new(\n            io::ErrorKind::InvalidInput,\n            format!(\"private file has no parent: {}\", path.display()),\n        )\n    })?;\n    let name = path.file_name().ok_or_else(|| {\n        io::Error::new(\n            io::ErrorKind::InvalidInput,\n            format!(\"private file has no name: {}\", path.display()),\n        )\n    })?;\n    let directory = open_directory_no_follow_unix(parent)?;\n    let flags = OFlag::O_RDONLY | OFlag::O_NOFOLLOW | OFlag::O_CLOEXEC;\n    openat(&directory, name, flags, Mode::from_bits_truncate(0o600))\n        .map(std::fs::File::from)\n        .map_err(nix_io_error)\n}\n\n#[cfg(unix)]\nfn atomic_write_private_file_unix(path: &Path, bytes: &[u8]) -> io::Result<()> {\n    use std::io::Write as _;\n    use std::os::unix::fs::OpenOptionsExt as _;\n\n    let parent = path.parent().ok_or_else(|| {\n        io::Error::new(","sourceCodeStart":624,"sourceCodeEnd":660,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-core/src/platform_fs.rs#L624-L660","documentation":"Alongside a parent, open_file_no_follow_unix requires a final file name component to perform the openat call. It throws this when path.file_name() returns None — paths that end in \"..\", \".\", or are root — because there is no concrete file to open.","triggerScenarios":"Calling restrict_private_file or validate_private_file with a path ending in \"..\", \".\", or a root path, so file_name() yields None.","commonSituations":"Joining or normalizing logic that produced \"dir/..\"; a caller passing the parent directory instead of the file; paths built by string concatenation ending with a trailing separator that normalizes to \".\".","solutions":["Pass the exact file path, not a directory or a path ending in \"..\" or \".\".","Normalize the path before calling (e.g. path.canonicalize() then append the file name).","Add a caller-side check: `path.file_name().is_some()` before invoking the API.","Fix any string-concatenation code that appends separators or \"..\" fragments."],"exampleFix":"// before\nrestrict_private_file(Path::new(\"/home/me/.astrid/..\"))?;\n// after\nrestrict_private_file(Path::new(\"/home/me/.astrid/credentials\"))?;","handlingStrategy":"validation","validationCode":"fn names_a_file(path: &std::path::Path) -> bool {\n    path.file_name().is_some()\n        && !matches!(path.components().next_back(), Some(std::path::Component::ParentDir) | Some(std::path::Component::CurDir))\n}","typeGuard":"fn has_file_name(path: &std::path::Path) -> bool { path.file_name().is_some() }","tryCatchPattern":"if path.file_name().is_none() {\n    return Err(anyhow!(\"path must end in a file name: {}\", path.display()));\n}\nvalidate_private_file(path)?;","preventionTips":["Avoid paths ending in \"..\" or \".\"","Canonicalize computed paths before passing them","Do not pass a directory where a file path is required"],"tags":["filesystem","path","invalid-argument"],"backgroundTag":"invalid-argument-value","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}