{"record":{"id":"ad3126f4ac20eaad","repo":"BigPizzaV3/CodexPlusPlus","slug":"linked-paths-are-unsupported","errorCode":null,"errorMessage":"Linked paths are unsupported","messagePattern":"Linked paths are unsupported","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/codex-plus-core/src/native_browser.rs","lineNumber":113,"sourceCode":"    candidate_sha: String,\n    modified_secs: u64,\n    modified_nanos: u32,\n}\n\nfn sha(bytes: &[u8]) -> String {\n    format!(\"{:x}\", Sha256::digest(bytes))\n}\n\nfn key_valid(key: &str) -> bool {\n    key.len() == 16 && key.bytes().all(|b| b.is_ascii_hexdigit())\n}\n\n// Reject junctions as well as symlinks, including in parent directories.\nfn plain_path(path: &Path) -> Result<()> {\n    ensure!(path.is_absolute(), \"Expected an absolute local path\");\n    for ancestor in path.ancestors() {\n        if let Ok(meta) = fs::symlink_metadata(ancestor) {\n            ensure!(\n                !meta.file_type().is_symlink(),\n                \"Linked paths are unsupported\"\n            );\n            #[cfg(windows)]\n            {\n                use std::os::windows::fs::MetadataExt;\n                ensure!(\n                    meta.file_attributes() & 0x400 == 0,\n                    \"Reparse paths are unsupported\"\n                );\n            }\n        }\n    }\n    ensure!(\n        !path\n            .components()\n            .any(|c| matches!(c, std::path::Component::ParentDir)),\n        \"Parent traversal is unsupported\"","sourceCodeStart":95,"sourceCodeEnd":131,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6/crates/codex-plus-core/src/native_browser.rs#L95-L131","documentation":"plain_path rejects any path that contains a symlink at any ancestor level, checked with fs::symlink_metadata. Native browser isolation assumes real on-disk directories; links (including Windows junctions, which report as symlinks here) could redirect writes outside the sandbox, so they are refused with 'Linked paths are unsupported'.","triggerScenarios":"pin_parents, selected_key, discover, prepare, restore_all, or reconcile_contract given a path under a directory that is itself a symlink or junction — e.g. a runtime root that is a link, or any parent directory in the chain.","commonSituations":"Users symlink ~/.localappdata-like folders to another drive; Windows junctions created to move large browser caches to another volume; dotfile managers symlinking config directories; macOS/Linux symlinked homes.","solutions":["Replace the symlink/junction with a real directory, or move the data and update config to point at the real (link-free) path","Point the native browser root at a path with no links in any ancestor (e.g. C:\\\\codex-browser\\\\...)","On Windows prefer junction-free relocation via configuration instead of filesystem links","Detect the condition proactively: walk ancestors and check symlink_metadata before configuring the root"],"exampleFix":"// before: runtime root is a junction to D:\\caches\\browser\nruntime_root = \"C:\\\\Users\\\\me\\\\AppData\\\\Local\\\\browser-link\"\n// after: point directly at the real directory\nruntime_root = \"D:\\\\caches\\\\browser\"","handlingStrategy":"validation","validationCode":"fn has_symlink_ancestor(p: &Path) -> bool {\n    p.ancestors().any(|a| fs::symlink_metadata(a).map(|m| m.file_type().is_symlink()).unwrap_or(false))\n}","typeGuard":"fn is_plain_path(p: &Path) -> bool {\n    p.is_absolute() && !has_symlink_ancestor(p)\n}","tryCatchPattern":"match plain_path(p) {\n    Err(e) if e.to_string().contains(\"Linked paths\") => {\n        eprintln!(\"{} (or a parent) is a symlink/junction; use the real directory\", p.display());\n    }\n    other => other?,\n}","preventionTips":["Avoid symlinked or junctioned directories for browser roots/caches","Use configuration-based relocation instead of filesystem links","Check symlink_metadata along ancestors before configuring roots"],"tags":["rust","symlink","path-safety"],"backgroundTag":"path-traversal-blocked","analyzedSha":"b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6","analyzedAt":"2026-09-19T23:35:21.129Z","contentChangedAt":"2026-09-19T23:35:21.129Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}