#keychain #api-key #vault #secret #cli

bin+lib klef

Local-first vault for API keys and secrets, backed by the OS keychain

7 releases

Uses new Rust 2024

new 0.4.4 Aug 10, 2026
0.4.3 Aug 10, 2026
0.4.1 May 11, 2026
0.3.0 May 6, 2026
0.2.0 May 6, 2026

#714 in Configuration

MIT license

315KB
6.5K SLoC

klef

klef stores your API keys in the OS Keychain and resolves them at runtime in your .env (STRIPE_KEY=klef:stripe). No master password, no cloud, no plaintext on disk.

Crates.io CI License: MIT Rust 1.85+ Platforms

A local vault for your API keys and secrets — so you stop losing them in Dashlane, Notes, or scattered .env files.

klef in action: importing a plaintext .env into the Keychain, then running a command with the values resolved

The problem

You have 15 API keys (Stripe, Anthropic, OpenAI, Gemini, Telnyx, etc.). You stash them in Dashlane, in text files, in lost .env files. When you start a project, you spend 10 minutes hunting them down — and worst of all, you copy-paste the value into the project's .env, so it lingers in plaintext on your disk.

The solution

A local CLI that:

  • Stores your keys in the OS Keychain — encryption handled by Apple/GNOME, not by us.
  • Retrieves a key with a single command: klef get stripe.
  • Injects values into your projects through references in the .env instead of plaintext values:
    STRIPE_KEY=klef:stripe          # reference — resolved at runtime
    ANTHROPIC_KEY=klef:anthropic    # same
    
    Then klef run -- npm start resolves it all and runs your command with the right env vars.
  • Stays 100% local — no server, no cloud, no telemetry.

Why not another tool?

klef 1Password CLI doppler / infisical direnv + .env
Local-first ❌ (1P account) ❌ (cloud)
Native Keychain storage via op
References in .env klef: op:// {{var}} ❌ literal
No master password ✅ (OS keychain)
Free $3/month freemium
Multi-machine sync ❌ (v0.4)

klef targets the single-user, single-machine, local-first, free use case. The competitors are excellent — it's just a different niche. (Comparison verified on 2026-05-06.)

klef has no master password of its own: your OS keychain holds the secrets, and it's already unlocked by your login session. Biometric confirmation per access (Touch ID) is not implemented — it's tracked in #118. On macOS, if you're seeing repeated password prompts, that's the login keychain re-locking: run klef keychain configure once (see docs/macos-keychain.md).

Demo

# You have a .env lying around with plaintext secrets:
$ cat .env
STRIPE_API_KEY=sk_live_xyz
ANTHROPIC_API_KEY=sk-ant-zzz
PORT=3000

# One command to import everything into the Keychain and rewrite the .env as references:
$ klef import .env --rewrite
ENV VAR             KLEF NAME             VALUE
STRIPE_API_KEY      stripe-api-key        sk_l*** (16 chars)
ANTHROPIC_API_KEY   anthropic-api-key     sk-a*** (12 chars)
PORT                port                  *** (4 chars)
Import 3 key(s)? [y/N] y
✓ STRIPE_API_KEY → klef:stripe-api-key
✓ ANTHROPIC_API_KEY → klef:anthropic-api-key
✓ PORT → klef:port
Imported 3 key(s).
Rewrote .env (3 reference(s) replaced).

$ cat .env
STRIPE_API_KEY=klef:stripe-api-key
ANTHROPIC_API_KEY=klef:anthropic-api-key
PORT=klef:port

# Now run your app like before — klef resolves the references on the fly:
$ klef run -- node app.js
Server on port 3000, Stripe wired ✓

Same thing as a playable recording: asciinema.org/a/5z9zCmNWd1igb3MH (cast source: docs/klef-demo.cast, re-uploadable if asciinema.org goes down; the GIF above is rendered from it with agg).

Install

cargo install klef

