astrid-runtime/astrid · error · io::Error
principal-store capability entry is redirected or not a dire
Error message
principal-store capability entry is redirected or not a directory
What it means
validate_directory_entry uses symlink_metadata to check that a principal-store directory entry is a real directory and not a symlink or other redirected entry. If the entry is a symlink, or otherwise not a directory, the library refuses to open it. This enforces that capability-relative paths only traverse genuine directories.
Source
Thrown at crates/astrid-storage/src/engine/durable/native_io.rs:96
.map_err(|source| {
io_error("create principal-store capability directory", source)
})?;
sync_directory(parent)?;
open()
.map(Some)
.map_err(|source| io_error("open principal-store capability directory", source))
},
Err(source) => Err(io_error(
"open principal-store capability directory",
source,
)),
}
}
fn validate_directory_entry(parent: &Dir, name: &Path) -> io::Result<()> {
let metadata = parent.symlink_metadata(name)?;
if !metadata.is_dir() || directory_entry_is_redirected(&metadata) {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"principal-store capability entry is redirected or not a directory",
));
}
Ok(())
}
#[cfg(windows)]
fn directory_entry_is_redirected(metadata: &cap_std::fs::Metadata) -> bool {
use cap_std::fs::MetadataExt as _;
use windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_REPARSE_POINT;
metadata.file_type().is_symlink()
|| metadata.file_attributes() & FILE_ATTRIBUTE_REPARSE_POINT != 0
}
#[cfg(not(windows))]
fn directory_entry_is_redirected(metadata: &cap_std::fs::Metadata) -> bool {View on GitHub (pinned to affd8760f4)
Solutions
- Replace the symlink with a real directory (rm the link, mkdir the directory) and restore/migrate its contents
- Do not symlink any entries inside the principal-store root
- Check permissions on the store root so untrusted users cannot plant symlinks
- Run any store-integrity/repair tooling the library provides to rebuild the directory layout
Example fix
// before ln -s /shared/data store/principals/tenant-a // after rm store/principals/tenant-a && mkdir store/principals/tenant-a
Defensive patterns
Strategy: validation
Validate before calling
fn assert_real_dir(p: &Path) -> io::Result<()> {
let md = p.symlink_metadata()?;
if md.file_type().is_symlink() || !md.is_dir() {
return Err(io::Error::new(io::ErrorKind::InvalidData, "entry is symlink or not a dir"));
}
Ok(())
} Type guard
fn is_plain_dir(p: &Path) -> bool {
std::fs::symlink_metadata(p).map(|m| m.is_dir() && !m.file_type().is_symlink()).unwrap_or(false)
} Try / catch
match open_directory_result {
Err(e) if e.kind() == io::ErrorKind::InvalidData => {
// replace symlink with real directory, then retry
}
r => r,
} Prevention
- Never symlink entries inside the principal-store root
- Set store directories to owner-only permissions
- Run integrity scans that flag symlinks before opening the store
- Avoid tools that restructure store layout with links
When it happens
Trigger: Calling open_directory where the named entry is a symlink (possibly to outside the store), a regular file, or another non-directory object; also triggered when filesystem-level redirection (e.g. overlay/symlink tricks) makes the entry look redirected to directory_entry_is_redirected.
Common situations: A developer replaced a store subdirectory with a symlink to share data between environments; automated tooling linked directories to save space; an attacker planted a symlink inside a world-writable store root.
Related errors
- legacy state source is redirected: {}
- legacy state source contains a redirect: {}
- private path is redirected: {}
- legacy principal home root is not a regular directory: {}
- legacy principal profile is not a regular file: {}
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/9493defa6860afdb.
Report an issue: GitHub.