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 sameView on GitHub (pinned to 88bb9c8533)
Solutions
- Move the key file to a plain NTFS location outside any reparse-pointed or cloud-synced directory and update config.
- Exclude the keys directory from OneDrive/cloud sync so placeholders are not created.
- 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).
- 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
- Keep key directories outside OneDrive/cloud-synced folders on Windows.
- Prefer junction-free plain paths like C:\zeroclaw-keys over links between checkouts.
- Verify with 'dir /a' or 'fsutil reparsepoint query' whenever a key path was set up with links.
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
- Key file path is a symlink — refusing to read
- Key file path is a symlink — refusing to write
- Cannot determine current username; ACL hardening is required
- Failed to take ownership of key file via takeown; cannot est
- Could not take ownership of key file; cannot establish restr
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/aa6fd905b6b5b568.
Report an issue: GitHub.