Homebrew (macOS / Linux)

Homebrew refuses to load anything from a third-party tap until you trust it, so brew trust is a required step, not an optional one:

brew tap slewinus/tap
brew trust slewinus/tap

brew install klef          # CLI — macOS (Intel + Apple Silicon) and Linux
brew install --cask klef   # macOS menu bar GUI, with the CLI bundled inside the .app

The cask is Apple Silicon only and isn't codesigned yet, so macOS quarantines it on first launch — clear it once with xattr -dr com.apple.quarantine /Applications/klef.app. Signing and notarization are tracked in #123.

Pre-built binaries

Available on the Releases page — macOS Intel + Apple Silicon, Linux x86_64 + ARM. Unpack and move into your $PATH.

Shell auto-completion

# zsh
klef completions zsh > ~/.zfunc/_klef

# bash
klef completions bash > /usr/local/etc/bash_completion.d/klef

# fish
klef completions fish > ~/.config/fish/completions/klef.fish

Static completion of subcommands and flags works today. Dynamic completion of key names (e.g. klef get <TAB>) is tracked in #28 and not yet implemented.

Commands

Command Role
klef add <name> Add a key (TTY prompt or stdin). Use --value-from-file <FILE> for multi-line secrets (PEM, JSON).
klef get <name> Print the value (pipeable).
klef show <name> Value + metadata.
klef list [--format table|json] [-v|--verbose] [--filter PATTERN] List keys (never values). --verbose adds the date added, --filter does substring search.
klef rm <name> (alias remove) Remove a key.
klef edit <name> Change the value or metadata. --value-from-file for multi-line secrets. --note-edit opens $VISUAL/$EDITOR to edit the note.
klef set-note <name> <text> Shortcut for edit --note.
klef rename <old> <new> Rename a key.
klef export <name>... [--format shell|dotenv] Emit export lines.
klef import <file.env> [--prefix P] [--dry-run] [--rewrite] [--yes] Bulk-import from an existing .env. --rewrite replaces literal values with klef: references in the source file.
klef run [--env-file FILE] -- <cmd> Resolve klef:<name> in .env and exec <cmd>.
klef status [--format text|json] Diagnostics: version, backend, index path, key count, desync. Exit 1 on desync.
klef completions <shell> Generate the auto-completion script.

Run klef --help or klef <cmd> --help for the details of each option.

Stack

  • Language: Rust (2024 edition)
  • Storage: native Keychain via keyring — Apple Security framework on macOS, Secret Service on Linux.
  • CLI: clap (derive)
  • No server, no cloud, no account, no telemetry.

Dev

# Setup hooks (run once after clone)
./scripts/setup-dev.sh

# Build / test (cargo workspace: klef-core + klef-cli + klef-gui)
cargo build --workspace
cargo test --workspace --all-features
cargo run -p klef -- --help

GUI (klef-gui)

The Tauri crate has a Svelte frontend that must be bundled before any cargo build/run -p klef-gui (because tauri::generate_context! validates frontendDist at compile time):

cd crates/klef-gui
npm ci                # once
npm run build         # on every frontend change (or use `npm run dev` alongside cargo run)
cd ../..
cargo run -p klef-gui # menu bar mode: icon top-right, click to open (no Dock icon — LSUIElement=true)

macOS: stop the password prompts while developing

A cargo build binary is ad-hoc signed, so its code identity changes with its bytes and the keychain's "Always Allow" never sticks — you get prompted on every klef get. Sign your build with a stable certificate once:

scripts/sign-local.sh

See docs/macos-keychain.md for the full explanation and for the other, unrelated cause (auto-lock timeout, fixed by klef keychain configure).

The git hooks (fmt, clippy, tests, line-cap < 300 lines/file) are versioned under .githooks/. CI on macOS + Ubuntu via GitHub Actions (.github/workflows/ci.yml).

Documentation

For AI agents

