gog Keyring Backends and Cross-Platform Credential Storage: A Complete Guide
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 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 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-keyringorkwallet). - 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). This function checks:
- The
GOG_KEYRING_BACKENDenvironment variable. - The
keyring_backendfield from the user configuration (internal/config/config.go). - 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_ADDRESSenvironment 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) handles writes:
- Calls
openKeyring()to initialize the selected backend. - Creates a
keyring.Itemwith the specified key and value. - Writes the item to the backend via
keyring.Set().
For the tracking feature, tracking.SaveSecrets() (lines 25–45 in 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 ininternal/tracking/config.go): When set totruevia the--secrets-in-keyringCLI flag,SaveConfigerases plaintext keys fromtracking.jsonand relies entirely on the keyring.GOG_KEYRING_PASSWORD(referenced inkeyringPasswordEnv, 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:
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:
import "github.com/steipete/gogcli/internal/tracking"
// Store credentials securely in the selected keyring
err := tracking.SaveSecrets(
"[email protected]",
"tracking-key-value",
"admin-key-value",
)
if err != nil {
log.Fatal(err)
}
// Retrieve with automatic fallback logic
trkKey, adminKey, err := tracking.LoadSecrets("[email protected]")
Direct Keyring Access
For custom secrets outside the tracking system, interact with the secrets store directly:
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", "[email protected]", secrets.Token{
RefreshToken: "xyz123",
Services: []string{"gmail"},
})
}
Disable Plaintext Storage
Force all secrets into the keyring, removing them from the JSON configuration:
cfg := &tracking.Config{
Enabled: true,
SecretsInKeyring: true, // Erases plaintext keys from tracking.json
WorkerURL: "https://worker.example.com",
}
tracking.SaveConfig("[email protected]", 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_BACKENDenvironment variable orkeyring_backendconfiguration, defaulting to"auto"for native OS selection. - Headless Linux detection automatically forces the file backend when
DBUS_SESSION_BUS_ADDRESSis 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
SecretsInKeyringconfiguration option andGOG_KEYRING_PASSWORDenvironment 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) 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 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) 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →