Hmbown/CodeWhale · error

xAI OAuth file must not have multiple filesystem links

Error message

xAI OAuth file must not have multiple filesystem links

What it means

This library hardens the xAI OAuth credential file against link-based attacks: it opens the file, then verifies the underlying metadata (regular file, owned by the current euid, hardlink count exactly 1). If `st_nlink` is greater than 1, someone could hardlink the credential file to another location and read or tamper with tokens outside the secured directory, so the open is rejected.

Solutions

  1. Delete the file and log in again so the store recreates a fresh, single-link token file
  2. Find and remove extra hard links: `find / -samefile <token-path>` then delete the stray link, keeping only the one in the credentials directory
  3. Replace the hardlinked file with a real copy: `cp --remove-destination <file> <file>` (or `cp -L` then remove the link source)
  4. Check sync/backup software configuration to copy rather than hardlink credential files

Example fix

// before (fails: file has 2 links)
ln ~/.codewhale/xai/auth.json ~/backup/auth.json
// after
cp ~/.codewhale/xai/auth.json ~/backup/auth.json
Defensive patterns

Strategy: validation

Validate before calling

use std::os::unix::fs::MetadataExt;
fn is_safe_credential_file(p: &std::path::Path) -> std::io::Result<bool> {
    let m = std::fs::metadata(p)?;
    Ok(m.is_file() && m.uid() == unsafe { libc::geteuid() } && m.nlink() == 1)
}

Type guard

fn hardlink_count(p: &std::path::Path) -> std::io::Result<u64> {
    Ok(std::fs::metadata(p)?.nlink())
}

Try / catch

match store.load_tokens() {
    Ok(t) => t,
    Err(e) if e.to_string().contains("multiple filesystem links") => {
        eprintln!("credential file hardlinked; removing and re-authenticating");
        let _ = store.logout();
        store.login()? // recreates a clean file
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Any read/write/remove/rename/open path through validate_owned_file_handle encounters a credentials file whose link count is not 1 — typically because the user (or a backup/restore/sync tool) created a hard link to the token file.

Common situations: Restoring credentials from a backup tool that hardlinks instead of copying; syncing the ~/.codewhale directory with a tool like rsync --link-dest; a developer manually running `ln` on the token file to share it between machines or profiles.

Related errors


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

Appendix: source

Thrown at crates/config/src/xai_credentials.rs:908

    }
}

#[cfg(unix)]
fn validate_owned_file_handle(file: &File, path: &Path) -> Result<fs::Metadata> {
    use std::os::unix::fs::MetadataExt as _;
    let metadata = file.metadata().with_context(|| {
        format!(
            "inspecting Codewhale-owned xAI OAuth file {}",
            crate::quote_os_path(path)
        )
    })?;
    anyhow::ensure!(metadata.is_file(), "xAI OAuth path must be a regular file");
    // SAFETY: geteuid(2) dereferences no pointers.
    anyhow::ensure!(
        metadata.uid() == unsafe { libc::geteuid() },
        "xAI OAuth file must be owned by the current user"
    );
    anyhow::ensure!(
        metadata.nlink() == 1,
        "xAI OAuth file must not have multiple filesystem links"
    );
    Ok(metadata)
}

#[cfg(windows)]
fn open_owned_credentials_directory(directory: &Path) -> Result<XaiOAuthCredentialStore> {
    use std::os::windows::fs::OpenOptionsExt as _;
    use windows_sys::Win32::Storage::FileSystem::{
        FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, FILE_GENERIC_READ,
        FILE_SHARE_READ, FILE_SHARE_WRITE, WRITE_DAC, WRITE_OWNER,
    };

    anyhow::ensure!(
        directory.is_absolute(),
        "xAI OAuth credentials directory must be absolute"
    );

View on GitHub (pinned to 73e0f67d83)