Skip to main content
Glama
DINAKAR-S

keywarden

by DINAKAR-S

keywarden

Your AI agent can use your API keys. It can never read them.

keywarden is a local, encrypted credential vault that speaks MCP. Claude Code, Claude Desktop, Cursor, or any MCP client connects to it and gets two capabilities: make an authenticated API call, and run a command with credentials in its environment. Neither one ever puts the credential itself into the model's context.

There is no get_secret tool. That absence is the whole product.

   agent                keywarden                    upstream
     |                     |                          |
     |  "POST /v1/chat     |                          |
     |   using openai/prod"|                          |
     |-------------------->|                          |
     |                     | check policy             |
     |                     | decrypt key              |
     |                     | attach Authorization     |
     |                     |------------------------->|
     |                     |<-------------------------|
     |  response only      | scrub any key from body  |
     |<--------------------| append to audit log      |

Why

Right now the normal way to let an agent use your OpenAI key is to put the key in a .env file and let the agent read it. The moment it does, the key is in a model's context window. From there it is in a provider's logs, possibly in a training set, possibly in a crash report, and definitely in your own transcript history that you will paste into a bug report six months from now.

Rotating a key is annoying. Not knowing whether it leaked is worse.

keywarden removes the step where the model sees the key at all.

Related MCP server: AgentPay MCP Server

Install

npm install -g keywarden

Node 20.10 or newer. Two runtime dependencies: the MCP SDK and zod. No native modules, no compiler, no daemon.

Quickstart

keywarden init --passphrase
keywarden add openai/prod --provider openai
keywarden mcp-config

init creates ~/.keywarden/ with an encrypted vault and a deny-by-default policy. add prompts for each field, so nothing lands in your shell history. mcp-config prints the block to paste into your MCP client.

Then, in Claude Code:

Call the OpenAI models endpoint with my prod key and tell me which ones I have access to.

The model calls http_request with ref: "openai/prod". keywarden attaches the key, makes the call, returns the response. Ask it to print the key and it will tell you it cannot.

The tools an agent gets

Tool

What it does

list_secrets

Metadata only: refs, providers, field names, last used. Never values.

describe_secret

One credential plus how it may be used, which hosts, which env vars.

list_providers

Built-in presets and what each one expects.

http_request

Authenticated HTTPS call. keywarden attaches the credential.

run

Spawn a local process with credentials injected as env vars.

audit_tail

Recent entries from the tamper-evident log.

Set KEYWARDEN_DISABLE_EXEC=1 to drop run entirely and expose only the HTTP proxy.

Three surfaces, one authorisation model

The same vault, policy engine, grants and audit log are reachable three ways. Which one you use changes nothing about what is allowed.

surface

for

how the caller is identified

MCP (stdio)

Claude Code, Claude Desktop, Cursor

the client that spawned the server

CLI

you, at a terminal

filesystem access to the vault

HTTP (loopback)

any language, CI, a script, a web UI

a scoped keywarden API key

The HTTP surface is what makes keywarden usable from code that does not speak MCP, and it is the first place keywarden can tell one caller from another:

keywarden apikey create ci-runner --ref 'openai/**' --http --audit --ttl 30d
keywarden serve --port 8787
curl -s http://127.0.0.1:8787/v1/proxy/openai%2Fprod \
  -H "Authorization: Bearer kw_live_..." \
  -H "content-type: application/json" \
  -d '{"method":"POST","url":"/v1/chat/completions","body":{"model":"gpt-4o","messages":[]}}'

