KeyChest is a secure, native macOS vault and agent-credential broker for SSH keys, API credentials, and PGP keys. It combines local encrypted storage with Agent Chest, a policy-aware HTTP/HTTPS proxy that brokers credentials to AI agents without exposing raw keys.
Discoverability tags: #KeyChest #AgentChest #Tauri #Rust #Go #SSH #APIKeys #AIAgents #DevTools
SSH Vault provides a secure, encrypted vault to store and manage:
- SSH private/public keys — Keep all your SSH keys in one encrypted location
- API keys and tokens — Store API credentials securely
- PGP keys — Generate, import, and manage PGP keys via system GPG
- Secure notes — Private notes encrypted with your vault
AI agents shouldn't hold raw API keys. Agent Chest runs a local HTTPS proxy that injects credentials into outbound requests, so agents never touch them.
How it works:
- Agent sets
HTTPS_PROXY=http://127.0.0.1:8080and authenticates with eitherProxy-Authorization(Bearer/Basic) orX-Vault-ID+X-Agent-ID+X-Agent-Token - Agent makes normal HTTP requests — no keys in code
- Proxy matches request host to stored credentials
- Proxy injects auth headers (Bearer token, API key header, Basic auth)
- Proxy forwards the request to the target API
- Every request is logged to an audit trail
What you get:
- Brokered access through HTTPS_PROXY, not retrieval — nothing to exfiltrate
- Firewall-like access rules (allow/deny by host, path, method)
- Built-in Rule Tester panel (preview allow/deny + matched rule before live calls)
- Policy templates library (OpenAI, GitHub, Stripe, AWS safe defaults)
- Multi-vault RBAC to scope agents to a tight blast radius
- Agent token TTLs (
15m,1h,24h) with countdown and automatic revoke on expiry - Strict No File Write mode (default ON): blocks launcher script/env generation unless explicitly disabled
- Explicit
/proxy/{host}/{path}denials return JSON withproposal_hint - CONNECT denials may only be visible in the audit log;
curlcan report000if the tunnel never completes - Full audit trail of every passing call
- Single Go binary; available as a Docker container
See AGENT_CHEST.md for full documentation. See AGENT_ONBOARDING.md for a no-code agent setup flow.
- Merge PRs into
masterwith clear labels (proxy,security,ui,docs,ci). Release Draftercontinuously updates the next release notes draft.- Cut a release by creating a semantic tag like
v0.2.1and pushing it:
git tag v0.2.1
git push origin v0.2.1- The
Releaseworkflow publishes GitHub release notes and attaches generated checksums.
From the app UI (Proxy tab):
- Start Proxy
- Add Credential, Rule, and RBAC Binding
- Create Invite
- Redeem Invite (gets one-time token and agent context)
- Pick token TTL (15m/1h/24h) and copy the generated snippet
- Use Rule Tester to preview policy outcomes before agent execution
- Use the built-in one-click snippet exporter (
Claude Code,Hermes,OpenClaw,Cursor) - Copy or download snippet and paste into your agent tool config
- Optional: disable Strict No File Write mode only if you want KeyChest to generate local launcher files
- AES-256-GCM encryption with Argon2id key derivation (64 MiB, 3 iterations, parallelism 4) — new vaults use Argon2id, old PBKDF2 vaults unlock automatically
- ZeroizeOnDrop — encryption keys are zeroed from memory when no longer needed
- Touch ID biometric unlock — Passwordless access using macOS Keychain
- Local-only storage — No cloud, no servers, your data never leaves your Mac
- Agent Chest proxy — Credentials never leave the vault; brokered at the proxy layer
- Global hotkey
Cmd+Shift+K— Quick search and copy any key from anywhere - Agent Chest proxy — Start/stop from the Proxy tab, manage credentials, rules, and audit trail
- SSH Agent integration — Add keys to ssh-agent directly from the app
- Git integration — Configure SSH keys per Git repository
- Generate new SSH keys (ed25519, ECDSA, RSA)
- Import existing keys from
~/.ssh(copies into vault, originals untouched) - Import PGP keys from armored private key blocks
- Export keys when needed
- Pin favorites to top of list
- Delete keys from vault (does NOT affect original files)
┌─────────────────────────────────────────────────────────────────┐
│ SSH Vault Tauri │
├─────────────────────────────────────────────────────────────────┤
│ Frontend (React + TypeScript) │
│ ├── Components: VaultList, UnlockVault, VaultDashboard │
│ ├── ProxyManager: Credential proxy UI (discover/CRUD/proposals/agents/audit) │
│ ├── QuickPicker: Global hotkey overlay (Cmd+Shift+K) │
│ └── Settings: Theme, auto-lock, Touch ID management │
├─────────────────────────────────────────────────────────────────┤
│ Backend (Rust + Tauri) │
│ ├── crypto.rs: AES-256-GCM, Argon2id, PBKDF2 │
│ ├── models.rs: Vault serialization/deserialization │
│ ├── proxy.rs: Agent Chest process management + API bridge │
│ ├── ssh.rs: ssh-keygen wrapper, ssh-agent integration │
│ ├── pgp.rs: PGP key management via gpg │
│ ├── biometric.rs: Touch ID via LocalAuthentication │
│ └── git.rs: Git repository SSH configuration │
├─────────────────────────────────────────────────────────────────┤
│ Agent Chest Proxy (Go binary — separate process) │
│ ├── HTTP/HTTPS proxy on :8080 │
│ ├── Management API on :8081 │
│ ├── Credential injection (Bearer, API key header, Basic) │
│ ├── Access rules (allow/deny by host/path/method) │
│ ├── Multi-vault RBAC bindings │
│ └── Audit logger (file + memory) │
├─────────────────────────────────────────────────────────────────┤
│ Storage │
│ ├── Vault files: ~/.ssh-vault/{vault_id}.json │
│ ├── Settings: macOS app storage │
│ └── Biometric keys: macOS Keychain │
└─────────────────────────────────────────────────────────────────┘
/Users/suhaas/Documents/Developer/ssh-vault-tauri/
├── src/ # Frontend source
│ ├── components/
│ │ ├── VaultList.tsx # Main vault selection screen
│ │ ├── CreateVault.tsx # Create new vault flow
│ │ ├── UnlockVault.tsx # Password/Touch ID unlock
│ │ ├── VaultDashboard.tsx # Main app interface (4 tabs + proxy)
│ │ ├── ProxyManager.tsx # Agent Chest proxy management UI
│ │ ├── Settings.tsx # App settings panel
│ │ └── QuickPicker.tsx # Cmd+Shift+K overlay
│ ├── lib/
│ │ ├── api.ts # Tauri command wrappers (including proxy)
│ │ ├── store.ts # Zustand state (including proxy state)
│ │ └── types.ts # TypeScript interfaces (including proxy types)
│ └── App.tsx # Main app component
│
├── src-tauri/src/ # Rust backend
│ ├── crypto.rs # Encryption/decryption (AES-256-GCM, Argon2id, PBKDF2)
│ ├── models.rs # Vault data structures & persistence
│ ├── proxy.rs # Agent Chest proxy management + API bridge
│ ├── ssh.rs # SSH key operations
│ ├── pgp.rs # PGP key management
│ ├── biometric.rs # Touch ID integration
│ ├── git.rs # Git repository handling
│ ├── settings.rs # Settings persistence
│ ├── lib.rs # Module declarations & re-exports
│ └── main.rs # App entry point with all invoke handlers
│
├── agent-chest-proxy/ # Go proxy binary
│ ├── cmd/agent-chest-proxy/
│ │ └── main.go # CLI entry point (proxy + mgmt servers)
│ └── internal/
│ ├── audit/audit.go # Audit logging (file + memory + subscribers)
│ ├── proxy/proxy.go # HTTP/HTTPS proxy handler + management API
│ ├── rules/rules.go # Firewall-like rule engine (allow/deny)
│ ├── rbac/rbac.go # Multi-vault role-based access control
│ └── vault/vault.go # In-memory credential store + AES-256-GCM
│
├── src-tauri/tauri.conf.json # Tauri configuration
├── package.json # Node.js dependencies
├── tailwind.config.js # Tailwind CSS config
├── AGENT_CHEST.md # Agent Chest documentation
├── AGENT_ONBOARDING.md # No-code agent onboarding guide
├── CHANGELOG.md # Changelog
├── ROADMAP.md # Future feature plans
└── README.md # This file
- User enters vault name and password
- App generates random 32-byte salt
- Argon2id derives encryption key from password + salt (m=64 MiB, t=3, p=4)
- Empty vault data encrypted with AES-256-GCM
- Vault metadata + ciphertext saved to disk
- User enters password
- App reads vault salt from file
- Argon2id derives key (tries first; falls back to PBKDF2 for old vaults)
- Attempts AES-GCM decryption
- Success → vault unlocked, data loaded
- Check if biometric key exists in Keychain
- If yes: Prompt for Touch ID
- Touch ID success → retrieve stored encryption key
- Decrypt vault without password
- If no key exists: Fall back to password unlock
- User starts proxy from the Proxy tab (or CLI)
- Proxy listens on
:8080(HTTPS proxy) and:8081(management API) - Agent configures
HTTPS_PROXY=http://127.0.0.1:8080and authenticates viaProxy-Authorization: Bearer <token>(or keeps the legacyX-Vault-ID+X-Agent-ID+X-Agent-Tokenheaders) - Agent makes normal HTTP(S) request to target API
- Proxy intercepts request, matches target host to stored credentials
- Proxy evaluates access rules (allow/deny)
- Proxy injects auth header and forwards to target
- Response returned to agent; request logged to audit trail
- If the explicit
/proxy/endpoint or network guard blocks the request, the proxy returns JSON withproposal_hintso callers can surface a remediation hint
SSH keys are copied into the vault, not referenced:
Before Import:
~/.ssh/
├── id_rsa ← Original file
└── id_rsa.pub
Import Process:
1. Read id_rsa content
2. Encrypt with vault key
3. Store in vault file
After Import:
~/.ssh/
├── id_rsa ← Still here (untouched)
└── id_rsa.pub
~/.ssh-vault/
└── my-vault.json ← Contains encrypted copy
Deleting from app:
- Removes encrypted copy from vault
- Original ~/.ssh/id_rsa remains
| Threat | Mitigation |
|---|---|
| Vault file stolen | AES-256-GCM encryption, Argon2id key derivation |
| Memory dump | Keys only in memory while vault unlocked; ZeroizeOnDrop in Rust |
| Biometric bypass | Touch ID only retrieves key, doesn't bypass encryption |
| Key logger | Biometric unlock doesn't require password entry |
| Cloud compromise | No cloud storage, completely offline |
| AI agent credential exfiltration | Agent Chest brokered access — keys never leave the proxy |
| Agent accessing unauthorized APIs | Firewall rules deny by host/path/method |
| Agent blast radius | Multi-vault RBAC scopes credentials per vault |
| SSRF attacks | Network guard blocks private IPs, loopback, and cloud metadata endpoints |
| DNS rebinding | Proxy resolves hostname, validates IP, then connects directly |
- Physical access to unlocked machine
- Malware with root access
- User accidentally sharing vault password
| Command | Module | Description |
|---|---|---|
vault_list |
models | List all vault files |
vault_save |
models | Save vault metadata |
vault_load |
models | Load vault by ID |
vault_delete |
models | Delete vault file |
pbkdf2_key_derive |
crypto | Derive key with PBKDF2 (legacy) |
argon2_key_derive |
crypto | Derive key with Argon2id |
aes_encrypt |
crypto | Encrypt data |
aes_decrypt |
crypto | Decrypt data |
ssh_generate_key |
ssh | Generate new SSH keypair |
ssh_import_keys |
ssh | Scan ~/.ssh for keys |
ssh_agent_add |
ssh | Add key to ssh-agent |
ssh_agent_list |
ssh | List agent keys |
ssh_agent_remove |
ssh | Remove key from agent |
ssh_agent_clear |
ssh | Clear all agent keys |
ssh_export_key |
ssh | Export key to file |
ssh_get_fingerprint |
ssh | Get key fingerprint |
pgp_generate_key |
pgp | Generate PGP key |
pgp_import_key |
pgp | Import PGP key |
pgp_delete_key |
pgp | Delete PGP key |
pgp_list_keys |
pgp | List PGP keys |
git_set_ssh_key |
git | Configure repo SSH key |
git_is_repo |
git | Check if path is a Git repo |
git_set_ssh_key |
git | Set SSH key per repo |
git_remove_ssh_key |
git | Remove SSH key from repo |
git_setup_deploy_key |
git | Generate and configure deploy key |
biometric_available |
biometric | Check Touch ID support |
biometric_store_key |
biometric | Store key in Keychain |
biometric_retrieve_key |
biometric | Get key with Touch ID |
biometric_unlock |
biometric | Full Touch ID unlock flow |
biometric_delete_key |
biometric | Delete biometric key |
proxy_start |
proxy | Start Agent Chest proxy |
proxy_stop |
proxy | Stop Agent Chest proxy |
proxy_status |
proxy | Check proxy status |
proxy_discover |
proxy | Discover available services and credential keys |
proxy_list_credentials |
proxy | List proxy credentials |
proxy_add_credential |
proxy | Add proxy credential |
proxy_delete_credential |
proxy | Delete proxy credential |
proxy_list_rules |
proxy | List access rules |
proxy_add_rule |
proxy | Add access rule |
proxy_delete_rule |
proxy | Delete access rule |
proxy_list_bindings |
proxy | List RBAC bindings |
proxy_add_binding |
proxy | Create RBAC binding |
proxy_delete_binding |
proxy | Delete RBAC binding |
proxy_list_proposals |
proxy | List access proposals |
proxy_create_proposal |
proxy | Create proposal |
proxy_approve_proposal |
proxy | Approve proposal and materialize allow rule |
proxy_deny_proposal |
proxy | Deny proposal |
proxy_list_agents |
proxy | List registered agents |
proxy_rotate_agent_token |
proxy | Rotate agent token |
proxy_revoke_agent |
proxy | Revoke agent |
proxy_list_invites |
proxy | List invites |
proxy_create_invite |
proxy | Create invite |
proxy_redeem_invite |
proxy | Redeem invite into agent + token |
proxy_rule_test |
proxy | Preview allow/deny + matched rule for a request |
proxy_list_policy_templates |
proxy | List built-in policy templates |
proxy_apply_policy_template |
proxy | Apply a policy template as concrete rules |
proxy_audit_log |
proxy | Query audit trail |
Run the diagnostic to check for corrupted vault ciphertexts:
python3 scripts/vault-diagnostic.pyThis performs a fast structural check without attempting decryption:
INVALID BASE64→ ciphertext is garbled and cannot be decodedCIPHERTEXT TOO SHORT→ ciphertext is truncated (data is missing)SUSPICIOUSLY SMALL→ valid but unusually small for real vault content
Auto-delete all corrupted vaults with a backup:
python3 scripts/vault-diagnostic.py --fixExpected ciphertext sizes (AES-256-GCM = 12-nonce + N-ct + 16-tag):
| Vault contents | Raw bytes | Base64 chars |
|---|---|---|
| Empty vault (all arrays) | ~64 B | ~88 |
| 1–5 SSH keys | ~100–300 B | ~140–420 |
| 10+ keys / notes | ~500–1000 B | ~700–1400 |
Anything under ~50 raw bytes is structurally invalid and cannot contain real data.
Why corruption happens: Bugs in earlier app versions wrote malformed ciphertext. The app now runs vault_check_integrity at startup to surface this before unlock, and all crypto operations are covered by unit tests (8 passing).
# Install dependencies
npm install
# Build the Go proxy binary
cd agent-chest-proxy && go build -o ../src-tauri/ ./cmd/agent-chest-proxy/ && cd ..
# Development (hot reload)
npm run tauri dev
# Production build
npm run build
cd src-tauri && cargo build --release# Frontend tests
npm run test
# Rust backend tests
cd src-tauri && cargo test
# Go proxy tests
cd agent-chest-proxy && go test ./...- React 18
- TypeScript (strict mode)
- Tailwind CSS
- Zustand (state management)
- Lucide React (icons)
- Tauri 2.0
- aes-gcm (encryption)
- argon2 (key derivation, with zeroize)
- pbkdf2 (legacy key derivation)
- reqwest (proxy management API bridge)
- LocalAuthentication (Touch ID via macOS Keychain)
- net/http (proxy + management servers)
- crypto/aes + crypto/cipher (AES-256-GCM)
- golang.org/x/crypto/argon2 (key derivation)
- Google UUID (unique IDs)
- Zero external dependencies beyond stdlib
| Feature | macOS | Notes |
|---|---|---|
| Core vault | ✅ | Full support |
| Touch ID | ✅ | macOS 10.15+ |
| ssh-agent | ✅ | Native integration |
| Global hotkey | ✅ | Cmd+Shift+K |
| PGP keys | ✅ | Via system GPG |
| Agent Chest proxy | ✅ | HTTP/HTTPS credential broker |
| Menu bar | ❌ | Planned |
| Windows/Linux | ❌ | Not planned |
MIT / Proprietary (TBD)
See ROADMAP.md for planned features and implementation timeline.