Secure API key and token koffer with session-based unlock
Project description
koffer >_
A CLI tool for managing API keys and tokens. Instead of scattering .env files across your projects, store your secrets in one encrypted file and inject them into your shell right before you need them.
Keys are AES-256-GCM encrypted with Argon2id key derivation, decrypted only when you run koffer unlock.
Why "koffer"?
"Koffer" is a versatile Dutch word for trunk, briefcase or suitcase; a strangely suitable name for this project.
Koffer is designed to be an easy-to-install, portable, no-subscription keystore that works across Windows, Linux (WSL2), PowerShell, and Bash. It keeps your secrets (somewhat) organized and locked away from casual prying eyes. It is not a vault however, and it doesn't pretend to be. It's just a simple container for your API keys and other CLI secrets so they don't get lost in translation or left out in half a dozen .env files. Think of it as "carry-on" security for your development workflow.
Features
- 🔐 Strong encryption: AES-256-GCM with Argon2id key derivation
- 🔑 System keyring integration: Optional password storage in OS credential manager
- 🐚 Shell integration: Works with PowerShell, Bash, and Zsh
- 📦 Zero config: Sensible defaults for 40+ popular services
- 💾 Portable: Single encrypted file, easy to backup/restore
Installation
Using uv
# Install as a tool (globally) using uv:
uv tool install koffer
# Install the latest (unreleased) development version:
uv tool install git+https://github.com/jsnel/koffer.git
Quick Start
# Add your first secret
koffer add openai
# Enter master password (first time creates the koffer)
# Enter your API key when prompted
# Recommended: run commands with secrets injected (secrets never printed)
koffer run -- python -c "import os; print('OPENAI_API_KEY' in os.environ)"
# If you really need env vars in the current shell:
# PowerShell:
Invoke-Expression (koffer unlock)
# Bash/Zsh:
eval "$(koffer unlock)"
Usage
Add secrets
# Known services get sensible defaults
koffer add openai # -> OPENAI_API_KEY
koffer add anthropic # -> ANTHROPIC_API_KEY
koffer add github # -> GITHUB_TOKEN
# Custom env var or type
koffer add myservice --env-var MY_SECRET --type secret
Unlock (inject into current shell)
unlock is meant for convenience when tools insist on reading env vars from the current shell.
For day-to-day usage, prefer koffer run -- <command> so secrets never need to be printed.
PowerShell:
Invoke-Expression (koffer unlock)
# or (equivalent)
koffer unlock | iex
Bash / Zsh / WSL:
eval "$(koffer unlock)"
Unlock specific secrets only:
eval "$(koffer unlock openai anthropic)"
💡 Tip: Your master password is automatically stored in your system's credential manager (Windows Credential Manager, macOS Keychain, or Linux Secret Service) after first use. Use
--no-keyringto disable this.
By default, unlock base64-wraps its payload and prefers using the clipboard to reduce accidental exposure.
If you want to inspect the generated commands, use:
# Print unlock payload to stdout (still base64-wrapped)
koffer unlock --stdout
# Print commands directly (masked, but decodable)
koffer unlock --stdout --no-obfuscate
# Show plaintext secrets (least secure)
koffer unlock --show-keys
Other commands
koffer list # Show stored secrets
koffer remove <name> # Delete a secret
koffer rotate # Change master password
koffer export backup.enc # Backup koffer
koffer import backup.enc # Restore koffer
koffer run -- <command> # Run a command with secrets injected (recommended)
koffer store-keyring # Re-store password in credential manager
koffer purge-keyring # Remove password from credential manager
Shell Profile Integration
Add to your shell profile for automatic unlocking:
PowerShell ($PROFILE):
# Unlock API keys on shell start (optional)
if (Get-Command koffer -ErrorAction SilentlyContinue) {
Invoke-Expression (koffer unlock 2>$null)
}
Bash/Zsh (~/.bashrc or ~/.zshrc):
# Unlock API keys on shell start (optional)
command -v koffer &>/dev/null && eval "$(koffer unlock 2>/dev/null)"
WSL2 Integration
To share the same koffer between Windows and WSL2, create a symlink to your Windows koffer file:
cd ~
ln -s /mnt/c/Users/<username>/.koffer.enc .koffer.enc
Now both environments use the same encrypted secrets.
Security Model
| Aspect | Implementation |
|---|---|
| Encryption | AES-256-GCM with 12-byte random nonce per secret |
| Key derivation | Argon2id (64 MB memory, 3 iterations, 4 parallelism) |
| Storage | ~/.koffer.enc with 600 permissions (Unix) |
| Credential storage | Optional OS keyring (Windows Credential Manager, macOS Keychain, Linux Secret Service) |
How it works
- Your master password derives an encryption key using Argon2id
- Each secret is encrypted with AES-256-GCM using a unique nonce
- The
unlockcommand outputs shellexportcommands - You
evalthe output to set environment variables in your current shell - Environment variables are process-local and inherited only by child processes
What this protects against
- ✅ Secrets at rest (encrypted on disk)
- ✅ Casual shoulder surfing (secrets are masked in output)
- ✅ Other users on shared systems (file permissions)
What this does NOT protect against
- ❌ Malware with access to your shell's memory
- ❌ Root/admin users on the same machine
- ❌ Keyloggers capturing your master password
Remember: This is a koffer, not a vault. It's designed for convenience and "good enough" security for development workflows, not for high-stakes production secrets.
Supported Services
Supported is a big word here, but koffer includes default environment variable mappings for some number of services (and you can easily add your own):
AI/ML: OpenAI, Anthropic, Google/Gemini, Groq, Mistral, Cohere, Together, Perplexity, Fireworks, DeepSeek, xAI, HuggingFace, Replicate
DevOps: GitHub, GitLab, Vercel, Netlify, Railway, Fly.io, Render, Ngrok
Cloud: AWS, DigitalOcean, Linode, Vultr
Communication: Discord, Slack, Telegram, Twilio
Other: Stripe, SendGrid, Sentry, and more...
Unknown services default to SERVICENAME_API_KEY (or _TOKEN/_SECRET based on --type).
Development
# Clone the repository
git clone https://github.com/jsnel/koffer.git
cd koffer
# Setup env with dev dependencies
uv sync --extra dev
# Install git hooks (runs Ruff + basic hygiene checks)
uv run pre-commit install
# Run hooks against all files
uv run pre-commit run -a
# Run tests
uv run pytest
# Run tests with coverage
uv run pytest --cov
License
MIT License - see LICENSE for details.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file koffer-0.1.1.tar.gz.
File metadata
- Download URL: koffer-0.1.1.tar.gz
- Upload date:
- Size: 76.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e440e4ad0bd033d0d6eba39a7f3f32e1d317caf56653ca7421db308485ad7656
|
|
| MD5 |
b4c124d3e1d675edb59e4c80fe089b69
|
|
| BLAKE2b-256 |
ba6c085381c26f0e31689816de921bf867f76dee514c9f8c08631311594365c4
|
Provenance
The following attestation bundles were made for koffer-0.1.1.tar.gz:
Publisher:
publish.yml on jsnel/koffer
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
koffer-0.1.1.tar.gz -
Subject digest:
e440e4ad0bd033d0d6eba39a7f3f32e1d317caf56653ca7421db308485ad7656 - Sigstore transparency entry: 791166740
- Sigstore integration time:
-
Permalink:
jsnel/koffer@fdc7bfd5239cf868384d6981b02e571995b8f088 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/jsnel
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@fdc7bfd5239cf868384d6981b02e571995b8f088 -
Trigger Event:
release
-
Statement type:
File details
Details for the file koffer-0.1.1-py3-none-any.whl.
File metadata
- Download URL: koffer-0.1.1-py3-none-any.whl
- Upload date:
- Size: 17.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3d6ee1bede0142818f3335cf57dabbc9e82b94d6952ab44c9e56ae6afd0056ca
|
|
| MD5 |
6e47bcdadc3df488a51fafe9c16f2a20
|
|
| BLAKE2b-256 |
a7bb0737ace913061f86b4b7a7971dae8d66b6c5137f2a383b6ed39d0e0fe1bf
|
Provenance
The following attestation bundles were made for koffer-0.1.1-py3-none-any.whl:
Publisher:
publish.yml on jsnel/koffer
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
koffer-0.1.1-py3-none-any.whl -
Subject digest:
3d6ee1bede0142818f3335cf57dabbc9e82b94d6952ab44c9e56ae6afd0056ca - Sigstore transparency entry: 791166745
- Sigstore integration time:
-
Permalink:
jsnel/koffer@fdc7bfd5239cf868384d6981b02e571995b8f088 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/jsnel
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@fdc7bfd5239cf868384d6981b02e571995b8f088 -
Trigger Event:
release
-
Statement type: