Skip to content

Repository files navigation

KeyKeeper — Your AI sees the key's name. Never the value.

Website License macOS 14+ Swift 6 Stars Last commit

English · 简体中文

Quick start · How it works · Why not .env · AI tools · Security model · keykeeper.dev


KeyKeeper is a small macOS menu bar app, a keykeeper CLI and two thin SDKs. API keys live in the macOS Keychain, and keykeeper run injects them into the one command that needs them. Claude Code, Cursor, Copilot, cron jobs and scripts only ever see the key's name.

There is no master password, no vault to unlock, no .env to leak into git or chat. Logging into your Mac is the unlock.

KeyKeeper screenshots: credential list, authorization request, add a key

Contents

Docs: keykeeper.dev/docs — getting started, approvals, provider templates, the security model, and the rules agents follow.

Quick start

Requirements: macOS 14+ and the Xcode command line tools (xcode-select --install). Signed binaries and Homebrew are on the roadmap; today you build from source, which takes about a minute.

git clone https://github.com/IvyYang1999/KeyKeeper.git
cd KeyKeeper
./scripts/build-app.sh                       # dist/dmg/KeyKeeper.app + dist/KeyKeeper-<version>.dmg
cp -R dist/dmg/KeyKeeper.app /Applications/
open /Applications/KeyKeeper.app
  1. First run — the setup screen installs the keykeeper CLI (asks for your password once) and shows a one-line prompt that installs the Claude Code skill.

  2. Add a key — click + in the menu bar window, type a name such as OpenAI, paste the value into api-key. The ID (openai) and the environment variable (API_KEY) are shown as you type.

  3. Use it — run anything with the key injected into that process only:

    keykeeper run -c openai -- python script.py     # script reads os.environ["API_KEY"]
    keykeeper run -c openai -- claude               # an AI tool that never sees the value

That is the whole setup. No passphrase to choose, no recovery key to file away.

How it works

sequenceDiagram
    autonumber
    participant AI as AI tool / script
    participant CLI as keykeeper run
    participant App as KeyKeeper app
    participant KC as macOS Keychain
    participant P as your command

    AI->>CLI: keykeeper run -c openai -- python app.py
    CLI->>App: request values (Unix socket)
    App->>App: who is asking? (pid, signature, parent chain)
    App-->>AI: first time only: Allow / Don't Allow
    App->>KC: read the one Keychain item
    KC-->>App: values
    App-->>CLI: values over the socket
    CLI->>P: spawn with API_KEY in the environment
    P-->>CLI: stdout / stderr
    CLI-->>AI: output with any secret replaced by [REDACTED]
Loading
  • Store — add a key in the app, or open a prefilled form with a keykeeper://add?label=…&fields=… link. Secret values go into a single Keychain item, encrypted by macOS and synced to nothing. Names, notes, field names — and any field you mark plain — stay in a plain meta.json.
  • Use — keykeeper run -c <id> -- <command> injects the secret fields as environment variables. Anything the command prints that contains a secret comes out as [REDACTED]. The app starts on demand.
  • Approve — the first time a new terminal session, script or agent asks for a key, KeyKeeper shows who is asking and lets you say yes once. Approvals are listed on the credential's page and can be revoked.
  • Reboot — log in once and everything, cron jobs included, works again. No prompts in day-to-day use.

Why not just .env?

.env file 1Password op run Cloud secret manager KeyKeeper
Where the value lives Plain text in the project 1Password vault Someone's cloud macOS Keychain
What the AI tool sees The value The value, via op The value, via SDK The name only
Unlock Nothing Master password + subscription Account + network Your Mac login
Who may ask Anyone who can read the file Any process with a service-account token Any process with credentials Each calling process, approved once
Output redaction No No No Yes
Offline / cron Yes Needs the agent unlocked Needs network Yes, after login
Cost Free Paid Paid Free, MIT

The moat is the fourth row. Token-based tools hand out a reusable secret that any process can present; KeyKeeper derives identity from the calling process itself, so there is no token to copy around.

Everyday commands

