All your secrets.
One vault. Zero network.

Keyr is a local-first CLI vault for API keys and credentials — AES-256-GCM encrypted, scrypt-hardened, stored in a single file at ~/.keyr/vault.json, and injected into any tool with keyr run. Nothing ever leaves your machine.

Star on GitHub

Node ≥ 18 · Windows / macOS / Linux · no account · no sync · no telemetry

keyr — ~/.keyr/vault.json AES-GCM
vault: 1 filenetwork calls: 0telemetry: 0

CAPABILITIES

A vault with a man page.

Standard primitives, explicit errors, atomic writes — and not a single surprise hiding behind a flag.

crypto

AES-256-GCM

Authenticated encryption straight from node:crypto — no external crypto dependency, no GPG. A wrong passphrase or a tampered file fails fast: ENCRYPTION_FAILED, with zero technical detail leaking out.

iv
tag✓ verified
ct
kdf

scrypt-hardened keys

Your passphrase never becomes the key directly. scrypt derives a 32-byte AES key with a random salt generated per vault — brute-forcing the ciphertext gets deliberately expensive.

N=16384r=8 p=1keylen=32salt=per-vault
env

keyr run

Spawn any command with your secrets injected into its environment — plaintext exists only in the child process's memory, never back on disk.

$keyr run -- <command>
tui

Interactive TUI

Run keyr bare for a menu-driven session. It requires a real TTY — non-interactive pipes are politely rejected.

net=0

Zero network

There is no network code in Keyr at all. No telemetry, no sync, no account — nothing to opt out of, nothing to leak.

telemetrynone
syncnone
accountsnone
0 network calls — ever
storage

One file, locked down

The whole vault is ~/.keyr/vault.json — written atomically, chmod 0o600 best-effort, user-scoped ACLs on Windows. Repoint it with KEYR_HOME for sandboxes and tests.

vault.json
{ "version":1, "kdf":"scrypt", … }
0o600atomic write
random

Fresh IV, every write

A new IV is generated on each save — two writes of the same plaintext never produce the same ciphertext.

write 19f4ce2a1b7d05538
write 2
same plaintext · same salt · new IV
src

Small, readable source

A codebase you can audit over coffee — Commander, Ink, ora, esbuild. Nothing exotic.

src/ ├─ index.js entry · spinner · errors ├─ lib/crypto.js aes-256-gcm + scrypt ├─ lib/store.js vault io · KEYR_HOME ├─ commands/run.js spawn + env injection └─ ui/ ink tui

LIFECYCLE

From passphrase to sealed file.

One passphrase becomes one AES-256 key — and your secrets become one authenticated file. The salt persists; the IV never repeats.

passphrase masked prompt · never a CLI arg
scrypt + salt → 32-byte AES key
AES-256-GCM fresh IV · authTag
vault.json atomic write · 0o600
vault.json — sealed on disk
{
  "version":    1,            // vault format
  "kdf":        "scrypt",     // N=16384 · r=8 · p=1
  "salt":       "qK1e…",      // random · per vault
  "iv":         "8Mwz…",      // fresh on every write
  "authTag":    "tX9p…",      // GCM integrity
  "ciphertext": "kJ8s…"       // your secrets, sealed
}
payload — in memory only
{
  "secrets": {
    "OPENAI_API_KEY":    "sk-…",
    "GEMINI_API_KEY":    "…",
    "ANTHROPIC_API_KEY": "sk-ant-…"
  }
} // plaintext exists only while a command runs

Flip one byte of ciphertext and the auth tag fails the whole decryption — tampering is a hard stop, not a warning.

ENV INJECTION

One vault, every tool.

keyr run spawns your command with secrets injected straight into its environment — plaintext lives only in the child process's memory.

  1. 01

    Decrypt

    The passphrase arrives via a masked prompt, or KEYR_PASSPHRASE for scripts — never as a CLI argument, so it stays out of shell history and logs.

  2. 02

    Spawn & inject

    stdio: 'inherit' keeps TUIs, colors and Ctrl-C intact; shell: true resolves .cmd shims on Windows; env merges your secrets in by name.

  3. 03

    Exit honestly

    The child's exit code propagates — so $? and $LASTEXITCODE stay meaningful inside your scripts.

commands/run.js
spawn(command, {
  stdio: 'inherit',   // TUI · colors · Ctrl-C — untouched
  shell:  true,         // resolves .cmd shims on Windows
  env:    { ...process.env, ...secrets }  // name → env var
})
// the child's exit code is inherited — scripts stay honest

IF IT READS CREDENTIALS FROM ENV, ONE VAULT CAN FEED IT

OpenCode Claude Code Codex Gemini CLI Aider Goose
"apiKey": "{env:HIVE_API_KEY}"

Tools reference the name, never the value — substitution happens in memory when the process starts. The vault never writes plaintext back to disk.

THE VERBS

Speak fluent shell.

Nine verbs with explicit error codes — VAULT_EXISTS, SECRET_NOT_FOUND, ENCRYPTION_FAILED — and nothing hidden behind flags you'll never find. Click any command to copy it.

  • Create a fresh vault. Refuses to clobber an existing one — VAULT_EXISTS.

  • Add or update a secret. Decrypt → mutate → re-encrypt with a fresh IV → atomic write.

  • Print one secret. Misses are explicit: SECRET_NOT_FOUND / VAULT_NOT_FOUND.

  • Every secret name in the vault — the values stay sealed.

  • Remove a secret. Asks y/N unless --yes is passed for scripts.

  • Take your secrets with you.

  • Deletes the vault permanently. There is no recovery — by design.

  • Spawn a command with secrets injected into its environment; the child's exit code is inherited.

  • Bare invocation opens the interactive TUI menu. Requires a real TTY — pipes are politely rejected.

HONEST LIMITS

The fine print, in bold.

Keyr is explicit about what it isn't. No overclaims — that's the whole point.

Not a kernel boundary

Malware running as your user can still read the child's environment and keystrokes. Keyr moves secrets from permanent storage to session memory — it narrows the window of exposure, it doesn't build a wall.

The vault is as strong as the passphrase

scrypt makes brute-force expensive, but a short passphrase is still the weak link. Choose long.

Forgetting is fatal

The passphrase is never stored anywhere — not on disk, not in a log. Lose it and the data is gone. There is no recovery, by design.

Not yet audited

The primitives are standard and correctly used — but Keyr has not been through an independent security audit. Honest today; better later.

Keep your keys where your code lives.

One file. One passphrase. Every tool.

$npm install -g @rillwz/keyr

Node ≥ 18 · Windows / macOS / Linux — and yes, this page is a single static file.