Providers¶
kstlib resolves secrets through a cascade of providers. Each provider is checked in order until a value is found.
kwargs → env → keyring → SOPS → default
Overview¶
Source: Direct injection via secrets parameter
Security: Plaintext in memory (caller-controlled)
Use case: Testing, temporary overrides
record = resolve_secret(
"api.key",
secrets={"api.key": "test-value"},
)
# record.value == "test-value"
# record.source == SecretSource.KWARGS
Tip
Kwargs take precedence over all other providers. Use this for unit tests or temporary overrides without modifying environment or config.
Source: Environment variables
Security: Plaintext in process memory (visible via env, /proc)
Use case: CI/CD, containers, runtime injection
import os
os.environ["KSTLIB__API__KEY"] = "sk-xxx"
record = resolve_secret("api.key")
# record.value == "sk-xxx"
# record.source == SecretSource.ENVIRONMENT
Note
Format: KSTLIB__<NAME> with __ as separator and uppercase.
api.key -> KSTLIB__API__KEY
Source: System keychain (macOS Keychain, Windows Credential Manager, Linux Secret Service)
Security: Encrypted by OS (AES-256, DPAPI)
Use case: Desktop apps, persistent local secrets
import keyring
# Store once
keyring.set_password("kstlib", "api.key", "sk-xxx")
# kstlib finds it automatically
record = resolve_secret("api.key")
# record.value == "sk-xxx"
# record.source == SecretSource.KEYRING
Source: SOPS-encrypted files (.sops.yml)
Security: Encrypted at rest (age/GPG/KMS), safe to commit to git
Use case: Git-tracked secrets, team sharing
# secrets.sops.yml
api:
key: ENC[AES256_GCM,data:...,type:str]
record = resolve_secret("api.key")
# record.value == "sk-xxx" (decrypted)
# record.source == SecretSource.SOPS
Tip
Use kstlib secrets init to set up SOPS quickly.
Source: Fallback value passed to resolve_secret()
Security: Plaintext in code (never use for real secrets)
Use case: Development defaults, optional secrets
record = resolve_secret(
"api.key",
required=False,
default="dev-key-for-testing",
)
# record.value == "dev-key-for-testing"
# record.source == SecretSource.DEFAULT
Warning
Only used when no provider returns a value AND required=False.
Kwargs (Direct Injection)¶
Pass secrets directly to resolve_secret() for testing or temporary overrides:
from kstlib.secrets import resolve_secret, SecretSource
record = resolve_secret(
"api.key",
secrets={"api.key": "test-value"},
)
# record.value == "test-value"
# record.source == SecretSource.KWARGS
Nested keys¶
Use dot notation in the secrets dict:
record = resolve_secret(
"database.credentials.password",
secrets={"database.credentials.password": "secret123"},
)
Multiple secrets¶
test_secrets = {
"api.key": "test-key",
"database.password": "test-pass",
"smtp.credentials": "test-creds",
}
api_key = resolve_secret("api.key", secrets=test_secrets)
db_pass = resolve_secret("database.password", secrets=test_secrets)
Environment Variables¶
Maps dotted paths to uppercase env vars with configurable prefix:
# "mail.smtp.password" looks for (in order):
# KSTLIB__MAIL__SMTP__PASSWORD (with prefix)
# MAIL_SMTP_PASSWORD (without prefix)
import os
os.environ["KSTLIB__MAIL__SMTP__PASSWORD"] = "secret"
record = resolve_secret("mail.smtp.password")
# record.source == SecretSource.ENVIRONMENT
Naming convention¶
Secret path |
Environment variable |
|---|---|
|
|
|
|
|
|
Custom prefix¶
Configure in kstlib.conf.yml under providers settings:
secrets:
providers:
- name: environment
settings:
prefix: "MYAPP" # MYAPP__API__KEY instead of KSTLIB__API__KEY
delimiter: "__" # Separator between path segments
Keyring¶
Uses the system’s secure credential storage:
import keyring
# Store a secret (one-time setup)
keyring.set_password("kstlib", "api.stripe_key", "sk_live_xxx")
# kstlib will find it automatically
record = resolve_secret("api.stripe_key")
# record.source == SecretSource.KEYRING
Platform backends¶
Platform |
Backend |
Storage |
|---|---|---|
Linux |
Secret Service |
GNOME Keyring / KWallet |
macOS |
Keychain |
|
Windows |
Credential Manager |
Windows Credential Store |
Service name¶
Configure the keyring service name in kstlib.conf.yml:
secrets:
providers:
- name: keyring
settings:
service: "myapp" # default: kstlib
Managing secrets manually¶
# Write a secret (GNOME Keyring / Secret Service)
secret-tool store --label="kstlib api.key" service kstlib username api.key
# Read a secret
secret-tool lookup service kstlib username api.key
# Delete a secret
secret-tool clear service kstlib username api.key
# Write a secret
security add-generic-password -s kstlib -a api.key -w "sk-xxx"
# Read a secret
security find-generic-password -s kstlib -a api.key -w
# Delete a secret
security delete-generic-password -s kstlib -a api.key
Setup (one-time):
# Install SecretManagement + CredMan extension (uses Windows Credential Manager)
Install-Module Microsoft.PowerShell.SecretManagement -Scope CurrentUser
Install-Module SecretManagement.JustinGrote.CredMan -Scope CurrentUser
# Register the vault
Register-SecretVault -Name 'CredMan' -ModuleName 'SecretManagement.JustinGrote.CredMan'
Usage:
# Write a secret
Set-Secret -Name 'kstlib:api.key' -Secret 'sk-xxx' -Vault CredMan
# Read a secret
Get-Secret -Name 'kstlib:api.key' -Vault CredMan -AsPlainText
# List secrets
Get-SecretInfo -Vault CredMan | Where-Object Name -like 'kstlib:*'
# Delete a secret
Remove-Secret -Name 'kstlib:api.key' -Vault CredMan
Note
The CredMan vault uses Windows Credential Manager, the same backend as Python’s keyring.
Secrets are interoperable between PowerShell and Python.
Tip
For programmatic access, use Python’s keyring library directly:
python import keyring keyring.set_password("kstlib", "api.key", "sk-xxx") # Write keyring.get_password("kstlib", "api.key") # Read
SOPS¶
Decrypts on-demand with intelligent caching:
LRU cache: Decrypted documents cached (default: 16 entries)
Mtime tracking: Cache invalidates when file changes
Format auto-detection: JSON, YAML, or raw text
Configuration¶
secrets:
sops:
path: secrets.sops.yml # default file to search
binary: sops # or full path: /usr/local/bin/sops
cache_size: 16 # LRU cache entries
Multiple SOPS files¶
# Use a specific file
record = resolve_secret("api.key", sops_path="production.sops.yml")
What gets encrypted¶
SOPS uses encrypted_regex to determine which keys to encrypt:
# .sops.yaml
creation_rules:
- path_regex: .*\.sops\.(yml|yaml)$
encrypted_regex: .*(?:key|password|secret|token|credentials?).*
age: age1xxx...
Warning
When a parent key matches the regex (like credentials), all children are encrypted too.
For detailed SOPS setup, see SOPS Setup.
Provider Priority¶
You can customize the provider order:
from kstlib.secrets import resolve_secret
# Skip kwargs, check env first
record = resolve_secret("api.key", skip_providers=["kwargs"])
# Only check specific providers
record = resolve_secret("api.key", providers=["env", "sops"])
Lazy Loading¶
Providers are lazily loaded to minimize startup time. The keyring provider (83ms import time) is only loaded when first accessed.
See Performance for details.