{"record":{"id":"ba57d30725114477","repo":"astrid-runtime/astrid","slug":"permissiondenied-ba57d3","errorCode":"PermissionDenied","errorMessage":"trusted Windows path contains a redirect or non-directory component: {}","messagePattern":"trusted Windows path contains a redirect or non-directory component: (.+?)","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-core/src/platform_fs/windows/path.rs","lineNumber":205,"sourceCode":"        let mut components: Vec<LockedPathComponent> = Vec::new();\n        let mut current = PathBuf::new();\n        let mut rooted = false;\n        for component in path.components() {\n            current.push(component.as_os_str());\n            if matches!(component, Component::RootDir) {\n                rooted = true;\n            }\n            if !rooted {\n                continue;\n            }\n            let metadata = match std::fs::symlink_metadata(&current) {\n                Ok(metadata) => metadata,\n                Err(error) if error.kind() == io::ErrorKind::NotFound => continue,\n                Err(error) => return Err(error),\n            };\n            if metadata.file_attributes() & FILE_ATTRIBUTE_REPARSE_POINT != 0 || !metadata.is_dir()\n            {\n                return Err(io::Error::new(\n                    io::ErrorKind::PermissionDenied,\n                    format!(\n                        \"trusted Windows path contains a redirect or non-directory component: {}\",\n                        current.display()\n                    ),\n                ));\n            }\n            let (handle, identity) = if let Some(parent) = components.last() {\n                open_directory_identity_relative(\n                    parent.handle.0,\n                    component.as_os_str(),\n                    current == path,\n                )?\n            } else if current == path {\n                open_locked_directory(&current)?\n            } else {\n                open_directory_identity(&current, true)?\n            };","sourceCodeStart":187,"sourceCodeEnd":223,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-core/src/platform_fs/windows/path.rs#L187-L223","documentation":"This PermissionDenied error is raised by `TrustedPathGuard::capture` while it walks each component of the path with `symlink_metadata`: any component that is a reparse point (symlink, junction, mount point) or not a directory makes the trusted path un-verifiable. The guard pins directory identities by handle so later mutations cannot be redirected; a redirect component in the chain would defeat that guarantee, so capture refuses with the offending component's path in the message.","triggerScenarios":"Capturing a guard for a path where an ancestor (or the boundary itself) is a symlink/junction — e.g. `C:\\Users\\me\\link\\app` where `link` is a junction, or a per-user directory redirected by OneDrive/Dropbox placeholders, or a profile path containing a mounted folder.","commonSituations":"Home directories redirected to OneDrive (placeholder reparse points); junctioned `C:\\Users\\<user>` profiles created by migrations; junction-based workspace setups (`mklink /J`) in development environments; running from a substituted drive (`subst`).","solutions":["Replace the symlink/junction component with a real directory, or capture the guard for the resolved real path.","Resolve the path first (e.g. `std::fs::canonicalize` minus the final symlink) and pass the physical location to capture.","Disable folder redirection (OneDrive Known Folder Move) for the directory used as the authority boundary, or choose a location outside redirected trees.","Audit the printed component path to identify exactly which ancestor is the reparse point."],"exampleFix":"// before\nlet install = home.join(\"MyApp\"); // home is a OneDrive-junctioned path\nlet guard = TrustedPathGuard::capture(install.as_path())?; // PermissionDenied\n\n// after\nlet install = std::env::var_os(\"LOCALAPPDATA\")\n    .map(PathBuf::into)\n    .unwrap_or_else(|| home.join(\"AppData\\\\Local\"))\n    .join(\"MyApp\"); // real, non-reparse directory\nlet guard = TrustedPathGuard::capture(install.as_path())?;","handlingStrategy":"validation","validationCode":"fn check_no_reparse_ancestors(path: &Path) -> std::io::Result<()> {\n    let mut current = PathBuf::new();\n    for component in path.components() {\n        current.push(component.as_os_str());\n        if let Ok(meta) = std::fs::symlink_metadata(&current) {\n            if meta.is_symlink() || !meta.is_dir() {\n                return Err(std::io::Error::new(\n                    std::io::ErrorKind::PermissionDenied,\n                    format!(\"{} is a reparse point or not a directory\", current.display()),\n                ));\n            }\n        }\n    }\n    Ok(())\n}","typeGuard":"fn is_plain_dir(p: &Path) -> bool {\n    std::fs::symlink_metadata(p).map(|m| m.is_dir() && !m.is_symlink()).unwrap_or(false)\n}","tryCatchPattern":"match TrustedPathGuard::capture(&path) {\n    Err(e) if e.kind() == io::ErrorKind::PermissionDenied\n        && e.to_string().contains(\"redirect or non-directory component\") => {\n        eprintln!(\"resolve {} to a real directory chain first\", path.display());\n    }\n    other => other?,\n}","preventionTips":["Capture guards only for paths built from real directories, never through symlinks or junctions.","Keep private app directories out of OneDrive Known Folder Move / redirected profile trees.","Resolve junctioned user profiles (post-migration) to their physical location before capture.","Prefer install locations under the real `Program Files`/`ProgramData`/`LOCALAPPDATA` physical trees."],"tags":["windows","security","symlink","path-traversal"],"backgroundTag":"path-traversal-blocked","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}