Hmbown/CodeWhale · error · io::Error

external credential path contains reparse point

Error message

external credential path contains reparse point {}

What it means

This error means a component of the credential file path is (or is inside) a reparse point such as a symlink or junction. Codewhale walks each path component and rejects any with FILE_ATTRIBUTE_REPARSE_POINT to prevent symlink-based escapes from the intended file location.

Solutions

  1. Point the configuration at the real, non-symlink physical path of the credential file
  2. Replace the symlink with a real copy (or hardlink-free duplicate) of the credential file
  3. On Windows, ensure no junction/mount point exists in any parent directory of the path

Example fix

// before
credentials_path = C:\Users\me\dotfiles\cred.json  # symlink
// after
credentials_path = C:\Users\me\AppData\Roaming\Codewhale\cred.json
Defensive patterns

Strategy: validation

Validate before calling

fn path_has_no_reparse(path: &std::path::Path) -> bool {
    let mut cur = path.to_path_buf();
    while let Some(parent) = cur.parent() {
        if let Ok(md) = std::fs::symlink_metadata(&cur) {
            #[cfg(windows)]
            if md.file_attributes() & 0x400 != 0 { return false; } // FILE_ATTRIBUTE_REPARSE_POINT
        }
        cur = parent.to_path_buf();
    }
    true
}

Try / catch

match read_to_string(path) {
    Ok(s) => use_credential(&s),
    Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => {
        eprintln!("path contains a symlink/reparse point; use the physical path");
    }
    Err(e) => return Err(e.into()),
}

Prevention

When it happens

Trigger: Calling open_secure_regular_file (via read_to_string / read_codewhale_owned_to_string) with a path that contains a symbolic link, junction, or mount-point directory, or whose parent directories are symlinks.

Common situations: Credential path configured through a symlinked dotfiles directory (e.g. dotfiles managed with symlinks under Dropbox/chezmoi); a junctioned profile directory; a redirected config path pointing at a link.

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


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/f4c5a8ea86dd2a1f. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/external_credentials.rs:622

}

#[cfg(windows)]
fn reject_windows_reparse_components(path: &Path) -> io::Result<()> {
    use std::os::windows::fs::MetadataExt;
    use windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_REPARSE_POINT;

    let mut current = std::path::PathBuf::new();
    for component in path.components() {
        current.push(component.as_os_str());
        if matches!(
            component,
            std::path::Component::Prefix(_) | std::path::Component::RootDir
        ) {
            continue;
        }
        let metadata = std::fs::symlink_metadata(&current)?;
        if metadata.file_attributes() & FILE_ATTRIBUTE_REPARSE_POINT != 0 {
            return Err(io::Error::new(
                io::ErrorKind::PermissionDenied,
                format!(
                    "external credential path contains reparse point {}",
                    codewhale_config::quote_os_path(&current)
                ),
            ));
        }
    }
    Ok(())
}

#[cfg(not(any(unix, windows)))]
fn open_secure_regular_file(_path: &Path, _require_owner_only: bool) -> io::Result<File> {
    Err(io::Error::new(
        io::ErrorKind::Unsupported,
        "secure external credential reads are unsupported on this platform",
    ))
}

View on GitHub (pinned to 73e0f67d83)