astrid-runtime/astrid · error
PermissionDenied
PermissionDenied
Error message
trusted Windows path contains a redirect or non-directory component: {} What it means
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.
Source
Thrown at crates/astrid-core/src/platform_fs/windows/path.rs:205
let mut components: Vec<LockedPathComponent> = Vec::new();
let mut current = PathBuf::new();
let mut rooted = false;
for component in path.components() {
current.push(component.as_os_str());
if matches!(component, Component::RootDir) {
rooted = true;
}
if !rooted {
continue;
}
let metadata = match std::fs::symlink_metadata(¤t) {
Ok(metadata) => metadata,
Err(error) if error.kind() == io::ErrorKind::NotFound => continue,
Err(error) => return Err(error),
};
if metadata.file_attributes() & FILE_ATTRIBUTE_REPARSE_POINT != 0 || !metadata.is_dir()
{
return Err(io::Error::new(
io::ErrorKind::PermissionDenied,
format!(
"trusted Windows path contains a redirect or non-directory component: {}",
current.display()
),
));
}
let (handle, identity) = if let Some(parent) = components.last() {
open_directory_identity_relative(
parent.handle.0,
component.as_os_str(),
current == path,
)?
} else if current == path {
open_locked_directory(¤t)?
} else {
open_directory_identity(¤t, true)?
};View on GitHub (pinned to affd8760f4)
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.
Example fix
// before
let install = home.join("MyApp"); // home is a OneDrive-junctioned path
let guard = TrustedPathGuard::capture(install.as_path())?; // PermissionDenied
// after
let install = std::env::var_os("LOCALAPPDATA")
.map(PathBuf::into)
.unwrap_or_else(|| home.join("AppData\\Local"))
.join("MyApp"); // real, non-reparse directory
let guard = TrustedPathGuard::capture(install.as_path())?; Defensive patterns
Strategy: validation
Validate before calling
fn check_no_reparse_ancestors(path: &Path) -> std::io::Result<()> {
let mut current = PathBuf::new();
for component in path.components() {
current.push(component.as_os_str());
if let Ok(meta) = std::fs::symlink_metadata(¤t) {
if meta.is_symlink() || !meta.is_dir() {
return Err(std::io::Error::new(
std::io::ErrorKind::PermissionDenied,
format!("{} is a reparse point or not a directory", current.display()),
));
}
}
}
Ok(())
} Type guard
fn is_plain_dir(p: &Path) -> bool {
std::fs::symlink_metadata(p).map(|m| m.is_dir() && !m.is_symlink()).unwrap_or(false)
} Try / catch
match TrustedPathGuard::capture(&path) {
Err(e) if e.kind() == io::ErrorKind::PermissionDenied
&& e.to_string().contains("redirect or non-directory component") => {
eprintln!("resolve {} to a real directory chain first", path.display());
}
other => other?,
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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`).
Understand the failure class
Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.
Related errors
- symlink {} resolves outside the capsule source root ({}); re
- cache contains a redirect or special entry: {}
- named-pipe endpoint path must not contain a parent component
- workspace tree contains a redirected or special entry: {}
- leftover capsule authority receipt is not a regular file: {}
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/ba57d30725114477.
Report an issue: GitHub.