block/buzz · warning · std::io::Error

oauth cache is not a regular file

Error message

oauth cache is not a regular file

What it means

buzz-agent reads its OAuth token cache with hardening: it opens the cache path with O_NOFOLLOW (symlinks are rejected at the kernel level) and then fstats the open fd; if the entry is not a regular file (FIFO, device node, directory), it returns this InvalidData error. This is a deliberate fail-closed security check — a cache that exists but is not a plain file is treated as tampering. Callers (read_cache) convert the error to None and fall back to a fresh browser-based auth flow.

Source

Thrown at crates/buzz-agent/src/auth.rs:1803

///
/// On Unix `O_NOFOLLOW` rejects a symlinked cache path at the kernel level
/// (no stat/open TOCTOU), and `fchmod` on the already-open handle repairs a
/// loose mode against the pinned inode rather than re-resolving the path.
/// A cache that exists but cannot be secured is an error, so the caller fails
/// closed instead of using an exposed file.
#[cfg(unix)]
fn read_private_cache(path: &Path) -> io::Result<Vec<u8>> {
    use std::io::Read;
    use std::os::unix::fs::{OpenOptionsExt, PermissionsExt};

    let mut file = fs::OpenOptions::new()
        .read(true)
        .custom_flags(nix::libc::O_NOFOLLOW)
        .open(path)?;

    let meta = file.metadata()?;
    if !meta.file_type().is_file() {
        return Err(io::Error::new(
            io::ErrorKind::InvalidData,
            "oauth cache is not a regular file",
        ));
    }
    // Tighten in place on the open fd if any group/other bit is set. fchmod
    // targets the inode we already hold, so no attacker can swap the path
    // between the check and the repair.
    if meta.permissions().mode() & 0o077 != 0 {
        file.set_permissions(fs::Permissions::from_mode(0o600))?;
    }

    let mut body = Vec::new();
    file.read_to_end(&mut body)?;
    Ok(body)
}

/// Non-Unix: token persistence and reading are both disabled until a
/// Windows-specific owner-only DACL is implemented. Any legacy token file

View on GitHub (pinned to dad5a33865)

Solutions

  1. Inspect the path: `file ~/.config/buzz-agent/oauth/*/<hash>.json` — expect 'JSON text data', not 'fifo' or 'directory'.
  2. Remove the offending non-regular entry: `rm` (fifo/file) or `rmdir`, then re-run the agent to trigger a fresh interactive login that rewrites a proper 0600 cache file.
  3. If you set a cache_dir_override, make sure it points at a directory only this agent manages.
  4. If you did not create the entry yourself, treat it as a security incident on that home directory — the check exists precisely to catch planted files.

Example fix

# before
file ~/.config/buzz-agent/oauth/default/<hash>.json
# -> FIFO

# after
rm ~/.config/buzz-agent/oauth/default/<hash>.json
buzz-agent login   # recreates a regular, 0600 cache file
Defensive patterns

Strategy: fallback

Validate before calling

// before reading, confirm the cache is a regular file (symlinks fail separately)
use std::os::unix::fs::FileTypeExt;
fn cache_is_plain_file(p: &std::path::Path) -> bool {
    std::fs::symlink_metadata(p)
        .ok()
        .map(|m| m.file_type().is_file())
        .unwrap_or(false)
}

Type guard

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

Try / catch

// read_cache already fails closed: map any read error to None and fall back
// to interactive re-auth instead of using an untrusted file
fn read_cache(path: &Path) -> Option<CachedToken> {
    read_private_cache(path).ok()?
        .pipe(|body| serde_json::from_slice(&body).ok())
}

Prevention

When it happens

Trigger: read_private_cache at crates/buzz-agent/src/auth.rs:501-507 hits this when something replaced $HOME/.config/buzz-agent/oauth/<namespace>/<sha256>.json (or the cache_dir_override path) with a named pipe (`mkfifo`), a device file, or a directory. Note a symlink surfaces earlier as an ELOOP open error, not this message; this error is specifically 'opened fine but fstat says not a regular file'.

Common situations: A test or debugging session left a FIFO at the cache path; the cache directory is shared and another tool wrote a directory named like the cache file; tampering/compromised home dir. User-visible symptom is benign: the agent ignores the cache and forces a new OAuth login each run.

Related errors


AI-assisted analysis of block/buzz@dad5a33865 (2026-08-20). Data as JSON: /api/errors/cd8a98f008c7478b. Report an issue: GitHub.