{"record":{"id":"38c0dcac8963fc11","repo":"libnyanpasu/clash-nyanpasu","slug":"materialization-journal-targets-reserved-private-s","errorCode":null,"errorMessage":"materialization journal targets reserved private storage","messagePattern":"materialization journal targets reserved private storage","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/tauri/src/service/profile_file.rs","lineNumber":630,"sourceCode":"            bail!(\n                \"materialization journal is not a regular file: {}\",\n                path.display()\n            );\n        }\n        let content = std::fs::read_to_string(path)\n            .with_context(|| format!(\"read materialization journal {}\", path.display()))?;\n        let journal: MaterializationJournal = serde_yaml::from_str(&content)\n            .with_context(|| format!(\"parse materialization journal {}\", path.display()))?;\n        if journal.operation_id != operation_id || !valid_operation_id(&journal.operation_id) {\n            bail!(\"materialization journal operation id mismatch\");\n        }\n        if journal\n            .managed_path\n            .as_path()\n            .components()\n            .any(|component| is_materialization_root_name(component.as_os_str()))\n        {\n            bail!(\"materialization journal targets reserved private storage\");\n        }\n        if journal.hash.len() != 64 || !journal.hash.bytes().all(|byte| byte.is_ascii_hexdigit()) {\n            bail!(\"materialization journal hash is invalid\");\n        }\n        Ok(journal)\n    }\n\n    fn remove_nofollow(path: &Path) -> anyhow::Result<()> {\n        match std::fs::symlink_metadata(path) {\n            Ok(metadata) if metadata.is_dir() && !is_symlink_or_reparse(&metadata) => {\n                bail!(\n                    \"refusing to remove directory as a profile resource: {}\",\n                    path.display()\n                )\n            }\n            Ok(_) => {\n                std::fs::remove_file(path)\n                    .with_context(|| format!(\"remove profile resource {}\", path.display()))?;","sourceCodeStart":612,"sourceCodeEnd":648,"githubUrl":"https://github.com/libnyanpasu/clash-nyanpasu/blob/f7dbce2997c633e484f54788035e770b3ee99773/backend/tauri/src/service/profile_file.rs#L612-L648","documentation":"A parsed materialization journal's `managed_path` is checked component-by-component against `is_materialization_root_name`; if any path component names a reserved private storage directory (staging, backup, cleanup, etc.), the journal is rejected with this error. This prevents a malicious or corrupted journal from directing materialization logic to modify the library's own private state area — a path-traversal style protection.","triggerScenarios":"`read_journal` parses a journal whose `managed_path` contains a reserved root name as any component, e.g. `managed_path: staging/profiles/config.yaml` or `.../backup/foo`. Produced by hand-crafted or tampered journal YAML, or a bug writing journals with private paths.","commonSituations":"Manually edited journal files pointing managed paths into the materialization root; an attacker-supplied or sync-corrupted journal; misconfigured relative paths that accidentally resolve under the private root.","solutions":["Remove or fix the journal file so `managed_path` points at the real managed profile location, outside the materialization root","Never hand-write journals; let the application create them","If tampering is suspected, wipe the materialization directory and re-materialize profiles from sources","Keep the materialization root not writable by untrusted users/processes"],"exampleFix":"// before (journal YAML)\n// managed_path: staging/profiles/config.yaml\nmanaged_path: profiles/config.yaml\n// after: managed path outside reserved roots (staging/, backup/, cleanup/)","handlingStrategy":"validation","validationCode":"fn targets_reserved(p: &std::path::Path, reserved: &[&str]) -> bool {\n    p.components().any(|c| {\n        let s = c.as_os_str().to_string_lossy();\n        reserved.iter().any(|r| s.eq_ignore_ascii_case(r))\n    })\n}\n// reject any candidate managed_path where targets_reserved(...) is true before writing a journal","typeGuard":"fn safe_managed_path(p: &std::path::Path, reserved: &[&str]) -> bool {\n    !targets_reserved(p, reserved)\n}","tryCatchPattern":"match read_journal(&path, &op_id) {\n    Err(e) if e.to_string().contains(\"reserved private storage\") => {\n        // journal is hostile/corrupt: quarantine and re-materialize\n        let _ = std::fs::remove_file(&path);\n    }\n    Err(e) => return Err(e),\n    Ok(j) => use_journal(j),\n}","preventionTips":["Never hand-edit journal YAML","Keep managed paths outside the materialization root","Restrict write access to the materialization directory","When constructing managed paths, resolve and verify no component equals reserved root names"],"tags":["security","path-traversal","filesystem"],"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"}