Command What it does
keykeeper list IDs and labels
keykeeper list --detail plus notes and field names (secrets shown as ********)
keykeeper meta <id> one credential as JSON, no values
keykeeper providers / providers show <id> built-in provider contracts: official creation page, complete fields, least privilege and safe validation
keykeeper run -c <id> [-c <id2>] [--prefix PREFIX_] [--verbose] [--tty] -- <command> run a command with the keys injected
keykeeper status is the app reachable (it starts on demand anyway)
keykeeper grants list / grants revoke <id> approvals: which callers may use which keys or logins, for how long
keykeeper requests list approval windows currently waiting
keykeeper get <id> <field> used by the SDKs; refuses to print to a terminal unless --reveal

--tty hands the command a real terminal (editors, TUIs, interactive agents); output redaction is off in that mode.

Who can use a key

Every key group has one of two access modes, shown as a badge in the list:

Badge Meaning Use it for
Background OK (default) Scripts, cron jobs and agents can use the key after you approve each caller once. Anything that runs unattended.
Ask every time Every new terminal session must be approved in the KeyKeeper window while you are at the Mac. Keys you only use by hand.

In Settings you can require approval for new background callers and turn on Launch at Login. KeyKeeper checks once a day for signed updates and tells you when one is available; turn on Install updates automatically if you prefer.

AI tools

Codex and Claude Code plugins

Open Settings → AI tool integration, choose your tool, and run its copied installation command in Terminal. The App carries a self-contained plugin package; no checkout is needed. Start a new conversation afterwards. Installing the plugin grants no credential access. See the installation and safety guide for updates/removal, same-Mac requirements and developer installation. Existing standalone skills aren't overwritten.

The standalone skill below remains available for older installations. Prefer one active source to avoid conflicting instructions; the plugin doesn't automatically remove existing skills.

Claude Code

The skill teaches Claude Code to discover credentials with keykeeper list --detail, run code through keykeeper run, offer a prefilled keykeeper://add?… link when a key is missing, and never ask for or print values.

mkdir -p ~/.claude/skills/keykeeper
curl -fsSL https://raw.githubusercontent.com/IvyYang1999/KeyKeeper/main/skill/keykeeper.md \
  -o ~/.claude/skills/keykeeper/SKILL.md

Or paste the prompt shown on the setup screen into Claude Code and let it do this.

Any other tool

Anything that can run a shell command can use KeyKeeper: keykeeper run -c <id> -- <tool>. When a key is missing, hand the user a link instead of asking them to retype names:

keykeeper://add?label=Stripe&fields=secret-key,publishable-key

SDKs

pip install ./sdk-python
npm install ./sdk-node
from keykeeper import get_key, list_credentials, run
api_key = get_key("openai", "api-key")           # value returned over a pipe
run("openai", ["python", "script.py"])           # same as keykeeper run
const { getKey, runWithSecrets } = require('keykeeper');
const apiKey = await getKey("openai", "api-key");
runWithSecrets("openai", ["node", "server.js"]);

Both SDKs shell out to the keykeeper CLI; no native dependencies.

Security model

Layer What happens
Values at rest Secret fields live in one generic-password item in the macOS Keychain, encrypted by the OS with your login credentials. Fields you mark plain are not secrets and are stored in meta.json as ordinary text.
Unlocking Your macOS login. The keychain stays available while the screen is locked and re-locks at logout/reboot — the same model as Safari passwords, gh, aws-vault and envchain.
Metadata meta.json holds labels, notes and field names in plain text — plus the values of fields you explicitly marked plain (an account id, a team id, a region). Never put a password, token or key in a plain field.
Using a key keykeeper run injects values into the child process's environment and replaces them with [REDACTED] in its stdout/stderr. keykeeper get refuses to print to a terminal.
Who may ask Per-credential mode (Background OK / Ask every time) plus per-caller approvals, shown and revocable in the app. Concurrent requests queue up instead of failing.
Other apps The Keychain item's ACL trusts only KeyKeeper's signing identity; any other program that tries to read it triggers the macOS confirmation prompt.
Clipboard Copying a value from the app marks it concealed for clipboard managers and clears it after 30 s unless you copied something else.
Backup Preserve both the original login Keychain and KeyKeeper application data, and verify that the backup Keychain can be unlocked and its values read. Restoring the application folder alone does not restore keys. Encrypted export is not implemented; see recovery guidance.

What an approval actually covers

When you approve a request, this is the exact scope. Nothing here is inferred — it is what the code checks.

