# Security Considerations for API Keys in Goose and Keyring Integration

> Learn about Goose API key security. Discover its three-tier hierarchy: environment variables, OS keyring, and restricted files for secure integration.

- Repository: [Block Open Source/goose](https://github.com/block/goose)
- Tags: security-best-practices
- Published: 2026-04-05

---

**Goose implements a three-tier security hierarchy for API keys that prioritizes environment variables, falls back to the OS keyring via the `keyring` crate, and finally uses a permission-restricted file at `~/.config/goose/secrets.yaml` with automatic failover when the keyring is unavailable.**

Managing API keys securely is critical for any AI orchestration tool. In the Goose codebase, sensitive credentials follow a strict precedence model that balances convenience with defense-in-depth. This article explores the security considerations for API keys in Goose and keyring integration, detailing how the Rust implementation in [`crates/goose/src/config/base.rs`](https://github.com/block/goose/blob/main/crates/goose/src/config/base.rs) protects secrets across macOS, Linux, and Windows platforms.

## Why the System Keyring Is the Preferred Storage Layer

Goose stores sensitive credentials through a layered approach that prioritizes the operating system’s secure credential store. The implementation in [`crates/goose/src/config/base.rs`](https://github.com/block/goose/blob/main/crates/goose/src/config/base.rs) defines a `SecretStorage` enum that selects between **Keyring** and **File** backends based on availability and user configuration.

The system keyring provides three critical security advantages:

- **Isolation from the filesystem** – Secrets remain inside the OS trusted storage, protected by the user’s login credentials and platform-specific encryption (macOS Keychain, Linux Secret Service, Windows Credential Manager).
- **Automatic locking** – The keyring does not expose raw files, eliminating the risk of accidental commits or backup leaks.
- **Standard user experience** – Users are prompted once per session when unlocking is required, matching native OS behavior.

The selection logic at lines 55-66 of [`base.rs`](https://github.com/block/goose/blob/main/base.rs) determines the storage backend:

```rust
let secrets = if env::var("GOOSE_DISABLE_KEYRING").is_ok()
    || keyring_disabled_in_config(&config_path)
{
    SecretStorage::File {
        path: config_dir.join("secrets.yaml"),
    }
} else {
    SecretStorage::Keyring {
        service: KEYRING_SERVICE.to_string(),
    }
};

```

If the environment variable `GOOSE_DISABLE_KEYRING` is set or the user’s [`config.yaml`](https://github.com/block/goose/blob/main/config.yaml) contains `GOOSE_DISABLE_KEYRING: true`, Goose falls back to file storage.

## File-Based Fallback with 0600 Permissions

When the keyring is unavailable or explicitly disabled, Goose writes API keys to a local YAML file with strict Unix permissions. The `write_secrets_file` function at lines 17-27 of [`base.rs`](https://github.com/block/goose/blob/main/base.rs) enforces **owner-only access**:

```rust
#[cfg(unix)]
{
    use std::os::unix::fs::OpenOptionsExt;
    OpenOptions::new()
        .write(true)
        .create(true)
        .truncate(true)
        .mode(0o600)      // <-- readable only by the file owner
        .open(path)?;
}

```

The `0o600` mode guarantees that only the owning user can read or write the file. This fallback is transparent to callers—the same `Config::get_secret` API works irrespective of the underlying storage mechanism.

## Automatic Failover When Keyring Services Fail

Goose detects keyring-related failures (e.g., DBus not running on Linux) and switches to file storage automatically. The `handle_keyring_fallback_error` function at lines 1001-1011 of [`base.rs`](https://github.com/block/goose/blob/main/base.rs) manages this transition:

```rust
fn handle_keyring_fallback_error<T>(
    &self,
    keyring_err: &keyring::Error,
    fallback_values: Option<&HashMap<String, Value>>,
) -> Result<T, ConfigError> {
    if self.is_keyring_availability_error(&keyring_err.to_string()) {
        std::env::set_var("GOOSE_DISABLE_KEYRING", "1");
        tracing::warn!("Keyring unavailable. Using file storage for secrets.");
        // write existing values to file if we have them …
    } else {
        Err(ConfigError::KeyringError(keyring_err.to_string()))
    }
}

```

When a keyring availability error occurs, the function sets `GOOSE_DISABLE_KEYRING=1` for the current process, ensuring subsequent operations use the file backend. Errors that are not availability-related (e.g., malformed entries) propagate as `ConfigError::KeyringError` rather than triggering fallback behavior.

## How Providers Retrieve Secrets Securely

Provider implementations request API keys through the unified configuration API without knowing the storage backend. The OpenAI provider at [`crates/goose/src/providers/openai.rs`](https://github.com/block/goose/blob/main/crates/goose/src/providers/openai.rs) (lines 81-88) demonstrates this pattern:

```rust
let secrets = config
    .get_secrets("OPENAI_API_KEY", &["OPENAI_CUSTOM_HEADERS"])
    .unwrap_or_default();
let api_key: Option<String> = secrets.get("OPENAI_API_KEY").cloned();

```

If the key is missing, the provider continues without authentication (useful for local server modes). This abstraction ensures that all providers benefit from the same security considerations for API keys in Goose and keyring integration without duplicating storage logic.

## CLI Configuration and User Guidance

The CLI offers interactive configuration that respects the same security model. Located in [`crates/goose-cli/src/commands/configure.rs`](https://github.com/block/goose/blob/main/crates/goose-cli/src/commands/configure.rs) (lines 538-553), the implementation prompts users: *"Would you like to save this value to your keyring?"* 

When the keyring is inaccessible, the CLI prints a clear warning and suggests either fixing the system keychain or disabling the keyring with `GOOSE_DISABLE_KEYRING=true`. This guidance prevents users from accidentally falling back to file storage without understanding the security trade-offs.

## Security Best Practices for Goose Deployments

Follow these guidelines to maintain secure API key handling:

- **Never commit** [`config.yaml`](https://github.com/block/goose/blob/main/config.yaml) or [`secrets.yaml`](https://github.com/block/goose/blob/main/secrets.yaml) to version control. These files contain sensitive credentials that must remain local.
- **Prefer environment variables** (`export OPENAI_API_KEY=...`) for CI/CD pipelines to avoid writing secrets to disk entirely.
- **Enable the system keyring** by default to leverage OS-level encryption and isolation.
- **Inspect system services** when keyring errors appear (e.g., `dbus-daemon` on Linux, macOS Keychain, Windows Credential Manager) to restore the most secure storage path.
- **Verify file permissions** when falling back to file storage—ensure `0600` on Unix or equivalent Windows ACLs prevent other users from reading the file.
- **Rotate API keys regularly** and delete old entries using `config.delete_secret("OPENAI_API_KEY")` to limit exposure.
- **Use Goose’s `configure` command** interactively, which automatically selects the safest available backend and reduces human error.

## Code Examples: Storing and Retrieving API Keys

### Reading a Secret (Any Provider)

```rust
use goose::config::Config;

let cfg = Config::global();                     // global singleton
let token: String = cfg.get_secret("SNOWFLAKE_TOKEN")?; // pulls from env → keyring → file

```

This uses the `get_secret` implementation at lines 88-92 of [`base.rs`](https://github.com/block/goose/blob/main/base.rs), which checks environment variables first, then the keyring, then the fallback file.

### Storing a Secret Securely

```rust
use goose::config::Config;
use serde_json::json;

let cfg = Config::global();
cfg.set_secret("OPENAI_API_KEY", &json!("sk-my-secret-key"))?;

```

If the keyring is active, the value enters the OS credential store; otherwise, it writes to `~/.config/goose/secrets.yaml` with `0600` permissions.

### Disabling the Keyring for CI/CD

```bash
export GOOSE_DISABLE_KEYRING=1

# Or set it in config.yaml:

# GOOSE_DISABLE_KEYRING: true

```

After setting this variable, all subsequent `set_secret` and `get_secret` calls use the file backend exclusively.

### Handling Keyring Errors Gracefully

```rust
match cfg.set_secret("MY_KEY", &json!("value")) {
    Ok(_) => println!("Stored securely."),
    Err(e) => eprintln!("Failed to store secret: {e}"),
}

```

If the OS keyring is unavailable, Goose automatically falls back to file storage and logs a warning via `tracing::warn!`.

## Summary

- Goose checks **environment variables first** for API keys, ensuring no persistence for CI/CD or ephemeral environments.
- **OS keyring integration** via the `keyring` crate provides encrypted, isolated storage that prevents filesystem leaks.
- **Automatic fallback** to `~/.config/goose/secrets.yaml` uses `0600` permissions and triggers when the keyring service is unavailable.
- The **`GOOSE_DISABLE_KEYRING`** environment variable allows explicit control over backend selection.
- Providers use the unified **`get_secret`** API in [`base.rs`](https://github.com/block/goose/blob/main/base.rs), ensuring consistent security behavior across OpenAI, Snowflake, and other integrations.

## Frequently Asked Questions

### How does Goose prioritize different API key storage methods?

Goose implements a three-layer precedence model. **Environment variables** are checked first and always take priority. If the environment variable is absent, Goose queries the **system keyring** (macOS Keychain, Linux Secret Service, or Windows Credential Manager). Finally, if the keyring is disabled or unavailable, Goose reads from the **file-based fallback** at `~/.config/goose/secrets.yaml`. This hierarchy ensures that transient environment configurations override persistent storage while maintaining availability.

### What happens if the OS keyring is unavailable?

When Goose encounters a keyring availability error (such as a missing DBus session on Linux), the `handle_keyring_fallback_error` function in [`base.rs`](https://github.com/block/goose/blob/main/base.rs) automatically sets `GOOSE_DISABLE_KEYRING=1` for the current process and switches to file storage. Existing secrets migrate to `~/.config/goose/secrets.yaml` with strict `0600` permissions. The application logs a warning via `tracing::warn!` to alert the user that the less secure backend is active.

### Are API keys in Goose encrypted at rest?

When using the **system keyring**, API keys are encrypted by the operating system’s native credential store (platform-specific encryption tied to user login). When using the **file fallback**, secrets are stored as plain YAML but protected by filesystem permissions (`0600` on Unix, restricting read access to the file owner). Goose does not implement application-level encryption for the file backend, relying instead on OS-level access controls.

### How can I disable keyring integration for CI/CD environments?

Set the environment variable **`GOOSE_DISABLE_KEYRING=1`** before running Goose commands. Alternatively, add `GOOSE_DISABLE_KEYRING: true` to your [`config.yaml`](https://github.com/block/goose/blob/main/config.yaml). When disabled, Goose bypasses the keyring entirely and uses the file-based backend for all secrets. For maximum security in CI/CD, prefer passing API keys via environment variables (e.g., `export OPENAI_API_KEY=...`) rather than using either persistent storage mechanism.