astrid-runtime/astrid · error
workspace path redirects from its selected target
Error message
workspace path redirects from its selected target: {} What it means
As a final per-component check, resolve_descendant canonicalizes the current path and requires it to equal itself; if canonicalization yields a different path the route 'redirects' from its selected target and InvalidInput is raised. This catches cases the metadata checks miss, such as case-insensitive filesystem aliases or hardlink/bind tricks that make the walked path differ from the kernel's true path.
Solutions
- Use the canonicalized path (dunce::canonicalize / fs::canonicalize output) when constructing descendant paths so walked and canonical forms match.
- Fix directory-name casing to exactly match the on-disk names.
- Remove bind/overlay mounts or symlinked parents inside the workspace.
- Re-run resolution if a concurrent process mutated the tree; serialize workspace mutations to avoid TOCTOU races.
Example fix
// before
let f = ws.resolve_file(Path::new("Config/Settings.TOML"))?; // on-disk: config/settings.toml
// after
let canonical = std::fs::canonicalize(root)?;
let rel = Path::new("config/settings.toml");
let f = ws.resolve_file(rel)?; Defensive patterns
Strategy: validation
Validate before calling
let canonical = std::fs::canonicalize(&root)?; let target = canonical.join(rel); debug_assert_eq!(std::fs::canonicalize(&target).unwrap(), target, "path redirects");
Prevention
- Build descendant paths from an already-canonicalized root with exact on-disk casing
- Avoid bind/overlay mounts inside workspace trees
- Re-resolve after any external process moves or remounts the workspace
When it happens
Trigger: resolve_directory or resolve_file walking a path where any component's canonical form differs from the walked form — typically due to symlinked parents that passed earlier checks only via TOCTOU, bind mounts, or case-insensitive filesystem mismatches.
Common situations: macOS/Windows case-insensitive volumes where the stored path casing differs from the on-disk casing; a parent directory swapped for a symlink between the metadata check and canonicalize; overlay/bind mounts inside the workspace.
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
- Astrid durable media is redirected or not a regular file
- cache changed to a redirect or special entry
- capsule materialization target is redirected or not a…
- capsule projection contains a symbolic link
- directory symlink not allowed in capsule source tree…
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/8a207ecf87860239.
Report an issue: GitHub.
Appendix: source
Thrown at crates/astrid-core/src/workspace_security.rs:208
Err(error) if error.kind() == io::ErrorKind::NotFound => continue,
Err(error) => return Err(error),
};
let final_component = index == components.len().saturating_sub(1);
let expected_file = final_component && kind == DescendantKind::File;
if metadata.file_type().is_symlink()
|| (expected_file && !metadata.is_file())
|| (!expected_file && !metadata.is_dir())
{
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!(
"workspace path must not contain redirects or unexpected file types: {}",
current.display()
),
));
}
if std::fs::canonicalize(¤t)? != current {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!(
"workspace path redirects from its selected target: {}",
current.display()
),
));
}
}
Ok(self.state_dir.join(relative))
}
/// Re-check that the selected state path has not been redirected.
///
/// A missing state directory remains valid. This permits a checked
/// selection to be created before initialization while still rejecting a
/// later symlink or non-directory replacement.
///
/// # ErrorsView on GitHub (pinned to affd8760f4)