{"record":{"id":"a88232acd2204ecd","repo":"Hmbown/CodeWhale","slug":"plugin-directory-is-a-reparse-point-or-non-directo","errorCode":null,"errorMessage":"plugin directory is a reparse point or non-directory","messagePattern":"plugin directory is a reparse point or non-directory","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/plugins/manifest.rs","lineNumber":1599,"sourceCode":"#[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()\n        .read(true)\n        .share_mode(0x0000_0001)\n        .custom_flags(0x0220_0000) // BACKUP_SEMANTICS | OPEN_REPARSE_POINT\n        .open(path)?;\n    let metadata = file.metadata()?;\n    let identity = windows_file_identity(&file)?;\n    if !metadata.is_dir() || identity.attributes & FILE_ATTRIBUTE_REPARSE_POINT != 0 {\n        return Err(std::io::Error::new(\n            std::io::ErrorKind::InvalidData,\n            \"plugin directory is a reparse point or non-directory\",\n        ));\n    }\n    Ok(file)\n}\n\n/// Reopen a reviewed path only to compare its current handle identity with a\n/// retained authority handle. Desired access is zero and sharing is permissive\n/// because the retained handle remains responsible for denying writes and\n/// replacement throughout the comparison.\n#[cfg(windows)]\npub(crate) fn open_bundle_identity_probe(\n    path: &Path,\n    expect_directory: bool,\n) -> std::io::Result<fs::File> {\n    use std::os::windows::fs::OpenOptionsExt as _;\n","sourceCodeStart":1581,"sourceCodeEnd":1617,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/plugins/manifest.rs#L1581-L1617","documentation":"The plugin loader opens a plugin directory on Windows with BACKUP_SEMANTICS | OPEN_REPARSE_POINT flags and verifies it is truly a directory and not a reparse point (symlink/junction/mount point). It throws InvalidData when the directory is a link or not a directory, to prevent plugin discovery from following links outside the plugin root.","triggerScenarios":"Plugin directory path (crates/tui/src/plugins/manifest.rs:1599) is a junction, directory symlink, or mount point (FILE_ATTRIBUTE_REPARSE_POINT), or metadata().is_dir() is false (file passed as directory).","commonSituations":"Plugins directory managed as a junction to another drive; directory symlink from a package manager or dotfile manager; passing a file path where the plugin directory is expected.","solutions":["Replace the junction/symlinked plugin directory with a real directory containing the plugin files.","Move the actual plugin directory to the expected location instead of linking it.","Verify the configured plugin directory path refers to a real directory, not a file."],"exampleFix":"// before (cmd.exe)\nmklink /J plugins\\my-plugin D:\\shared\\my-plugin\n// after\nrobocopy D:\\shared\\my-plugin plugins\\my-plugin /E  // real directory","handlingStrategy":"validation","validationCode":"let md = std::fs::metadata(&dir)?;\nassert!(md.is_dir(), \"plugin directory must be a real directory\");\nassert!(md.file_type().is_symlink() == false, \"plugin directory must not be a symlink/junction\");","typeGuard":"fn is_plain_dir(md: &std::fs::Metadata) -> bool { md.is_dir() && !md.file_type().is_symlink() }","tryCatchPattern":"if let Err(e) = load_plugins(&dir) { if e.kind() == std::io::ErrorKind::InvalidData { eprintln!(\"replace {} with a real directory\", dir.display()); } }","preventionTips":["Do not use mklink /J for the plugins folder; use a real directory on the target drive","Exclude the plugins directory from tools that create reparse placeholders (OneDrive Files On-Demand)","Verify path kind with fs::metadata before configuring it as the plugin dir"],"tags":["windows","filesystem","junction","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"}