Hmbown/CodeWhale · error · anyhow::Error

could not securely open {}: {error}

Error message

could not securely open {}: {error}

What it means

On Unix, Codewhale opens .env with O_NOFOLLOW | O_NONBLOCK | O_CLOEXEC. O_NOFOLLOW makes the open fail with ELOOP if the final path component is a symbolic link; O_NONBLOCK stops a FIFO named .env from hanging startup before the regular-file check can reject it. Any open(2) failure surfaces as 'could not securely open {path}'.

Source

Thrown at crates/tui/src/lib.rs:2671

#[cfg(not(any(unix, windows)))]
fn workspace_dotenv_has_multiple_links(
    _file: &std::fs::File,
    _metadata: &std::fs::Metadata,
) -> Result<bool> {
    Ok(false)
}

#[cfg(unix)]
fn open_workspace_dotenv_without_following_links(path: &Path) -> Result<std::fs::File> {
    use std::os::unix::fs::OpenOptionsExt;

    std::fs::OpenOptions::new()
        .read(true)
        // `O_NONBLOCK` is inert for regular files but prevents a FIFO named
        // `.env` from hanging startup before the metadata check can reject it.
        .custom_flags(libc::O_CLOEXEC | libc::O_NOFOLLOW | libc::O_NONBLOCK)
        .open(path)
        .map_err(|error| anyhow!("could not securely open {}: {error}", path.display()))
}

#[cfg(windows)]
fn open_workspace_dotenv_without_following_links(path: &Path) -> Result<std::fs::File> {
    use std::os::windows::fs::{MetadataExt, OpenOptionsExt};

    const FILE_ATTRIBUTE_REPARSE_POINT: u32 = 0x0000_0400;
    const FILE_FLAG_OPEN_REPARSE_POINT: u32 = 0x0020_0000;
    let file = std::fs::OpenOptions::new()
        .read(true)
        .custom_flags(FILE_FLAG_OPEN_REPARSE_POINT)
        .open(path)
        .map_err(|error| anyhow!("could not securely open {}: {error}", path.display()))?;
    let metadata = file
        .metadata()
        .map_err(|error| anyhow!("could not inspect {}: {error}", path.display()))?;
    if metadata.file_attributes() & FILE_ATTRIBUTE_REPARSE_POINT != 0 {
        bail!(

View on GitHub (pinned to 8880682c63)

Solutions

  1. Replace the symlink with a real file: copy the target's contents into .env and set restrictive permissions
  2. Have your secret manager write or copy the actual file into the workspace at deploy time instead of linking
  3. Fix permissions: chmod 600 .env and ensure every parent directory is traversable
  4. If indirection is required, link the parent directory, not the .env itself — O_NOFOLLOW only guards the final component

Example fix

# before
ln -s ~/.secrets/shared.env .env

# after
cp ~/.secrets/shared.env .env && chmod 600 .env
Defensive patterns

Strategy: validation

Validate before calling

let meta = std::fs::symlink_metadata(".env")?;
if meta.file_type().is_symlink() {
    anyhow::bail!(".env is a symlink; Codewhale rejects it — copy a real file in");
}
if !meta.is_file() {
    anyhow::bail!(".env is not a regular file");
}

Type guard

fn is_plain_regular_file(path: &std::path::Path) -> bool {
    std::fs::symlink_metadata(path)
        .map(|meta| meta.is_file())
        .unwrap_or(false)
}

Prevention

When it happens

Trigger: A .env that is a symlink (ELOOP, typically reported as 'Too many levels of symbolic links'); EACCES on the file or a parent directory; the file vanishing between the ancestor walk and the open (ENOENT race); opening certain device special files.

Common situations: A developer symlinks .env to a shared secret outside the repo (ln -s ~/.secrets/env .env) — this is rejected on purpose; group or world-unreadable permissions on .env; secret managers that install links instead of copying files.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16). Data as JSON: /api/errors/99f6438dfce79dac. Report an issue: GitHub.