{"record":{"id":"7418398588a6c339","repo":"astrid-runtime/astrid","slug":"private-file-has-no-parent","errorCode":null,"errorMessage":"private file has no parent: {}","messagePattern":"private file has no parent: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-core/src/platform_fs.rs","lineNumber":636,"sourceCode":"    }\n}\n\n#[cfg(target_os = \"macos\")]\nfn absolute_command_path(path: &Path) -> io::Result<PathBuf> {\n    if path.is_absolute() {\n        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)]","sourceCodeStart":618,"sourceCodeEnd":654,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-core/src/platform_fs.rs#L618-L654","documentation":"open_file_no_follow_unix needs both a parent directory and a final file name to open the file relative to its parent via openat (for symlink protection). It throws this when path.parent() returns None, which happens for bare-root paths like \"/\" that cannot name a file.","triggerScenarios":"Calling restrict_private_file or validate_private_file with a Path that has no parent component, such as \"/\" or an empty/normalized root path.","commonSituations":"A config variable that should hold a file path accidentally holds a mount root or \"/\"; path-joining code that stripped all components; programmatic construction passing Path::new(\"\").","solutions":["Pass the full path to the file including its parent directory, not \"/\" or a bare root.","Check the configured value; assign the concrete file name (e.g. /home/me/.astrid/credentials).","Guard caller code to reject paths without a parent before invoking the API.","If the value comes from user input or env, validate it names a file before use."],"exampleFix":"// before\nrestrict_private_file(Path::new(\"/\"))?;\n// after\nrestrict_private_file(&Path::new(\"/home/me/.astrid\").join(\"credentials\"))?;","handlingStrategy":"validation","validationCode":"fn has_parent(path: &std::path::Path) -> bool { path.parent().is_some() }","typeGuard":"fn is_file_path_with_parent(path: &std::path::Path) -> bool {\n    path.parent().is_some() && path.file_name().is_some()\n}","tryCatchPattern":"assert!(path.parent().is_some(), \"private path must include a parent: {}\", path.display());\nrestrict_private_file(path)?;","preventionTips":["Never pass \"/\" or empty paths as file arguments","Validate configured file paths at startup","Build paths as parent.join(file_name)"],"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"}