klef ships documentation designed for AI assistants:

  • llms.txt: navigation index (following the llmstxt.org convention)
  • llms-full.txt: concatenated doc for single-prompt ingestion
  • docs/llm-usage.md: concrete patterns — decision table, exit codes, JSON outputs
  • docs/mcp.md: MCP server (klef mcp) — let Claude/Cursor use your keys without ever seeing the plaintext value. klef mcp install wires it into Claude Code and Codex in one command.

Claude Code, Cursor and ChatGPT agents can ingest these files and know how to drive klef without hallucinating.

Status

v0.4.4 tagged (2026-08-10) — ships the MCP server that v0.4.0–v0.4.3 claimed to ship but didn't: release.yml never passed --features mcp, so every published tarball lacked the klef mcp subcommand while the binary bundled in klef.app had it. New klef mcp install wires klef into Claude Code and Codex in one command. klef restore --identity can finally read backups made with klef backup --recipient, which until now were written successfully and could never be restored (#59).

v0.4.3 tagged (2026-08-10) — security + performance patch on v0.4.2. klef run and klef edit --note-edit no longer hand KLEF_PASSPHRASE to the child process (in CI, klef run -- npm start was giving the age vault's master key to npm and every postinstall script under it). The age backend now caches the decrypted vault while the file is unchanged: add drops from 7.9s to 4.0s, get from 2.9s to 1.3s (partial fix for #62).

v0.4.2 tagged (2026-05-26) — bug-fix patch on v0.4.1. Three CLI/core fixes from the backlog audit: klef edit <name> no longer clobbers custom env_var (#71), piped commands (klef list | head, etc.) exit cleanly on SIGPIPE instead of panicking (#73), and Store::restore now holds the exclusive lock across both phases to prevent races with concurrent writes (#72). README translated to English (#126).

v0.4.1 tagged (2026-05-11) — security patch on v0.4.0. Five audit findings fixed (shell-safe env_var validation, 0600 perms on metadata, O_EXCL tempfile for --note-edit, GUI dotenv import reworked to keep plaintext on the Rust side, MCP redaction documented as best-effort).

What's new in v0.4 (cumulative):

  • MCP server (klef mcp) — klef_list (metadata) + klef_run (spawn process with klef: refs injected as env vars) for Claude Desktop / Claude Code. Per-rule TOML policy, fail-closed NDJSON audit log, best-effort byte-exact redaction, shell denylist. Closes #24. Doc: docs/mcp.md.
  • macOS GUI (klef.app) — menu-bar app (no Dock icon, LSUIElement=true) with a ⌘⇧K popover. Search, auto-clear copy, drag-and-drop a .env for bulk import + rewrite as klef: refs. The CLI binary is bundled inside the .app for a unified install. Closes #18.
  • klef keychain configure (macOS) — disables Keychain auto-lock to stop the repeated prompts. Idempotent, prints the revert command.
  • One-time banner (macOS) pointing to keychain configure when the Keychain is locked.

Security (v0.4.1):

  • env_var validation (^[A-Za-z_][A-Za-z0-9_]*$) on write and on render — closes the klef export | eval path even for legacy indexes.

  • klef_core::fsx::{write_private, write_inheriting} helper: 0600 on index.json, audit.log, .age.tmp, backup.tmp; source-file perms preserved on .env rewrites.

  • GUI dotenv import: plaintext never returned to the webview, session-id kept on the Rust side with a 5-min TTL, single-use on apply.

  • Supported platforms: macOS (native Keychain + menu-bar GUI) + Linux desktop (Secret Service) + headless Linux / CI / Docker via --backend age:./vault.age (closes #12).

  • Out of scope: Windows, multi-machine sync.

  • Roadmap: see issues by milestone. v0.5+ tracked in #125 (Rust-side clipboard, MCP output_policy, audit log retention).

License

MIT — © 2026 Oscar R.

Dependencies

~15–34MB
~508K SLoC