Claude Code on alex-laptop wants to POST to api.stripe.com/v1/refunds
payment_intent=pi_3Mt7XoLkdIwHu7ix0kjN2wKg
amount=4200
reason=requested_by_customer
metadata[ticket]=SUP-1187
mehen.dev
A credential vault and proxy for AI agents. Agents get capabilities, never secrets.
Mehen makes the call for the agent, returns the response scrubbed, and asks a human before anything risky goes out.
$ curl -fsSL https://mehen.dev/install | sh
$ mehen run -- bun dev
STRIPE_SECRET_KEY=mehen://stripe-live/b07e1a
ready on http://localhost:3000
allowed GET api.stripe.com/v1/balance 188 ms
held POST api.stripe.com/v1/refunds
waiting for Alex's passkey
allowed POST api.stripe.com/v1/refunds
approved. The key never left.
Get started
You need an invite from a workspace owner first. Your agent does the setup. You, or a workspace owner, approve the machine with a passkey.
Set me up with Mehen by following https://mehen.dev/setup.md
Or install it yourself: curl -fsSL https://mehen.dev/install | sh, then mehen setup. Read the setup guide.
Where keys leak today
Once the model has seen the value, masking it in the output changes nothing. It is already in too many places.
.envRead by every tool with file access.the transcriptStored with the provider, forever.the logsRequest bodies, tool results, stack traces.shell historyOne export, kept for years.prompt injectionA page that asks nicely gets the key.How it works
The key crosses from the human to the vault once. After that the agent only ever handles a placeholder or a scrubbed response.
The agent needs a key, so it asks. A human gets an intake link and types the key into the vault's page, never into the chat.
The vault makes the call and returns the response, scrubbed. Or mehen run hands your program a placeholder that becomes the real key only on the way to the right host.
OPENAI_API_KEY=mehen://openai-personal/4f91c2
STRIPE_SECRET_KEY=mehen://stripe-live/b07e1a
api.openai.com and api.stripe.com only.A write, or anything in production, waits for a person who sees the exact request and approves it with a passkey. Agents cannot approve.
Approvals
The exact request, the binding it runs under, and why it waited. One passkey tap, and the agent carries on. Nothing is sent until then.
payment_intent=pi_3Mt7XoLkdIwHu7ix0kjN2wKg
amount=4200
reason=requested_by_customer
metadata[ticket]=SUP-1187
The vault
Each credential is bound to hosts, paths and methods. Sent anywhere else, it is refused, and the refusal says the fix.
POST /v1/chat/* POST /v1/embeddingsplaceholderGET /v1/* POST needs approvalrelayread sendoauthGET /repos/* POST /repos/*/statusesrelayThe CLI and MCP
Run your program under mehen run and SDKs work unchanged. Over MCP, the agent names the capability, never the value. Every refusal says the fix.
$ mehen request stripe
mehen intake link sent to Alex: mehen.dev/i/7h3q
mehen expires in 15 min. The key is typed into the
vault's page, never into this chat.
$ mehen connect gmail
mehen opening Google sign-in for Alex
mehen connected gmail-alex read, send
tokens stay in the vault
$ mehen run -- bun dev
mehen OPENAI_API_KEY=mehen://openai-personal/4f91c2
ready on http://localhost:3000
mehen allowed POST api.openai.com/v1/chat/completions
mehen refused openai-personal is bound to api.openai.com.
Nothing was sent.
fix: ask a human at mehen.dev/a/7KQ2
// the agent names the capability, never the value
{
"tool": "vault_fetch",
"arguments": {
"item": "openai-personal",
"method": "POST",
"url": "https://api.openai.com/v1/chat/completions",
"body": { "model": "gpt-5",
"input": "Summarise the log" }
}
}
// the response comes back scrubbed
{
"status": 200, "scrubbed": true,
"body": { "output": "Six crossings, two refused,
one waiting." }
}
$ mehen enrol
mehen this machine is quiet-heron-lamp
mehen match the words at mehen.dev/machines to approve it
Everything the vault does
Hosts, paths and methods per credential. Anywhere else is refused.
api.stripe.com/v1/*GETPOST needs approvalapi.openai.com/v1/chat/*/v1/embeddingsPOSTThe vault makes the HTTP call and returns the response, scrubbed.
200 in 188 mskey never leftmehen run gives programs a stand-in that becomes real only at the right host.
OPENAI_API_KEY=mehen://openai-personal/4f91c2Risky calls wait for a passkey. Agents cannot approve.
held POST api.stripe.com/v1/refundswaiting for AlexThe human types the key into the vault's page, never into the chat.
mehen.dev/i/7h3qexpires in 15 min"Connect my Gmail": tokens go to the vault, the agent gets the capability.
connected gmail-alexread, sendEach device holds its own key, approved by matching fingerprint words.
Who, which credential, where, when, what was decided. Every time.
23:39:18 codex@laptop stripe-liveheld POST /v1/refundsNo dead ends: each "no" carries the link to open or the command to run. Nothing is sent in the meantime.
refused openai-personal is bound to api.openai.com. Nothing was sent.fix: ask a human at mehen.dev/a/7KQ2Install in a minute. Free for one person; a link covers the rest.
The serpent coils round the sun, and nothing gets past.