Secure middleware that gives AI agents access to secrets stored in vault providers (macOS Keychain, gopass, 1Password), with per-session caching and vault-delegated authorization (Keychain, GPG passphrase, 1Password desktop app).
curl -fsSL https://github.com/lorem-dev/locksmith/releases/latest/download/install.sh | shPin a specific version:
LOCKSMITH_VERSION=v0.4.0 curl -fsSL https://github.com/lorem-dev/locksmith/releases/download/v0.4.0/install.sh | shCustom install dir (default ~/.local/bin):
LOCKSMITH_INSTALL_DIR=/usr/local/bin curl -fsSL https://github.com/lorem-dev/locksmith/releases/latest/download/install.sh | sudo shRe-running the same command updates an existing install in place and
refreshes bundled plugins / locksmith-pinentry. Supported platforms:
linux/amd64, linux/arm64, darwin/arm64. On Intel Macs (darwin/amd64)
the install script prints go install instructions instead of
fetching a prebuilt binary. See
docs/install.md for manual download, GPG signature
verification, build-from-source, the go install fallback, and the
full list of install-script flags. Plugin and pinentry extraction
happens on first locksmith init; see PLUGINS.md.
locksmith initlocksmith init is an interactive wizard: it detects your available vaults
(Keychain, gopass, 1Password), writes a starter config, and installs hooks for the AI
agents you use (Claude Code, Codex, Gemini CLI, OpenCode) so they get
a LOCKSMITH_SESSION automatically. The daemon is started by the installed
shell hook on the next shell session.
After init, your config lives at ~/.config/locksmith/config.yaml. A
minimal example with three vaults (gopass for work, macOS Keychain for
personal, 1Password for shared team secrets) and four named keys:
defaults:
session_ttl: 3h
vaults:
work:
type: gopass
store: work-secrets
personal:
type: keychain
service: locksmith # macOS only; Keychain service name
op:
type: 1password
vault: Personal # default 1Password vault for non-qualified paths
keys:
github-token:
vault: work
path: github/personal-access-token
openai-key:
vault: work
path: ai/openai-api-key
slack-webhook:
vault: personal
path: slack-incoming-webhook
notion-token:
vault: op
path: Notion # resolves to op://Personal/Notion/password
mcp:
servers:
github:
command: ["npx", "-y", "@modelcontextprotocol/server-github"]
env:
GITHUB_TOKEN: github-token # alias from keys:
my-api:
url: https://api.example.com
transport: auto # sse | http | auto (default: auto)
headers:
Authorization: "Bearer {key:openai-key}"Retrieve a secret by alias:
locksmith get --key github-tokenReload after editing the file (the daemon also auto-detects changes within ~1 second):
locksmith reloadSee the Configuration Reference for all fields, vault types, defaults, logging, and security notes.
locksmith mcp run injects secrets into MCP servers at startup. No shell
substitution required - all values in mcp.json are static strings.
Local MCP server (secret injected as environment variable):
{
"mcpServers": {
"github": {
"command": "locksmith",
"args": ["mcp", "run", "--env", "GITHUB_TOKEN=github-token", "--", "npx", "-y", "@modelcontextprotocol/server-github"]
}
}
}Remote MCP server (secret injected as HTTP header, stdio<->HTTP proxy):
{
"mcpServers": {
"my-api": {
"command": "locksmith",
"args": ["mcp", "run", "--url", "https://api.example.com", "--header", "Authorization=Bearer {key:openai-key}"]
}
}
}Named server from config (secrets configured in ~/.config/locksmith/config.yaml):
{
"mcpServers": {
"github": {
"command": "locksmith",
"args": ["mcp", "run", "--server", "github"]
}
}
}| Format | Description |
|---|---|
alias |
Key alias from keys: in config.yaml (for --env only) |
{key:alias} |
Key alias (template form, works in --header and --env) |
{vault:vault-name path:some/path} |
Direct vault + path, no alias required |
locksmith mcp run supports both SSE (legacy) and Streamable HTTP (MCP spec
2025-03-26). Use --transport sse|http|auto (default: auto). In auto mode,
Streamable HTTP is tried first; on 404/405 the proxy falls back to SSE.
locksmith mcp run does not contact the vault at startup. The first
vault prompt (Keychain dialog, GPG passphrase) for a given MCP server
fires only when the AI client sends its first MCP request to that
server. MCP servers configured but never used in a session never
trigger a prompt.
In local mode (-- command) locksmith stays resident as the parent
of the MCP server child. The process tree becomes:
AI client -> locksmith mcp run -> <MCP server binary>
This adds roughly 5-10 MB RAM per server. Signals (SIGTERM, SIGINT) sent by the AI client to locksmith are forwarded to the child so graceful shutdown works as expected.
In proxy mode, locksmith goes one step further: templated Authorization
(or other vault-referencing) headers are not sent on the first request.
If the remote MCP server accepts the handshake unauthenticated (a
common pattern - initialize and capability negotiation usually do
not require auth), no vault prompt fires. Locksmith only resolves and
sends the auth headers after the server responds 401 or 403,
after which it remembers the resolved value for the rest of the
proxy session.
The optional long-lived SSE GET stream that some servers use for
notifications is opened with the headers available at that time.
If a server happens to authorise the GET without credentials but
later demands them on POST, the GET is NOT reopened with auth;
that server should be configured with --transport http (Streamable
HTTP) instead, which has no long-lived GET.
See the Configuration Reference for
configuring named servers in config.yaml. For client-specific notes
(Claude Code, Codex, Gemini CLI, OpenCode), see
Agent Integration.
Locksmith provides a session-aware CLI for AI agents. See Agent Integration for the full protocol and platform-specific setup (Claude Code hooks, Codex hook, Gemini CLI, OpenCode).
For Claude Code, run locksmith init - the UserPromptSubmit hook is
installed automatically. The hook command is locksmith session ensure --hook (a native invocation - no shell script), and init also adds
Bash(locksmith:*) to permissions.allow, augments your shell PATH
if needed, and restarts any running daemon. Restart Claude Code after
running init.
Quick start:
# Ensure a valid session (reuses existing or creates new)
export LOCKSMITH_SESSION=$(locksmith session ensure --quiet)
# Retrieve a secret by alias (configured in config.yaml)
locksmith get --key my-api-key
# Retrieve a secret directly by vault path (no alias needed)
locksmith get --vault gopass --path work/aws/access-key-id
# Sub-agents: pass the session in their environment
LOCKSMITH_SESSION=$LOCKSMITH_SESSION some-subagent-toolSession delegation to sub-agents is controlled by agent.pass_session_to_subagents
in ~/.config/locksmith/config.yaml (default: true).
| Vault | Platform | Auth | Status |
|---|---|---|---|
| macOS Keychain | macOS | Keychain | Supported |
| gopass | macOS, Linux | GPG passphrase | Supported |
| 1Password | macOS, Linux | Desktop app (biometric) / op signin |
Supported |
| GNOME Keyring | Linux | Keyring password | Planned |
Per-plugin installation, configuration examples, and troubleshooting:
| Plugin | Platform | Setup guide |
|---|---|---|
| gopass | Linux, macOS | plugins/gopass/README.md |
| keychain | macOS only | plugins/keychain/README.md |
| 1password | Linux, macOS | plugins/onepassword/README.md |
See CONTRIBUTING.md.