{"record":{"id":"d45d0376a6fb9d74","repo":"libnyanpasu/clash-nyanpasu","slug":"runtime-candidate-directory-is-a-symlink-or-repars","errorCode":null,"errorMessage":"runtime candidate directory is a symlink or reparse point: {path}","messagePattern":"runtime candidate directory is a symlink or reparse point: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/tauri/src/client/runtime.rs","lineNumber":270,"sourceCode":"            }\n            Err(error) => Err(error.into()),\n        }\n    }\n}\n\nimpl Drop for CandidateFile {\n    fn drop(&mut self) {\n        if !self.cleaned {\n            let _ = std::fs::remove_file(&self.path);\n        }\n    }\n}\n\nasync fn prepare_private_dir(path: &Utf8Path) -> anyhow::Result<()> {\n    if let Ok(metadata) = tokio::fs::symlink_metadata(path).await\n        && is_symlink_or_reparse(&metadata)\n    {\n        anyhow::bail!(\"runtime candidate directory is a symlink or reparse point: {path}\");\n    }\n    tokio::fs::create_dir_all(path).await?;\n    let metadata = tokio::fs::symlink_metadata(path).await?;\n    if is_symlink_or_reparse(&metadata) || !metadata.is_dir() {\n        anyhow::bail!(\"runtime candidate path is not a private directory: {path}\");\n    }\n    #[cfg(unix)]\n    {\n        use std::os::unix::fs::PermissionsExt;\n        tokio::fs::set_permissions(path, std::fs::Permissions::from_mode(0o700)).await?;\n    }\n    Ok(())\n}\n\n#[cfg(unix)]\nfn is_symlink_or_reparse(metadata: &std::fs::Metadata) -> bool {\n    metadata.file_type().is_symlink()\n}","sourceCodeStart":252,"sourceCodeEnd":288,"githubUrl":"https://github.com/libnyanpasu/clash-nyanpasu/blob/f7dbce2997c633e484f54788035e770b3ee99773/backend/tauri/src/client/runtime.rs#L252-L288","documentation":"Raised by prepare_private_dir (backend/tauri/src/client/runtime.rs:270) when symlink_metadata of the runtime candidate directory reports it is a symlink (or Windows reparse point). The store refuses to write private candidate files into a symlinked location because a symlink could redirect sensitive runtime files elsewhere or be swapped mid-operation.","triggerScenarios":"create_candidate_with_names, cleanup_stale_candidates, or candidate_collision_retries_with_exclusive_create calls prepare_private_dir while candidate_dir itself is a symlink or reparse point (checked before create_dir_all).","commonSituations":"User or packaging replaced the app data directory with a symlink to another disk; portable installs with linked config dirs; Windows junction/reparse points created by sync tools (Dropbox, OneDrive).","solutions":["Remove the symlink at the candidate path and let the app create a real directory (passing the real target path explicitly if needed).","Point the candidate directory configuration at the physical path instead of the symlink.","On Windows, replace junctions/reparse points created by cloud-sync tools with real directories excluded from syncing."],"exampleFix":"// before (path is a symlink)\nlet store = RuntimeStore::new(Utf8PathBuf::from(\"/app/data/candidates-linked\"));\n// after (real directory)\nstd::fs::remove_file(\"/app/data/candidates-linked\")?;\nstd::fs::create_dir(\"/app/data/candidates\")?;\nlet store = RuntimeStore::new(Utf8PathBuf::from(\"/app/data/candidates\"));","handlingStrategy":"validation","validationCode":"use std::os::unix::fs::MetadataExt;\nlet md = std::fs::symlink_metadata(&candidate_path)?;\nif md.file_type().is_symlink() {\n    return Err(anyhow::anyhow!(\"candidate dir must not be a symlink\"));\n}\nif !md.is_dir() {\n    return Err(anyhow::anyhow!(\"candidate path is not a directory\"));\n}","typeGuard":"fn is_plain_dir(path: &Utf8Path) -> bool {\n    std::fs::symlink_metadata(path.as_std_path())\n        .map(|md| md.is_dir() && !md.file_type().is_symlink())\n        .unwrap_or(false)\n}","tryCatchPattern":"match store.create_candidate(&bytes).await {\n    Ok(c) => c,\n    Err(e) if e.to_string().contains(\"symlink or reparse point\") => {\n        // refuse to proceed; surface a configuration/integrity error to the user\n        return Err(IntegrityError::SymlinkedCandidateDir.into());\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Never symlink the app data / candidate directory; configure the real physical path instead","Exclude the data directory from cloud-sync tools that create reparse points (Dropbox, OneDrive)","Check install scripts and packaging for ln -s or junction creation on data dirs","Validate the data dir layout at startup and fail fast with a clear message"],"tags":["filesystem","symlink","security","rust"],"backgroundTag":"path-is-not-a-directory","analyzedSha":"f7dbce2997c633e484f54788035e770b3ee99773","analyzedAt":"2026-09-08T01:24:59.197Z","contentChangedAt":"2026-09-08T01:24:59.197Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}