# gog Keyring Backends and Cross-Platform Credential Storage: A Complete Guide

> Explore gog's keyring backends supporting macOS Keychain Linux Secret Service Windows Credential Manager encrypted files and headless detection for secure cross-platform credential storage.

- Repository: [Peter Steinberger/gogcli](https://github.com/steipete/gogcli)
- Tags: how-to-guide
- Published: 2026-02-16

---

**gog supports macOS Keychain, Linux Secret Service (D-Bus), Windows Credential Manager, and an encrypted file-based fallback, automatically selecting the appropriate native backend while providing headless Linux detection and timeout protection to prevent hangs.**

The `gog` CLI tool from the [steipete/gogcli](https://github.com/steipete/gogcli) repository manages OAuth tokens, tracking keys, and other sensitive configuration data using the **99designs/keyring** library. Understanding how gog handles keyring backends and credential storage across macOS, Linux, and Windows ensures you can securely deploy the tool in both desktop and headless server environments.

## Supported gog Keyring Backends

`gog` leverages the 99designs/keyring library to abstract platform-specific credential stores. The supported backends are defined in [`internal/secrets/store.go`](https://github.com/steipete/gogcli/blob/main/internal/secrets/store.go) within the `allowedBackends` function (lines 96–104).

### macOS Keychain

On macOS, gog can use the native **Keychain** backend. When the `keyring_backend` configuration is set to `"keychain"` (or via the `GOG_KEYRING_BACKEND` environment variable), the `allowedBackends` function returns a slice containing only `keyring.KeychainBackend` (lines 99–102). This ensures OAuth tokens are stored in the macOS Keychain Access application.

### Linux Secret Service and Windows Credential Manager

When `keyring_backend` is set to `"auto"` (the default), `allowedBackends` returns `nil` (lines 98–100), allowing the 99designs/keyring library to select the native default:
- **Linux**: Uses the **Secret Service** API via D-Bus (typically implemented by `gnome-keyring` or `kwallet`).
- **Windows**: Uses the **Credential Manager** (Windows Vault).

### Encrypted File Backend

For headless servers or when users prefer file-based storage, gog supports an **encrypted file** backend. When `keyring_backend` is `"file"`, `allowedBackends` returns `keyring.FileBackend` (lines 101–104). This backend encrypts secrets at rest using a user-supplied password, which can be provided via the `GOG_KEYRING_PASSWORD` environment variable or prompted interactively via `fileKeyringPasswordFunc` (lines 186–199).

## How gog Selects the Right Backend

Backend selection involves environment detection, configuration resolution, and platform-specific fallbacks to prevent operational hangs.

### Backend Resolution Logic

The resolution process begins in `ResolveKeyringBackendInfo()` (lines 77–93 in [`internal/secrets/store.go`](https://github.com/steipete/gogcli/blob/main/internal/secrets/store.go)). This function checks:
1. The `GOG_KEYRING_BACKEND` environment variable.
2. The `keyring_backend` field from the user configuration ([`internal/config/config.go`](https://github.com/steipete/gogcli/blob/main/internal/config/config.go)).
3. Falls back to `"auto"` if neither is set.

The `allowedBackends()` function (lines 96–104) then maps these string values to concrete backend constraints:
- `"auto"` → `nil` (library chooses native default).
- `"keychain"` → `[]string{keyring.KeychainBackend}`.
- `"file"` → `[]string{keyring.FileBackend}`.

### Headless Linux Fallback

To prevent hangs on headless Linux systems without a D-Bus session, `shouldForceFileBackend()` (lines 151–152) detects when:
- The OS is Linux.
- The backend is `"auto"`.
- The `DBUS_SESSION_BUS_ADDRESS` environment variable is empty.

When these conditions are met, gog forces the file backend regardless of the `"auto"` setting, ensuring the application remains functional in server environments.

### Timeout Protection for D-Bus

When a D-Bus address exists but the secret service is unresponsive (common in WSL or misconfigured Linux desktops), `shouldUseKeyringTimeout()` (lines 155–156) triggers `openKeyringWithTimeout()` (lines 220–246). This wraps `keyring.Open()` with a **5-second timeout**, preventing indefinite hangs while allowing functional secret services to operate normally.

## Storing and Retrieving Credentials

Once the backend is selected, gog provides a consistent interface for secret management through the `internal/secrets` package.

### The Storage Flow

The `SetSecret(key, value)` function (lines 58–73 in [`internal/secrets/store.go`](https://github.com/steipete/gogcli/blob/main/internal/secrets/store.go)) handles writes:
1. Calls `openKeyring()` to initialize the selected backend.
2. Creates a `keyring.Item` with the specified key and value.
3. Writes the item to the backend via `keyring.Set()`.

For the tracking feature, `tracking.SaveSecrets()` (lines 25–45 in [`internal/tracking/secrets.go`](https://github.com/steipete/gogcli/blob/main/internal/tracking/secrets.go)) calls `secrets.SetSecret` with scoped keys like `tracking:<email>` and `admin:<email>`.

Retrieval follows the inverse path via `GetSecret(key)` (lines 76–92), which opens the keyring and returns the item value. The tracking system implements fallback logic in `tracking.LoadSecrets()` (lines 50–66): if new scoped keys are not found, it falls back to legacy keys, gracefully handling `keyring.ErrKeyNotFound`.

### Configuration Options

Users control credential storage behavior through several mechanisms:

- **`SecretsInKeyring`** (line 26 in [`internal/tracking/config.go`](https://github.com/steipete/gogcli/blob/main/internal/tracking/config.go)): When set to `true` via the `--secrets-in-keyring` CLI flag, `SaveConfig` erases plaintext keys from [`tracking.json`](https://github.com/steipete/gogcli/blob/main/tracking.json) and relies entirely on the keyring.
- **`GOG_KEYRING_PASSWORD`** (referenced in `keyringPasswordEnv`, line 50): Supplies the passphrase for the file backend non-interactively, essential for automation.

## Practical Code Examples

### Force a Specific Backend via Environment Variable

For headless servers or specific security requirements, override the automatic backend selection:

```bash
export GOG_KEYRING_BACKEND=file
export GOG_KEYRING_PASSWORD=MySecurePassphrase
gog tracking enable

```

### Programmatic Secret Storage

When building tools atop gog, use the tracking package for scoped secret management:

```go
import "github.com/steipete/gogcli/internal/tracking"

// Store credentials securely in the selected keyring
err := tracking.SaveSecrets(
    "user@example.com",
    "tracking-key-value",
    "admin-key-value",
)
if err != nil {
    log.Fatal(err)
}

// Retrieve with automatic fallback logic
trkKey, adminKey, err := tracking.LoadSecrets("user@example.com")

```

### Direct Keyring Access

For custom secrets outside the tracking system, interact with the secrets store directly:

```go
import "github.com/steipete/gogcli/internal/secrets"

func storeOAuthToken() error {
    // Open the default store with automatic backend selection
    store, err := secrets.OpenDefault()
    if err != nil {
        return err
    }
    
    // Store a structured token
    return store.SetToken("gmail", "user@example.com", secrets.Token{
        RefreshToken: "xyz123",
        Services:     []string{"gmail"},
    })
}

```

### Disable Plaintext Storage

Force all secrets into the keyring, removing them from the JSON configuration:

```go
cfg := &tracking.Config{
    Enabled:          true,
    SecretsInKeyring: true, // Erases plaintext keys from tracking.json
    WorkerURL:        "https://worker.example.com",
}
tracking.SaveConfig("user@example.com", cfg)

```

## Summary

- **gog** utilizes the **99designs/keyring** library to abstract platform-native credential stores, supporting **macOS Keychain**, **Linux Secret Service**, **Windows Credential Manager**, and an **encrypted file backend**.
- Backend selection is controlled via the `GOG_KEYRING_BACKEND` environment variable or `keyring_backend` configuration, defaulting to `"auto"` for native OS selection.
- **Headless Linux detection** automatically forces the file backend when `DBUS_SESSION_BUS_ADDRESS` is missing, preventing hangs in server environments.
- **Timeout protection** wraps D-Bus operations with a 5-second limit via `openKeyringWithTimeout`, ensuring responsiveness even when the secret service is unavailable.
- The **`SecretsInKeyring`** configuration option and **`GOG_KEYRING_PASSWORD`** environment variable provide additional control for security-hardened and automated deployments.

## Frequently Asked Questions

### What happens if gog cannot connect to the Linux Secret Service?

If gog detects a missing `DBUS_SESSION_BUS_ADDRESS` environment variable on Linux with the backend set to `"auto"`, the `shouldForceFileBackend` function (lines 151–152 in [`internal/secrets/store.go`](https://github.com/steipete/gogcli/blob/main/internal/secrets/store.go)) automatically forces the encrypted file backend. This prevents the application from hanging while attempting to connect to a non-existent D-Bus session.

### How do I use gog on a headless server without interactive password prompts?

Set the `GOG_KEYRING_BACKEND` environment variable to `file` and provide the encryption passphrase via `GOG_KEYRING_PASSWORD`. This bypasses the interactive prompt defined in `fileKeyringPasswordFunc` (lines 186–199) and allows automated scripts to store and retrieve credentials without user interaction.

### Can I migrate existing plaintext credentials into the keyring?

Yes. Set the `SecretsInKeyring` field to `true` in your `tracking.Config` and call `tracking.SaveConfig()`. This configuration triggers the removal of plaintext keys from [`tracking.json`](https://github.com/steipete/gogcli/blob/main/tracking.json) and ensures subsequent calls to `tracking.SaveSecrets()` store credentials exclusively in the configured keyring backend, with `tracking.LoadSecrets()` automatically handling the transition via its fallback logic.

### Why does gog use a 5-second timeout when opening the keyring on Linux?

The `openKeyringWithTimeout` function (lines 220–246 in [`internal/secrets/store.go`](https://github.com/steipete/gogcli/blob/main/internal/secrets/store.go)) implements a 5-second timeout to handle scenarios where a D-Bus address exists but the secret service (like `gnome-keyring-daemon`) is unresponsive or misconfigured. This prevents the CLI from hanging indefinitely and improves reliability in environments like WSL or containers with partial D-Bus setups.