{"record":{"id":"57154bec3f23c4cb","repo":"libnyanpasu/clash-nyanpasu","slug":"journal-source-is-not-a-regular-file","errorCode":null,"errorMessage":"journal source is not a regular file: {}","messagePattern":"journal source is not a regular file: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/tauri/src/service/profile_file.rs","lineNumber":1179,"sourceCode":"            if duplicate_journal != journal {\n                bail!(\n                    \"materialization operation has conflicting journal payloads in {duplicate_location:?}\"\n                );\n            }\n            Self::remove_private_regular(&duplicate_path)?;\n        }\n        Ok(Some((location, journal)))\n    }\n\n    /// The journal locations share one private root, so Unix `rename` is an\n    /// atomic same-filesystem phase transition. Do not use `move_atomic`: its\n    /// hard-link/unlink fallback can leave duplicate phase artifacts.\n    #[allow(dead_code)]\n    fn rename_journal_same_filesystem(source: &Path, destination: &Path) -> anyhow::Result<()> {\n        let metadata = std::fs::symlink_metadata(source)\n            .with_context(|| format!(\"inspect journal source {}\", source.display()))?;\n        if is_symlink_or_reparse(&metadata) || !metadata.is_file() {\n            bail!(\"journal source is not a regular file: {}\", source.display());\n        }\n        std::fs::rename(source, destination).with_context(|| {\n            format!(\n                \"atomically rename journal {} -> {}\",\n                source.display(),\n                destination.display()\n            )\n        })?;\n        sync_directory(source.parent().expect(\"journal source has parent\"))?;\n        if source.parent() != destination.parent() {\n            sync_directory(\n                destination\n                    .parent()\n                    .expect(\"journal destination has parent\"),\n            )?;\n        }\n        Ok(())\n    }","sourceCodeStart":1161,"sourceCodeEnd":1197,"githubUrl":"https://github.com/libnyanpasu/clash-nyanpasu/blob/f7dbce2997c633e484f54788035e770b3ee99773/backend/tauri/src/service/profile_file.rs#L1161-L1197","documentation":"rename_journal_same_filesystem inspects the journal source with symlink_metadata and rejects any node that is a symlink/reparse point or not a regular file before renaming it. Like the journal-read check, this defends the atomic rename protocol against following attacker-controlled links or renaming directories/special nodes into journal positions.","triggerScenarios":"rename_journal_same_filesystem called with a source path that is a symlink, directory, or device node — e.g. a planted symlink at the journal location in a shared directory, or a caller passing a wrong path that happens to exist as a directory.","commonSituations":"Untrusted processes writing into a world-writable staging root; refactored callers passing directory paths instead of journal file paths; filesystems materializing links as other node types.","solutions":["Verify the source path passed in is the actual journal file path, not a parent directory or link.","Remove the non-regular node (after confirming it is not legitimate) and recreate the journal via the normal write path.","Restrict staging-root permissions so only the app user can create nodes there.","Note: this function is currently #[allow(dead_code)]; if you hit this in tests or new call sites, ensure callers validate node type before invoking."],"exampleFix":"// before\nrename_journal_same_filesystem(&dir_path, &dest)?; // dir passed, not file\n// after\nlet meta = std::fs::symlink_metadata(&journal_file)?;\nassert!(meta.is_file(), \"journal source must be a regular file\");\nrename_journal_same_filesystem(&journal_file, &dest)?;","handlingStrategy":"validation","validationCode":"let meta = std::fs::symlink_metadata(&source)?;\nif meta.file_type().is_symlink() || !meta.is_file() {\n    bail!(\"refusing to rename non-regular journal source: {}\", source.display());\n}","typeGuard":"fn is_regular_file_node(p: &Path) -> bool {\n    std::fs::symlink_metadata(p).map(|m| m.is_file()).unwrap_or(false)\n}","tryCatchPattern":"match rename_journal_same_filesystem(&src, &dst) {\n    Err(e) if e.to_string().contains(\"not a regular file\") => {\n        // inspect the node; remove if it's not legitimate, then recreate journal\n        bail!(\"journal source invalid, operation must be redone\")\n    }\n    r => r,\n}","preventionTips":["Pass exact journal file paths, never directories or links.","Keep the journal directory user-private to block symlink planting.","Recreate journals only through the library's write path.","If adding call sites to this (currently dead-code) helper, validate node type upstream first."],"tags":["filesystem","security","journal"],"backgroundTag":"path-traversal-blocked","analyzedSha":"f7dbce2997c633e484f54788035e770b3ee99773","analyzedAt":"2026-09-08T01:24:59.197Z","contentChangedAt":"2026-09-08T01:24:59.197Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}