{"record":{"id":"01c1aa096cf5f8b7","repo":"Hmbown/CodeWhale","slug":"plugin-file-is-a-reparse-point-hard-link-or-non","errorCode":null,"errorMessage":"plugin file is a reparse point, hard link, or non-regular file","messagePattern":"plugin file is a reparse point, hard link, or non-regular file","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/plugins/manifest.rs","lineNumber":1573,"sourceCode":"}\n\n#[cfg(windows)]\npub(crate) fn open_bundle_file(path: &Path) -> std::io::Result<fs::File> {\n    use std::os::windows::fs::OpenOptionsExt as _;\n\n    const FILE_ATTRIBUTE_REPARSE_POINT: u32 = 0x0000_0400;\n    let file = fs::OpenOptions::new()\n        .read(true)\n        .share_mode(0x0000_0001) // deny concurrent writes and replacement\n        .custom_flags(0x0020_0000) // FILE_FLAG_OPEN_REPARSE_POINT\n        .open(path)?;\n    let metadata = file.metadata()?;\n    let identity = windows_file_identity(&file)?;\n    if !metadata.is_file()\n        || identity.attributes & FILE_ATTRIBUTE_REPARSE_POINT != 0\n        || identity.links != 1\n    {\n        return Err(std::io::Error::new(\n            std::io::ErrorKind::InvalidData,\n            \"plugin file is a reparse point, hard link, or non-regular file\",\n        ));\n    }\n    Ok(file)\n}\n\n#[cfg(all(not(unix), not(windows)))]\npub(crate) fn open_bundle_file(path: &Path) -> std::io::Result<fs::File> {\n    fs::File::open(path)\n}\n\n#[cfg(windows)]\nfn open_bundle_directory(path: &Path) -> std::io::Result<fs::File> {\n    use std::os::windows::fs::OpenOptionsExt as _;\n\n    const FILE_ATTRIBUTE_REPARSE_POINT: u32 = 0x0000_0400;\n    let file = fs::OpenOptions::new()","sourceCodeStart":1555,"sourceCodeEnd":1591,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/plugins/manifest.rs#L1555-L1591","documentation":"On Windows, the plugin loader validates that a plugin file is an ordinary regular file: a real file (not a symlink/junction/reparse point), with exactly one hard link, and not a device or directory. It opens the file and inspects the NTFS file identity (attributes and hard-link count) before accepting it. This guards plugin loading from path-traversal tricks via symlinks or junctions.","triggerScenarios":"Calling the plugin file open/validation path (crates/tui/src/plugins/manifest.rs:1573) on Windows when the plugin file is a symlink or junction (FILE_ATTRIBUTE_REPARSE_POINT set), has hard-link count > 1, or metadata().is_file() is false (directory, device, pipe).","commonSituations":"Plugin installed as a symlink into a dotfiles-managed directory; plugin file hard-linked from a shared cache; plugin path accidentally pointing at a directory; syncing tools (Dropbox, OneDrive) creating reparse placeholders.","solutions":["Replace the symlink/hard link with a real copy of the plugin file at the plugin path.","Remove the reparse point (junction/symlink) and copy the target contents in place.","Point the plugin manifest at the real file location instead of a linked path.","On non-Windows systems this check is compiled out; if on Linux/macOS, verify you are not running a Windows-specific validation path."],"exampleFix":"// before\nln -s ~/dotfiles/my-plugin ~/codewhale/plugins/my-plugin\n// after\ncp ~/dotfiles/my-plugin ~/codewhale/plugins/my-plugin  // real copy, links == 1","handlingStrategy":"validation","validationCode":"// Windows: before handing the plugin path to the loader\nlet md = std::fs::metadata(&path)?;\nassert!(md.is_file(), \"plugin path must be a regular file\");\nassert!(md.file_type().is_symlink() == false, \"plugin path must not be a symlink\");","typeGuard":"fn is_plain_file(md: &std::fs::Metadata) -> bool { md.is_file() && !md.file_type().is_symlink() }","tryCatchPattern":"match loader_result { Err(e) if e.to_string().contains(\"reparse point, hard link\") => install_real_copy(&path)?, other => other?, }","preventionTips":["Never symlink or junction plugin directories managed by dotfile tools; copy instead","Avoid hard-link deduplicators (e.g. jdupes -L) on plugin directories","Document that plugin paths must be plain real files on Windows"],"tags":["windows","filesystem","symlink","plugin-loading"],"backgroundTag":"path-traversal-blocked","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}