{"record":{"id":"6c73a7ef5ab1fad8","repo":"Hmbown/CodeWhale","slug":"plugin-identity-probe-found-a-reparse-point-hard","errorCode":null,"errorMessage":"plugin identity probe found a reparse point, hard link, or unexpected object","messagePattern":"plugin identity probe found a reparse point, hard link, or unexpected object","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/plugins/manifest.rs","lineNumber":1641,"sourceCode":"        | if expect_directory {\n            FILE_FLAG_BACKUP_SEMANTICS\n        } else {\n            0\n        };\n    let file = fs::OpenOptions::new()\n        .access_mode(0)\n        .share_mode(FILE_SHARE_READ_WRITE_DELETE)\n        .custom_flags(flags)\n        .open(path)?;\n    let metadata = file.metadata()?;\n    let identity = windows_file_identity(&file)?;\n    let expected_kind = if expect_directory {\n        metadata.is_dir()\n    } else {\n        metadata.is_file() && identity.links == 1\n    };\n    if !expected_kind || identity.attributes & FILE_ATTRIBUTE_REPARSE_POINT != 0 {\n        return Err(std::io::Error::new(\n            std::io::ErrorKind::InvalidData,\n            \"plugin identity probe found a reparse point, hard link, or unexpected object\",\n        ));\n    }\n    Ok(file)\n}\n\n#[cfg(windows)]\nfn ensure_windows_path_still_opened(path: &Path, opened: &fs::File) -> Result<(), String> {\n    let after = fs::symlink_metadata(path)\n        .map_err(|e| format!(\"failed to re-inspect plugin path after handle open: {e}\"))?;\n    if metadata_is_link_or_reparse(&after) {\n        return Err(\"plugin path changed into a reparse point during validation\".to_string());\n    }\n    let expect_directory = if after.is_dir() {\n        true\n    } else if after.is_file() {\n        false","sourceCodeStart":1623,"sourceCodeEnd":1659,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/plugins/manifest.rs#L1623-L1659","documentation":"A final identity probe re-checks the opened plugin file or directory: for files it requires is_file() plus a single hard link; for directories just is_dir(). Either kind must not carry the reparse-point attribute. This is the last line of defense against TOCTOU swaps between initial open and use.","triggerScenarios":"Calling the identity probe (crates/tui/src/plugins/manifest.rs:1641) when the file was replaced by a symlink/hard link after the earlier check, links != 1 on a file, wrong object kind (directory where file expected or vice versa), or any reparse-point attribute.","commonSituations":"Concurrent replacement of a plugin file during update (TOCTOU); updater tooling swapping plugin files via rename+link; misconfigured path pointing at the wrong object type.","solutions":["Ensure nothing concurrently replaces plugin files while the app runs; retry after the update finishes.","Install plugins as regular files with a single hard link (no hard-link dedup tools).","Fix the expected kind: point the file entry at a file and the directory entry at a directory."],"exampleFix":"// before: updater hard-links new plugin over old\nfs.link(\"/cache/plugin-v2.dll\", \"/plugins/plugin.dll\")?;\n// after: copy so links stays 1\nfs.copy(\"/cache/plugin-v2.dll\", \"/plugins/plugin.dll\")?;","handlingStrategy":"validation","validationCode":"let md = std::fs::metadata(&path)?;\nlet kind_ok = if expect_dir { md.is_dir() } else { md.is_file() };\nassert!(kind_ok && !md.file_type().is_symlink(), \"plugin path swapped or linked mid-flight\");","typeGuard":"fn probe_ok(md: &std::fs::Metadata, expect_dir: bool) -> bool { if expect_dir { md.is_dir() } else { md.is_file() } && !md.file_type().is_symlink() }","tryCatchPattern":"match probe(&path) { Err(e) if e.to_string().contains(\"identity probe\") => retry_after_plugin_update(), Err(e) => return Err(e), Ok(f) => use(f), }","preventionTips":["Pause plugin auto-updaters while the app is running to avoid TOCTOU swaps","Deploy plugin updates by copy/replace, never by hard links","Verify expected file vs directory kind in the plugin manifest"],"tags":["windows","filesystem","toctou","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"}