zeroclaw-labs/zeroclaw · error

Key file path is a reparse point — refusing to read

Error message

Key file path is a reparse point — refusing to read

What it means

Windows counterpart of the symlink guard: after opening the key file, the same handle's metadata is inspected and FILE_ATTRIBUTE_REPARSE_POINT (which covers NTFS symlinks, junctions, and OneDrive placeholders) triggers a refusal. The read never goes through a reparse indirection, blocking swap attacks via reparse points.

Source

Thrown at crates/zeroclaw-config/src/secrets.rs:538

#[cfg(windows)]
fn open_no_follow(key_path: &Path) -> std::io::Result<std::fs::File> {
    use std::os::windows::fs::{MetadataExt, OpenOptionsExt};
    // FILE_FLAG_OPEN_REPARSE_POINT (0x0020_0000): open the reparse point itself
    // instead of following it.  FILE_FLAG_BACKUP_SEMANTICS (0x0200_0000) lets the
    // call also work if the entry is a directory reparse point.
    const FILE_FLAG_OPEN_REPARSE_POINT: u32 = 0x0020_0000;
    const FILE_FLAG_BACKUP_SEMANTICS: u32 = 0x0200_0000;

    let file = std::fs::OpenOptions::new()
        .read(true)
        .custom_flags(FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS)
        .open(key_path)?;

    // Inspect the SAME handle we will read from.  If it carries the
    // reparse-point attribute, refuse.
    let attrs = file.metadata()?.file_attributes();
    if attrs & FILE_ATTRIBUTE_REPARSE_POINT != 0 {
        return Err(std::io::Error::new(
            std::io::ErrorKind::InvalidInput,
            "Key file path is a reparse point — refusing to read",
        ));
    }
    Ok(file)
}

#[cfg(not(any(unix, windows)))]
fn open_no_follow(_key_path: &Path) -> std::io::Result<std::fs::File> {
    compile_error!(
        "no-follow key reads require platform symlink/reparse-point semantics \
         (O_NOFOLLOW on Unix, reparse-point attribute on Windows); unsupported target"
    );
}

/// Read the key file from a no-follow / reparse-point-verified handle.
///
/// Opening with `open_no_follow` binds the "not a symlink" check to the same

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Move the key file to a plain NTFS location outside any reparse-pointed or cloud-synced directory and update config.
  2. Exclude the keys directory from OneDrive/cloud sync so placeholders are not created.
  3. Copy the key to a regular file at the expected path instead of linking (copy, mklink without reparse is not possible; a hardlink is fine only if the tool accepts it, but a plain copy is the safe answer).
  4. Verify with 'fsutil reparsepoint query <path>' or 'dir /a' (shows <SYMLINK>/<JUNCTION>) before reporting a bug.

Example fix

:: before
C:\Users\me\OneDrive\zeroclaw\keys\agent.key   (cloud placeholder -> refused)

:: after
mkdir C:\zeroclaw-keys
copy C:\Users\me\OneDrive\zeroclaw\keys\agent.key C:\zeroclaw-keys\agent.key
:: point config at C:\zeroclaw-keys\agent.key
Defensive patterns

Strategy: validation

Validate before calling

// Windows: check the reparse attribute before the runtime opens it
#[cfg(windows)]
fn key_file_is_plain(path: &std::path::Path) -> bool {
    const FILE_ATTRIBUTE_REPARSE_POINT: u32 = 0x400;
    std::fs::metadata(path)
        .map(|m| m.file_attributes() & FILE_ATTRIBUTE_REPARSE_POINT == 0)
        .unwrap_or(false)
}

Try / catch

match secrets::read_key_file_no_follow(path) {
    Err(e) if e.kind() == std::io::ErrorKind::InvalidInput && e.to_string().contains("reparse point") => {
        Err(anyhow!("key {path} is a reparse point; copy it to a plain directory and update config"))
    }
    other => other,
}

Prevention

When it happens

Trigger: provisioning_state or read_key_file_no_follow opens a key path on Windows that is an NTFS symlink, a directory junction target, a mklink'd file, or a cloud placeholder carrying the reparse attribute (OneDrive, Dev Drive dedupe).

Common situations: Config directories inside OneDrive-synced folders (placeholders carry the attribute even when hydrated), developers using junctions to share keys between checkouts, mklink experiments, CI on Windows runners with linked secret directories.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/aa6fd905b6b5b568. Report an issue: GitHub.