credstore
Cross-platform credential storage — OS keyring with AES-encrypted file backup.
A standalone secret manager that ships with Slife but has no dependency on it. Depends only on keyring and keyrings-cryptfile.
Install
pip install credstore
# or bundled with Slife:
uv tool install git+https://github.com/juzcn/slife.git
Verify: credstore status
No configuration needed. Run credstore set-password to enable encrypted backup.
CLI
Setup
credstore set-password # creates ~/.credstore/credentials.crypt
Path overridable via CREDSTORE_FILE env var or credstore.json5.
Commands
| Command | Auth | Description |
|---|---|---|
set-password |
sets it | Create or change master key (≥8 chars) |
status |
— | Show backend health |
set KEY |
master + secret | Atomic dual-write: cryptfile → keyring. Rolls back on keyring failure |
get KEY |
— | Keyring only, masked output (sk-5f…b722) |
get KEY -p |
master | Dual-query keyring + cryptfile, plaintext. Fails on mismatch |
delete KEY |
master | Remove from both stores |
list |
master¹ | Triple-read: keyring + cryptfile + env. Shows sync status per key |
inject KEY… [--shell] |
— | Persist to system env: registry (Win) or shell profile (Unix) |
uninject KEY… [--shell] |
— | Remove from system env |
reset-keyring |
master | Restore all from cryptfile → keyring (disaster recovery) |
reset-backup |
master | Sync keyring → cryptfile |
¹ Master password required only if cryptfile exists.
get Modes
| Mode | Reads from | Output | Use case |
|---|---|---|---|
get KEY |
Keyring only | Masked | Quick check, safe for screen sharing |
get KEY -p |
Keyring + cryptfile | Plaintext | Verify consistency, pipe to another tool |
-p mode performs a dual-query consistency check:
- Both stores have the value AND they match → prints plaintext
- One store missing → error with recovery instructions
- Values differ → error, tells you which tool to run
inject / uninject
inject reads a secret from the keyring and persists it to the system environment:
| Platform | Persistence | Activation |
|---|---|---|
| Windows | Registry (HKCU\Environment) + broadcast |
Restart shell, or Invoke-Expression (credstore inject KEY) |
| Unix | Shell profile (~/.bashrc) |
New shell, or eval "$(credstore inject KEY)" |
When stdout is a TTY, inject prints an activation hint instead of the secret. The actual export command only flows through a pipe.
eval "$(credstore inject DEEPSEEK_API_KEY)" # Bash/Zsh — activate now
Invoke-Expression (credstore inject DEEPSEEK_API_KEY) # PowerShell — activate now
uninject reverses the operation — removes from registry or profile and prints the unset command.
list Output
KEY SYSTEM KEYRING CRYPTFILE ENV STATUS
──────── ────────────── ────────────── ──── ──────
ANTHROPIC_API_KEY ✔ ✔ — synced
DEEPSEEK_API_KEY ✔ ✔ ✔ synced
OPENAI_API_KEY — ✔ — cryptfile only
──────── ────────────── ────────────── ──── ──────
3 credential(s) — synced: 2, cryptfile only: 1, env: 1
| Column | Meaning |
|---|---|
SYSTEM KEYRING |
✔ = stored in OS keyring |
CRYPTFILE |
✔ = stored in encrypted backup |
ENV |
✔ = currently set as environment variable |
STATUS |
synced, keyring only, cryptfile only, or MISMATCH ⚠ |
Memory Safety
Secrets are immutable Python str objects — they cannot be zeroed in place. Mitigations:
- Never batch-load —
listcollects only key names. Sync comparison fetches one value at a time and immediatelydels it. - Prefer existence checks —
exists_credential()/list_credential_keys()never retrieve secret content. - Explicit cleanup — every CLI handler
dels secret references on all exit paths including error branches.
| Operation | Cleanup |
|---|---|
get / get_credential() |
Caller must del the returned value |
set |
del secret + del master_pw after dual-write |
list |
Values fetched one-at-a-time, compared, deled immediately |
inject |
Value read → persisted → deled. TTY: no secret on stdout |
reset-keyring |
Each value deled after writing to keyring |
reset-backup |
Batch load unavoidable; del entries + del master_pw after sync |
masked_input() echoes * per keystroke — paste works, actual value never displayed.
Python API
import credstore
# Read / check / delete (system keyring only, no prompt)
credstore.get_credential("myapp/api_key") # → str | None
credstore.exists_credential("myapp/api_key") # → bool (NEVER returns secret)
credstore.list_credential_keys() # → list[str] (NEVER returns values)
credstore.set_credential("myapp/api_key", "sk-…")
credstore.delete_credential("myapp/api_key") # → bool
# keyring: URI resolution
credstore.is_keyring_uri("keyring:myapp/k") # → True
credstore.resolve_uri("keyring:myapp/k") # → the secret value (or KeyError)
credstore.parse_keyring_uri("keyring:srv/k") # → ("srv", "k") | None
# Shell formatting
credstore.format_export("KEY", "secret", "bash") # → "export KEY='secret'"
credstore.format_unset("KEY", "bash") # → "unset KEY"
# Diagnostics
credstore.check_backend() # → {"available": True, "backend": "…", …}
credstore.get_backend_name() # → "system keyring + cryptfile (dual-write)"
Python API talks to system keyring only — no master password, no prompt. Dual-write (keyring + cryptfile) is handled by the CLI.
Callers of get_credential() and resolve_uri() must del the returned value after use. Prefer exists_credential() when you only need to know if a credential exists.
Configuration
Optional credstore.json5 (searched at ./credstore.json5 then ~/.credstore/config.json5):
{
// Override default cryptfile path
cryptfile_path: "/custom/path/credentials.crypt",
}
Priority: CREDSTORE_FILE env var → credstore.json5 → ~/.credstore/credentials.crypt (or ./credentials.crypt in Slife dev mode).
Architecture
┌──────────────────────────────────────────────────┐
│ CLI (__main__.py) │
│ Interactive: masked_input(), master password │
│ Dual-write: cryptfile → keyring (atomic) │
├──────────────────────────────────────────────────┤
│ Python API (__init__.py) │
│ Programmatic: no prompt, system keyring only │
├────────────────────┬─────────────────────────────┤
│ System Keyring │ Cryptfile Backup │
│ (primary) │ (encrypted) │
│ Win CredMan │ keyrings.cryptfile │
│ macOS Keychain │ AES-encrypted INI │
│ Linux Secret Svc │ Survives OS pw changes │
└────────────────────┴─────────────────────────────┘
License
MIT
Metadata
Release files for credstore 0.3.22
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| credstore-0.3.22.tar.gz | 44.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| credstore-0.3.22-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 70.9 kB
Release files / credstore-0.3.22.tar.gz
| Download URL | credstore-0.3.22.tar.gz |
|---|---|
| Size | 44.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
49d30d15b9309309de2bbe1983e6d13198765a55413579cb6a2fa675849087b5
|
|
BLAKE2b-256 checksum How to use checksums |
a2f9e528caad7e394a7042a84d5c74006abb67802d5e0acaf26ac18a5d922ca1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / credstore-0.3.22-py3-none-any.whl
| Download URL | credstore-0.3.22-py3-none-any.whl |
|---|---|
| Size | 26.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0f221597433d1ebb88726000aa29a59249f6e5b4b9557fd05969f6ccfad256ef
|
|
BLAKE2b-256 checksum How to use checksums |
25bf869dca4fe2791e68b6e20b8688ffd038bedc23bea1a19e026590a5e1c1a7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|