Secrets¶
Resolve credentials from multiple sources (env, keyring, SOPS) with automatic cascade and memory-safe access.
TL;DR¶
# 1. Quick setup (generates age key + .sops.yaml config)
kstlib secrets init
# 2. Check everything is ready
kstlib secrets doctor
# 3. Encrypt your secrets
kstlib secrets encrypt secrets.yml --out secrets.sops.yml --shred
# 4. Use in code (secure pattern)
from kstlib.secrets import resolve_secret, sensitive
record = resolve_secret("api.key")
with sensitive(record) as secret_value:
call_api(api_key=secret_value)
# secret purged from memory
Key Features¶
Multi-provider cascade: env vars, keyring, SOPS files
Memory safety:
sensitive()context manager auto-purges secretsSOPS integration: Encrypted YAML/JSON with age, GPG, or KMS
CLI tools:
init,doctor,encrypt,decrypt,shredSecure delete: Overwrite files before deletion
Quick Start¶
# 1. Install prerequisites
# Linux/macOS: brew install sops age
# Windows: scoop install sops; scoop bucket add extras && scoop install age
# 2. Initialize (creates age key + .sops.yaml)
kstlib secrets init
# 3. Create secrets file
cat > secrets.yml << 'EOF'
api:
stripe_key: "sk_live_xxxxx"
openai_key: "sk-xxxxx"
EOF
# 4. Encrypt and shred original
kstlib secrets encrypt secrets.yml --out secrets.sops.yml --shred
# 5. Use in code
from kstlib.secrets import resolve_secret, sensitive
record = resolve_secret("api.stripe_key")
with sensitive(record) as key:
stripe.api_key = key
How It Works¶
When you call resolve_secret("api.key"), kstlib checks providers in order:
kwargs → env → keyring → SOPS → default
↓ ↓ ↓ ↓ ↓
First match wins. If nothing found, raise or return default.
The sensitive() context manager provides memory-safe access:
record = resolve_secret("api.key")
with sensitive(record) as secret_value:
# Use secret_value here
pass
# record.value is now None - secret purged from memory
See Providers for detailed documentation on each provider.
Configuration¶
In kstlib.conf.yml¶
secrets:
sops:
path: secrets.sops.yml # default SOPS file
binary: sops # or full path
keyring:
service: kstlib # keyring service name
secure_delete:
method: auto # auto | command | overwrite
passes: 3
zero_last_pass: true
Prerequisites¶
Install sops and age:
See SOPS releases and age releases.
brew install sops age
scoop install sops
scoop bucket add extras && scoop install age
For manual setup or advanced configuration (GPG, KMS), see SOPS Setup.
Common Patterns¶
Encrypt and shred¶
# Recommended: encrypt then securely delete original
kstlib secrets encrypt secrets.yml --out secrets.sops.yml --shred
Decrypt for inspection¶
# Print to stdout (safe for quick peek)
kstlib secrets decrypt secrets.sops.yml
# Edit in place (opens in $EDITOR, re-encrypts on save)
sops secrets.sops.yml
Resolve with fallback¶
record = resolve_secret("api.missing_key", required=False, default="fallback")
Error handling¶
from kstlib.secrets import (
resolve_secret,
sensitive,
SecretNotFoundError,
SecretDecryptionError,
)
try:
record = resolve_secret("api.key", required=True)
with sensitive(record) as secret_value:
response = call_api(api_key=secret_value)
except SecretNotFoundError:
logger.warning("API key not configured")
response = call_api_anonymous()
except SecretDecryptionError:
logger.error("Cannot decrypt secrets file")
raise
Quick Reference¶
Task |
Command |
|---|---|
Quick setup |
|
Local setup |
|
Check setup |
|
Encrypt |
|
Encrypt + shred |
|
Decrypt to stdout |
|
Decrypt to file |
|
Edit in place |
|
Secure delete |
|
Troubleshooting¶
SecretNotFoundError¶
Secret key not found in any provider:
# Fix: Check key path matches YAML structure
# secrets.sops.yml:
# api:
# stripe_key: "sk_xxx"
# Correct: resolve_secret("api.stripe_key")
# Wrong: resolve_secret("stripe_key")
SecretDecryptionError¶
SOPS cannot decrypt the file:
# Check your key is available
kstlib secrets doctor
# Verify SOPS can decrypt
sops -d secrets.sops.yml
“age: no identity found”¶
Age key file missing or wrong location:
# Linux/macOS
ls -la ~/.config/sops/age/keys.txt
# Windows
dir %APPDATA%\sops\age\keys.txt
# Regenerate if needed
kstlib secrets init
Secure delete not working¶
Check the configured method:
secrets:
secure_delete:
method: auto # Try 'command' or 'overwrite' explicitly
Security Notes¶
Never log secrets - kstlib redacts sensitive output, but be careful in your code
Use
--shred- Securely delete plaintext files after encryptionRestrict key permissions:
Linux/macOS:
chmod 400 ~/.config/sops/age/keys.txtWindows: User-level NTFS permissions protect
%APPDATA%\sops\age\
Rotate regularly - Both secret values and encryption keys
Use
sensitive()- Minimize secret lifetime in memory
Learn More¶
API Reference¶
Full autodoc: Secrets
Function |
Description |
|---|---|
|
Resolve a secret from the provider cascade |
|
Context manager for secure access with auto-purge |
|
Get the global resolver instance |
|
Result object with |