Skip to content

System Keyring Backend

When to use

KeyringBackend is the right choice when you need to store developer credentials locally on a single machine without running any additional infrastructure. It delegates storage to the OS credential manager, which provides hardware-backed or OS-level encryption automatically. Common use cases include storing personal API keys, OAuth tokens, or database passwords for CLI tools, desktop applications, and local development workflows.

Platform support

Platform Credential store Notes
Windows Windows Credential Manager Built-in; no extra setup required
macOS macOS Keychain Built-in; no extra setup required
Linux (desktop) GNOME Keyring / KWallet Requires a running Secret Service daemon
Linux (headless) Secret Service via D-Bus Install python3-secretstorage; run GNOME Keyring or a compatible daemon

Linux headless setup

On a headless Linux server, install the secretstorage library and launch a D-Bus Secret Service:

Bash
pip install secretstorage
# Then start a Secret Service daemon such as GNOME Keyring in headless mode

Constructor parameters

Parameter Type Default Description
service_name str "default_service" Groups all secrets under this namespace in the OS credential store.
log_level LogLevel \| str LogLevel.WARNING Minimum log level for the internal logger.
logger PyLogShield \| None None Provide your own PyLogShield logger instance.
mask bool True Mask secret values in log output.

Default service name differs between library and CLI

KeyringBackend() defaults service_name to "default_service", but the CLI (cb keyring) defaults --service-name to "default". A secret written with one using its default is invisible to the other. Always pass service_name= / --service-name explicitly to ensure portability.

JSON serialisation

All secrets are stored as JSON strings in the keyring. When you call add_secret("key", {"field": "value"}), the dict is serialised with json.dumps() before being written. On retrieval, get_secret() deserialises the string back to a dict with json.loads(). This means any JSON-serialisable dict round-trips transparently, including multi-field secrets.

Python
# What gets stored in the OS keyring:
# service: "myapp", username: "database"
# password: '{"host": "localhost", "port": "5432", "user": "admin"}'

# What you get back:
secret = backend.get_secret("database")
# {"host": "localhost", "port": "5432", "user": "admin"}

CRUD operations

add_secret

Stores a new secret. Raises KeyringError if the key already exists — use update_secret() to change an existing entry.

Python
from credential_bridge import KeyringBackend

backend = KeyringBackend(service_name="myapp")

# Single-field secret
backend.add_secret("github_token", {"github_token": "ghp_xxx"})

CLI equivalent:

Bash
cb keyring add github_token --secret github_token=ghp_xxx --service-name myapp

get_secret

Returns the secret dict. Raises KeyringSecretNotFoundError if the key does not exist.

Python
secret = backend.get_secret("github_token")
# {"github_token": "ghp_xxx"}
print(secret["github_token"])  # ghp_xxx

CLI equivalent:

Bash
cb keyring get github_token --service-name myapp

update_secret

Replaces the stored value for an existing key. Raises KeyringSecretNotFoundError if the key does not exist — use add_secret() first.

Python
backend.update_secret("github_token", {"github_token": "ghp_new"})

CLI equivalent:

Bash
cb keyring update github_token --secret github_token=ghp_new --service-name myapp

delete_secret

Removes a secret from the keyring. Raises KeyringSecretNotFoundError if the key does not exist.

Python
backend.delete_secret("github_token")

CLI equivalent:

Bash
cb keyring delete github_token --service-name myapp --yes

list_secrets

Not supported on any platform

list_secrets() always raises KeyringError. Windows Credential Manager and macOS Keychain do not expose enumeration APIs — there is no supported way to retrieve all keys under a service name. Keep track of your key names separately (e.g. in application configuration or documentation).

Python
from credential_bridge import KeyringError

try:
    keys = backend.list_secrets()
except KeyringError as e:
    print(e)
    # KeyringBackend.list_secrets() is not supported on this platform.
    # Windows Credential Manager and macOS Keychain do not expose enumeration APIs.

Multi-field secrets

Store any number of key-value pairs under a single keyring entry. The entire dict is serialised as one JSON string.

Python
backend.add_secret(
    "database",
    {"host": "localhost", "port": "5432", "user": "admin"},
)

secret = backend.get_secret("database")
# {"host": "localhost", "port": "5432", "user": "admin"}

print(secret["host"])  # localhost
print(secret["port"])  # 5432

Error handling

Python
from credential_bridge import KeyringError, KeyringSecretNotFoundError

# Distinguish "not found" from other keyring failures
try:
    secret = backend.get_secret("missing_key")
except KeyringSecretNotFoundError:
    print("Key does not exist — check the name and service_name")
except KeyringError as e:
    print(f"Keyring backend error: {e}")

# Catch duplicate on add
try:
    backend.add_secret("github_token", {"github_token": "ghp_xxx"})
except KeyringError as e:
    # Key already exists
    print(f"Already exists: {e}")

# Safe upsert pattern
try:
    backend.add_secret("github_token", {"github_token": "ghp_xxx"})
except KeyringError:
    backend.update_secret("github_token", {"github_token": "ghp_xxx"})

Common errors

Exception Cause Resolution
KeyringError add_secret() called on an existing key Use update_secret() to change the value
KeyringSecretNotFoundError get_secret(), update_secret(), or delete_secret() called with a key that does not exist Verify the key name and service_name match what was used when adding
KeyringError list_secrets() called Platform limitation — track key names manually
ConfigurationError logger argument is not a PyLogShield instance Pass a valid PyLogShield instance

KeyringSecretNotFoundError is a subclass of KeyringError, which is itself a subclass of BackendError.