{"record":{"id":"5555744bf002c3e5","repo":"libnyanpasu/clash-nyanpasu","slug":"managed-profile-path-uses-reserved-private-storage","errorCode":null,"errorMessage":"managed profile path uses reserved private storage","messagePattern":"managed profile path uses reserved private storage","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/tauri/src/service/profile_file.rs","lineNumber":68,"sourceCode":"            paths,\n            self_proxy_port,\n            http_timeout: Duration::from_secs(30),\n        }\n    }\n\n    #[cfg(test)]\n    fn with_http_timeout(mut self, timeout: Duration) -> Self {\n        self.http_timeout = timeout;\n        self\n    }\n\n    fn resolve(&self, path: &ManagedProfilePath) -> anyhow::Result<PathBuf> {\n        if path\n            .as_path()\n            .components()\n            .any(|component| is_materialization_root_name(component.as_os_str()))\n        {\n            bail!(\"managed profile path uses reserved private storage\");\n        }\n\n        let full = self.paths.app_profiles_dir().join(path.as_path());\n        self.validate_existing_parent_chain(&full)?;\n        Ok(full)\n    }\n\n    fn validate_existing_parent_chain(&self, full: &Path) -> anyhow::Result<()> {\n        let root = self.paths.app_profiles_dir();\n        let relative = full.strip_prefix(&root).with_context(|| {\n            format!(\n                \"profile path containment violation: {} escapes {}\",\n                full.display(),\n                root.display()\n            )\n        })?;\n\n        match std::fs::symlink_metadata(&root) {","sourceCodeStart":50,"sourceCodeEnd":86,"githubUrl":"https://github.com/libnyanpasu/clash-nyanpasu/blob/f7dbce2997c633e484f54788035e770b3ee99773/backend/tauri/src/service/profile_file.rs#L50-L86","documentation":"ManagedProfilePathResolver::resolve joins a user-supplied managed profile path onto the app's private profiles directory. Before joining it rejects any path component that names the reserved private materialization root, because user-managed files must never be mapped into the private storage tree the app owns. This is the lexical (component-name) check that runs before any filesystem access.","triggerScenarios":"Calling read/write_atomic/remove/prepare_materialization (all of which funnel through resolve) with a ManagedProfilePath containing a component equal to the materialization root name, e.g. a path literally named after the private storage directory (such as a dot/underscore-prefixed reserved name) or nested like 'sub/<reserved>/file.yaml'.","commonSituations":"A user naming a profile or folder the same as the app's internal materialization directory; importing a profile list exported from a different app version whose layout used the reserved name; automated scripts deriving profile paths from filesystem listings that included the private root.","solutions":["Rename the offending profile file/directory so no path component matches the reserved private-storage root name","Keep managed profile paths relative and below the profiles directory, avoiding any component that duplicates internal storage names","If you need to inspect materialized content, use the API/read path for the private root rather than routing it through ManagedProfilePath","Check the path components in your code before constructing the ManagedProfilePath to fail fast with a clearer message"],"exampleFix":"// before\nlet path = ManagedProfilePath::new(\".materialized/profile.yaml\")?; // reserved component\n// after\nlet path = ManagedProfilePath::new(\"profiles/profile.yaml\")?;","handlingStrategy":"validation","validationCode":"fn is_reserved_component(name: &std::ffi::OsStr) -> bool { name == RESERVED_ROOT_NAME } // mirror is_materialization_root_name\npub fn check_managed_path(path: &str) -> Result<(), String> {\n    use std::path::Path;\n    if Path::new(path).components().any(|c| is_reserved_component(c.as_os_str())) {\n        return Err(format!(\"path {path} contains reserved private storage component\"));\n    }\n    Ok(())\n}","typeGuard":null,"tryCatchPattern":"match resolver.resolve(&managed_path) {\n    Err(e) if e.to_string().contains(\"reserved private storage\") => {\n        eprintln!(\"pick a path outside the reserved storage root, e.g. rename the offending component\");\n    }\n    other => other?,\n}","preventionTips":["Never name profile files/folders after the app's internal materialization root name","Keep managed paths simple, relative, and below the profiles directory","When importing/exporting profile sets, sanitize component names against the reserved root name","Derive managed paths from app APIs, not from raw listings that may include private storage entries"],"tags":["filesystem","path","validation"],"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"}