The caller holds a keywarden key scoped to openai/**, carrying only the capabilities it was granted, expiring in 30 days, revocable in one command. It never holds the OpenAI key. Routes: /v1/secrets, /v1/secrets/:ref, /v1/proxy/:ref, /v1/run, /v1/audit, /v1/usage, /v1/whoami, /healthz.

The server binds 127.0.0.1 and refuses a routable interface without --allow-remote, because anyone who can reach that port gets an authorisation oracle for every credential the key covers.

Who used what, and what it cost

Every entry in the audit log names an actor, and the actor is inside the hash, so attribution cannot be rewritten without breaking the chain. Every proxied response is parsed for provider-reported token counts.

keywarden usage --since 7d
CREDENTIAL        CALLS          IN         OUT       TOTAL
openai/prod         142     418,220      96,410     514,630
anthropic/prod       38      92,004      31,887     123,891

ACTOR             CALLS          IN         OUT       TOTAL
http:ci-runner      118     356,900      74,220     431,120
mcp:mcp-client       62     153,324      54,077     207,401

keywarden records tokens, not money. Prices change, differ by contract, and a stale hardcoded rate produces a confident wrong number in a finance report. Note that run cannot be metered: once a credential is inside a subprocess, keywarden sees an exit code, not a token count.

Two ways to use a credential

Proxy, for HTTP APIs. The agent describes a request, keywarden attaches the credential and makes the call. Works for OpenAI, Anthropic, Stripe, GitHub, Slack, Cloudflare, Vercel, Supabase, and any API that authenticates with a header or a query parameter.

// what the agent sends
{ "ref": "openai/prod", "method": "POST", "url": "/v1/chat/completions", "body": { "model": "gpt-4o", "messages": [] } }

Inject, for everything else. AWS needs SigV4 request signing, a Postgres URL is not HTTP at all, and terraform apply wants real environment variables. keywarden spawns the process itself:

{ "command": "aws", "args": ["s3", "ls"], "inject": ["aws/prod"] }

The child process gets AWS_ACCESS_KEY_ID and friends. The model gets stdout, with any credential appearing in it masked on the way out.

Policy

~/.keywarden/policy.json decides which credential may be used, by which capability, against what. Rules are evaluated top to bottom, first match wins, and the default is deny.

{
  "version": 1,
  "default": "deny",
  "redactResponses": true,
  "rules": [
    {
      "ref": "openai/**",
      "http": { "allow": true, "methods": ["POST"], "paths": ["/v1/**"] },
      "exec": { "allow": false, "commands": [] },
      "rateLimitPerMinute": 30
    },
    {
      "ref": "aws/prod",
      "http": { "allow": false },
      "exec": { "allow": true, "commands": ["aws", "terraform"] },
      "rateLimitPerMinute": 10,
      "expiresAt": "2026-12-31T00:00:00.000Z"
    }
  ]
}

Or from the CLI:

keywarden policy allow "openai/**" --http --path "/v1/**" --method POST
keywarden policy allow aws/prod --exec aws --exec terraform --arg-deny "s3://*"
keywarden policy test aws/prod exec terraform

* matches inside one path segment, ** spans segments. expiresAt makes a rule temporary.

Naming a command is not enough on its own. Allowlist aws for aws s3 ls and the same binary does aws s3 cp into a bucket someone else owns. That is the sequence-level gap the MCP threat literature keeps pointing at: every individual call is authorised, and the combination is the exfiltration. So rules constrain arguments too:

"exec": {
  "allow": true,
  "commands": ["aws"],
  "argsDeny": ["s3://*", "--endpoint-url"],   // any match refuses the call
  "argsAllow": ["s3", "ls", "--region", "*"]  // if set, every argument must match
}

Grants: temporary, expiring, use-capped access

Policy is standing configuration. It is the wrong shape for "let the agent do this one thing, now, for fifteen minutes", which today means widening a rule and forgetting to narrow it again.

A grant is a capability that carries its own restrictions, borrowed from the macaroon and biscuit line of work, and issued by you at a terminal:

keywarden grant aws/prod --exec aws --ttl 15m --uses 5 --arg-deny "s3://*"
keywarden grant openai/prod --http --path "/v1/chat/**" --method POST --ttl 1h --uses 20
keywarden grant list
keywarden grant revoke <id>

Grants expire on their own, die when their use budget runs out, and are HMACed with a key derived from your vault, so a hand-edited grants.json is rejected rather than honoured. A denied attempt does not burn a use.

Set "requireGrant": true on a policy rule and standing configuration becomes necessary but not sufficient: nothing happens until you issue a grant. That is the human-in-the-loop approval step, without needing an interactive prompt inside a stdio server.

policy

grant

result

allows, no requireGrant

allow

allows, requireGrant

live match

allow

allows, requireGrant

none

deny

denies

live match

allow

denies

none

deny

Config integrity

Encrypting the secrets is half the job. policy.json decides whether a credential may be used and providers.json decides where it is sent. Both are plain files. Someone who cannot decrypt a single byte can still add a provider whose hosts are theirs and repoint your credential at it.

So the vault pins a hash of both files, and refuses to act on either until you have looked at the change:

keywarden trust show    # what drifted
keywarden trust         # review, then pin the current contents

The vault file itself is MACed as a whole, not just per-field, because flipping provider: "openai" to something else never touches a ciphertext and would otherwise verify cleanly.

What keywarden actually enforces

  • No plaintext tool. The MCP surface has no code path that returns a credential value.

  • Egress allowlist. A credential can only be sent to hosts its provider declares, plus any you add in policy. A prompt injection telling the agent to POST your key to attacker.example fails at the host check, before the network is touched.

  • HTTPS only, no redirect following. A 302 to another origin will not replay your Authorization header off-host.

  • SSRF guard. Loopback, private ranges, CGNAT, and link-local (which covers the 169.254.169.254 cloud metadata endpoint) are blocked, and the address is validated in the DNS lookup that the socket actually uses, so DNS rebinding does not open a window.

  • No shell. run passes an argv array to spawn with shell: false. There is no metacharacter parsing to inject into.

  • Constructed child environment. The child gets an allowlist of inherited variables plus the injected ones. Your other secrets, and keywarden's own passphrase, are not inherited.

  • Output redaction. Every tool result is scanned for known credential values, their base64 and URL-encoded forms, and about a dozen well-known key shapes. Defence in depth, not the primary control.

  • Tamper-evident audit. Every decision, allow or deny, is appended to a hash-chained log. keywarden audit verify recomputes the chain and reports the first modified or deleted entry.

  • Whole-file integrity. The vault is MACed including its metadata, so a credential cannot be repointed at another provider without detection. policy.json and providers.json are hash-pinned to the vault and refused when they change out of band.

  • Environment hardening. The server refuses to start when NODE_TLS_REJECT_UNAUTHORIZED=0, NODE_OPTIONS, or SSLKEYLOGFILE are set, and warns on NODE_EXTRA_CA_CERTS and HTTPS_PROXY. CVE-2026-21852 against Claude Code was one environment override that redirected outbound traffic with the Authorization header attached; a process whose job is attaching credentials must not start when the request path is under someone else's control.

  • Argument constraints. argsAllow / argsDeny narrow which invocations of an allowlisted command are permitted, not just which binary.

  • Attenuated grants. Expiring, use-capped, operator-issued capabilities, forgery-resistant via a vault-derived MAC.

  • Untrusted-data framing. Proxied response bodies are labelled as untrusted content from a named host, so an injected instruction in an API response is presented to the model as data.

Crypto

Envelope encryption, all from node:crypto, no third-party crypto libraries.

  • A random 256-bit data key encrypts each field with AES-256-GCM, with the credential's ref and field name as additional authenticated data, so a ciphertext cannot be moved between vault entries.

  • The data key is wrapped by a key derived from your passphrase with scrypt at N=2^17, r=8, about 128 MiB and roughly a second per attempt. That is deliberate: the vault file is what an attacker walks off with, so offline guessing has to be expensive.

  • Rotating your passphrase rewraps 32 bytes. It does not re-encrypt every secret.

Vault modes

--passphrase is the strong one. The MCP server needs KEYWARDEN_PASSPHRASE in its environment to unlock without a prompt.

--keyfile writes a random key to ~/.keywarden/masterkey so nothing has to prompt. It is convenient, and it means anyone who can read your home directory can open the vault. It is still far better than plaintext .env files scattered across projects, because the key is in one place, its use is policy-gated, and every use is logged. Know which trade you made. keywarden doctor will remind you.

On Windows, file modes are set but not enforced the way POSIX enforces 0600. See THREAT_MODEL.md.

CLI

keywarden init --passphrase|--keyfile   create the vault
keywarden doctor                        check the install, flag weak settings
keywarden trust [show]                  re-pin policy.json + providers.json after reviewing a change
keywarden grant <ref> ...               issue a temporary, use-capped capability
keywarden grant list | revoke <id>
keywarden add <ref> --provider <id>     store a credential (prompts for each field)
keywarden list                          metadata only
keywarden describe <ref>                metadata plus how it can be used
keywarden reveal <ref>                  print plaintext, asks first, always audited
keywarden rm <ref> [--field f]          delete
keywarden exec <ref[,ref]> -- <cmd>     run a command with credentials injected
keywarden policy show|init|allow|deny|test
keywarden audit [tail|verify]
keywarden passphrase                    rotate
keywarden providers                     built-in presets
keywarden mcp-config                    print the MCP client config
keywarden doctor                        check the install, flag weak settings

Custom providers

Anything not built in goes in ~/.keywarden/providers.json. See docs/PROVIDERS.md.

{
  "acme": {
    "label": "Acme Internal API",
    "hosts": ["api.acme.internal", "*.acme.io"],
    "baseUrl": "https://api.acme.io",
    "fields": ["token", "tenant"],
    "required": ["token"],
    "auth": { "type": "header", "name": "X-Acme-Key", "template": "{{token}}" },
    "env": { "ACME_TOKEN": "{{token}}", "ACME_TENANT": "{{tenant}}" }
  }
}

What keywarden does not protect you from

Read THREAT_MODEL.md before you trust it with anything expensive. The short version:

  • If the agent can run arbitrary local commands through some other tool, it can read your vault file and, in keyfile mode, your master key. keywarden protects the model's context, not your disk.

  • run gives the credential to a real process. If you allowlist a command that can be steered into exfiltrating its own environment, the credential leaves. Allowlist narrowly.

  • Redaction is a safety net with holes. A credential that an API returns re-encoded in a way we do not recognise will not be caught.

  • keywarden does not stop an agent from doing something expensive or destructive with a credential it is legitimately allowed to use. That is what policy scoping and rate limits are for.

Development

npm install
npm run build
npm test          # 89 unit tests + 46 end-to-end checks against the real CLI, MCP and HTTP servers

The e2e suite drives the actual binaries in a throwaway KEYWARDEN_HOME and asserts, among other things, that no tool response contains the credential.

Reading

  • THREAT_MODEL.md — what is in scope, and what is honestly not

  • docs/RESEARCH.md — the 2026 literature this design is drawn from, what was adopted, and what was considered and rejected

  • docs/COMPETITORS.md — the landscape, and where keywarden is genuinely different rather than just differently marketed

  • docs/TEAM.md — the multi-developer architecture: identity, key sharing without a readable server, approval workflow, cost accounting, and the build order

  • docs/PROVIDERS.md — writing a custom provider

Hosted

A hosted version is planned for people who want a team vault, browser-based management, and sync across machines, with the same zero-exposure guarantee. Everything in this repository stays MIT and stays fully usable standalone. See docs/HOSTED.md.

License

MIT

Install Server
A
license - permissive license
A
quality
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • A
    license
    A
    quality
    A
    maintenance
    Credential isolation proxy for AI agents. Injects API keys at the network boundary so your agent never sees the raw credential. Supports domain allowlists, agent auth, policy enforcement, and audit logging.
    3
    89
    13
    Apache 2.0
  • F
    license
    Not graded
    quality
    A
    maintenance
    Provides a trust and governance layer for AI agents, enabling secure API access, credential vaulting, paid execution with human approval, and automatic call resume.
    8
    2
  • A
    license
    A
    quality
    A
    maintenance
    Identity and credential governance for AI agents. Every agent gets its own cryptographic identity, scoped short-lived credentials per platform, human approval on sensitive actions, and an immutable audit log.
    7
    1
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables AI agents to securely perform privileged actions like creating GitHub issues by minting short-lived, single-purpose tokens on demand, with policy enforcement and audit logging.
    MIT

View all related MCP servers

Related MCP Connectors

  • Issue, rotate and revoke scoped API-key passes for 25+ providers — the agent never sees a real key

  • Encrypted secret store and rotation for autonomous agent credentials

  • Operate Linux, macOS and Windows from your LLM. Every action runs through an auditable allowlist.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/DINAKAR-S/keywarden'

If you have feedback or need assistance with the MCP directory API, please join our Discord server