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, keyring-wincred, and keyrings-cryptfile.
Supports Windows, macOS, Linux (desktop + headless), and WSL (Windows Credential Manager via PowerShell bridge).
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 |
copy SOURCE DEST |
master | Idempotent copy (keyring + cryptfile). Re-injects dest to env if previously injected |
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 |
copy |
Same as set. Idempotent: skips if dest matches source. Re-injects dest to env if previously persisted |
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
Backend Matrix
Credstore auto-selects the best available backend via keyring's priority system:
| Platform | Backend | Priority | Mechanism |
|---|---|---|---|
| Windows | WinCredKeyring | 9.0 | Windows Credential Manager (pywin32) |
| WSL | WslBackend | 9.5 | PowerShell → advapi32.dll CredReadW/CredWriteW (C# P/Invoke) |
| macOS | Keychain | 5.0 | macOS Keychain |
| Linux desktop | SecretService | 5.0 | D-Bus Secret Service (GNOME Keyring / KWallet) |
| Linux headless | KeyutilsBackend | 1.5 | Kernel keyring via add_key/keyctl syscalls (ctypes, zero deps) |
Dual-Write Flow
┌──────────────────────────────────────────────────┐
│ CLI (__main__.py) │
│ Interactive: masked_input(), master password │
│ Atomic dual-write: cryptfile → keyring │
│ Rollback on keyring failure │
├──────────────────────────────────────────────────┤
│ Python API (__init__.py) │
│ Programmatic: no prompt, system keyring only │
├────────────────────┬─────────────────────────────┤
│ System Keyring │ Cryptfile Backup │
│ (primary) │ (encrypted) │
│ ──────────────── │ ─────────────────────── │
│ Win CredMan │ keyrings.cryptfile │
│ WSL (PowerShell) │ AES-encrypted INI │
│ macOS Keychain │ Survives OS pw changes │
│ Linux Secret Svc │ │
│ Linux keyutils │ │
└────────────────────┴─────────────────────────────┘
WSL Backend
On WSL, no Linux desktop keyring is available. WslBackend bridges to Windows Credential Manager by calling powershell.exe with embedded C# that P/Invokes advapi32.dll (CredReadW, CredWriteW, CredDeleteW). Priority 9.5 beats WinCredKeyring (9.0) when both are installed — our backend fixes target-format and encoding issues.
Keyutils Backend
On headless Linux (no D-Bus session), KeyutilsBackend stores credentials in the Linux kernel's persistent keyring (@p). Calls add_key and keyctl syscalls directly through ctypes — zero Python dependencies beyond stdlib. Each credential is a "user" key with description "credstore:<service>/<key>".
Credential Enumeration
credstore list reads keys from the OS credential store using platform-specific APIs:
| Platform | API |
|---|---|
| Windows | win32cred.CredEnumerate |
| WSL | powershell.exe + inline C# CredEnumerateW via advapi32.dll |
| Other | Unsupported — re-run credstore set <KEY> to populate cryptfile |
Enumeration retrieves key names only — secret values are never batch-loaded. Sync comparison fetches one value at a time and immediately discards it.
License
MIT
Metadata
Release files for credstore 0.9.6
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.9.6.tar.gz | 58.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| credstore-0.9.6-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 94.1 kB
Release files / credstore-0.9.6.tar.gz
| Download URL | credstore-0.9.6.tar.gz |
|---|---|
| Size | 58.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
20256b78e40a75f42aa07b28903f6a3aa035be8e79d96781b661919bc66f2449
|
|
BLAKE2b-256 checksum How to use checksums |
43bde1f6b6ca81b77013ad5e55a7fb421df5bf63cf8d875c5a8e7e79e730334f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.15
|
Release files / credstore-0.9.6-py3-none-any.whl
| Download URL | credstore-0.9.6-py3-none-any.whl |
|---|---|
| Size | 36.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f1ceb5bc83f6268b4d716bf953fd9a703bf4eb912bcfb327706017cf34508d0e
|
|
BLAKE2b-256 checksum How to use checksums |
78cb50504df90daf0f65eb572eda0d8dbdc7b0a7a82f2e35b8cd49cc7c27dc8c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.15
|