astrid-runtime/astrid · error · io::Error

principal-store capability entry is redirected or not a…

Error message

principal-store capability entry is redirected or not a regular file

What it means

validate_regular checks that an opened principal-store capability file is a real regular file and not a symlink or redirected entry; on failure it raises this InvalidData error. Like the directory check, it guarantees the store never reads or writes through redirected file entries. The wrapper text "inspect principal-store capability file" propagates the underlying metadata error if stat fails.

Solutions

  1. Remove the symlink or non-regular entry and restore a real regular file at that path
  2. Copy the target data back into a genuine file inside the store instead of linking to it
  3. Restrict write access to the store root to prevent symlink planting
  4. Re-run store integrity checks after fixing the entry

Example fix

// before
ln -s /shared/blob store/principals/tenant-a/data.bin
// after
rm store/principals/tenant-a/data.bin && cp /shared/blob store/principals/tenant-a/data.bin
Defensive patterns

Strategy: validation

Validate before calling

fn assert_regular_file(p: &Path) -> io::Result<()> {
    let md = p.symlink_metadata()?;
    if !md.is_file() || md.file_type().is_symlink() {
        return Err(io::Error::new(io::ErrorKind::InvalidData, "not a regular file"));
    }
    Ok(())
}

Type guard

fn is_plain_file(p: &Path) -> bool {
    std::fs::symlink_metadata(p).map(|m| m.is_file() && !m.file_type().is_symlink()).unwrap_or(false)
}

Try / catch

match open_rw_result {
    Err(e) if e.kind() == io::ErrorKind::InvalidData => {
        // restore a real regular file at the path, then retry
    }
    r => r,
}

Prevention

When it happens

Trigger: Calling open_rw or create_private on a path where the entry is a symlink, device node, FIFO, or directory instead of a regular file; or where file_is_redirected detects redirection metadata on the opened handle.

Common situations: A developer symlinked a data file to shared storage; a tooling bug replaced a file with a FIFO/socket; tampering attempts inside a world-writable store directory.

Related errors


AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09). Data as JSON: /api/errors/5e8092803b914dae. Report an issue: GitHub.

Appendix: source

Thrown at crates/astrid-storage/src/engine/durable/native_io.rs:178

        use cap_std::fs::OpenOptionsExt as _;
        options.custom_flags(libc::O_NOFOLLOW | libc::O_NONBLOCK);
    }
    #[cfg(windows)]
    {
        use cap_std::fs::OpenOptionsExt as _;
        use windows_sys::Win32::Storage::FileSystem::FILE_FLAG_OPEN_REPARSE_POINT;
        options.custom_flags(FILE_FLAG_OPEN_REPARSE_POINT);
    }
}

fn validate_regular(file: &NativeFile) -> Result<(), DurableError> {
    let metadata = file
        .metadata()
        .map_err(|source| io_error("inspect principal-store capability file", source))?;
    if !metadata.is_file() || file_is_redirected(&metadata) {
        return Err(io_error(
            "validate principal-store capability file",
            io::Error::new(
                io::ErrorKind::InvalidData,
                "principal-store capability entry is redirected or not a regular file",
            ),
        ));
    }
    Ok(())
}

#[cfg(windows)]
fn file_is_redirected(metadata: &std::fs::Metadata) -> bool {
    use std::os::windows::fs::MetadataExt as _;
    use windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_REPARSE_POINT;

    metadata.file_attributes() & FILE_ATTRIBUTE_REPARSE_POINT != 0
}

#[cfg(not(windows))]
const fn file_is_redirected(_metadata: &std::fs::Metadata) -> bool {

View on GitHub (pinned to affd8760f4)