Choice Who it covers What it covers Until
Just this once Only the program that asked Every secret field of that one credential The next successful read
This terminal session Only that program, in that terminal session Same The terminal session ends, or 24 hours, whichever comes first
1 hour Only the program that asked Same One hour later
Always allow Only the program that asked Same You revoke it

Two things worth being precise about:

  • "Only the program that asked" is identified by where it came from, not by a name it gives itself: the first ancestor process with a bundle identifier (so an agent you launched from Terminal is identified as Terminal), otherwise the path of the script or executable. An agent running inside an app is covered by that app's approval.
  • An approval covers the whole credential, not one key. If a credential holds three secret fields, approving a request for one of them lets that program read all three. Split credentials you want to grant separately.

Website sessions use the same three durations and the same per-caller rule, and are stored the same way the login snapshots are — in the Keychain, not in a file. Their windows also end permission on a timer: the window freezes and cannot reach the network until you authorize again.

Approvals issued before KeyKeeper 0.3.4 carry no caller. They keep working, and the first program to use one becomes its owner from then on; after that, nothing else can use it. You can see and revoke every approval in the app.

What KeyKeeper does not do: a child process you approve still receives the value and can misuse, save or transmit it. Output redaction is a safety net, not a sandbox. Only run software you trust with production credentials.

Troubleshooting

Import a key from an Agent's browser

Run keykeeper save -c my-provider --field api_key --from-browser --create. The CLI prints a one-time local receiver link: open it in the same browser session, paste, then confirm once in KeyKeeper. The key is not passed in command arguments or returned to the Agent. The receiver comes with the App, runs on each user's own Mac, and closes after the request; no hosted server or extension is needed.

Omit --create to restore an existing missing field. Values are never overwritten, and new credentials are strict with no read grants. --from-clipboard uses the macOS clipboard instead: website Copy buttons and browser-session clipboards can differ. Desktop/same-Mac only; see usage and limitations.

You see Do
The KeyKeeper app could not be started Open KeyKeeper from Applications once; check it isn't blocked by Gatekeeper.
This caller is not authorized Click Authorize in the KeyKeeper window, or set the credential to Background OK. keykeeper grants list shows approvals.
Timed out … waiting for approval Run it again while you're at the Mac; approval windows wait 2 minutes.
Cron jobs fail right after a reboot Log in once; jobs work from then on (the login keychain unlocks with your session).
macOS asks about "confidential information" You rebuilt KeyKeeper with a different signing identity. Click Always Allow once, or set a stable identity in .signing-identity (see scripts/build-app.sh).
Refusing to print a secret to the terminal Use keykeeper run, or add --reveal if you really want it on screen.
Setup says the CLI is out of date Click Update CLI (Settings › Command line) so CLI and app come from the same build.

Project structure

KeyKeeper/
├── Sources/
│   ├── KeyKeeperCore/   # Keychain blob store, metadata, grants, IPC protocol
│   ├── KeyKeeperCLI/    # keykeeper: list, get, meta, run, status, grants, requests, migrate-storage
│   └── KeyKeeperApp/    # menu bar app (SwiftUI): credentials, approvals, settings
├── sdk-python/          # Python SDK (wraps the CLI)
├── sdk-node/            # Node.js SDK (wraps the CLI)
├── skill/               # Claude Code skill
├── scripts/             # build-app.sh, install-cli.sh, git hooks
├── Tests/               # XCTest suites (swift test)
└── Package.swift

Roadmap

  • Signed, notarized DMG on GitHub Releases and a Homebrew cask
  • Encrypted export / import for moving keys between Macs
  • Chinese UI for the app (the website is already bilingual)
  • Signed in-app updates
  • Per-caller approval with process identity
  • keykeeper://add deep links for AI tools

Contributing

  1. Fork and branch: git checkout -b my-feature
  2. Bug fixes come with a test first (swift test runs in the pre-commit hook).
  3. Keep commits atomic and open a pull request with what changed and why.

License

The app, CLI and core are under the Functional Source License, Version 1.1, MIT Future License (FSL-1.1-MIT): the source is public, you may read, build, modify and use it for anything except offering a competing product or service, and each version becomes plain MIT two years after its release. The Node SDK, Python SDK, the agent skill and the Codex / Claude Code plugin stay MIT, so anything that talks to KeyKeeper can be embedded without a second thought. Releases up to 0.3.4 were published under MIT and remain so.

About

Secure API key management for AI coding tools

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages