Your agent needs an API key. Normally you'd paste it into the chat, where it ends up in the context window and every log. keyhole gives the agent a reference instead.
$claude plugin add github:maferland/keyhole
Runs on Node · macOS, Linux · MIT licensed
You — on localhost
Your agent — all it ever sees
127.0.0.1:54213/s/8f3a2c…
Your agent needs a secret
OpenAI key for the ingest script
OPENAI_API_KEY
sk-••••••••••••••••••
localhost only · single-use · value never leaves this machine
agent session
I need OPENAI_API_KEY to run the ingest script.
→ opened keyhole on localhost, waiting…
✓ stored
{
"name": "OPENAI_API_KEY",
"dest": keychain:OPENAI_API_KEY,
"retrieve": security find-generic-password -s OPENAI_API_KEY -a $USER -w
}
Got a reference — not the key.
encrypted at rest · macOS Keychain
↑ switch destinations. the retrieve command changes. the key itself never shows up.
The old way
you: sk-live-1a2b3c…
↳ now in the context window, the transcript, and every log line
Once it's in the chat, you can't get it back out. Rotate the key and move on.
The keyhole way
agent: keychain:OPENAI_API_KEY
↳ a reference it expands only at runtime. The value is elsewhere.
The secret lands in your Keychain. The agent never touches it, only a pointer.
How it works
01
Agent asks
It runs keyhole NAME instead of asking you to paste a key into the chat.
02
You type it
A form opens on a random localhost URL. The value goes straight to your chosen store.
03
Agent gets a ref
It gets a one-line retrieve command and looks up the key only when it needs it.