| name | mxkey | |||||
|---|---|---|---|---|---|---|
| version | 1.0.0 | |||||
| description | macOS dev-secrets workflow via mxkey (a Keychain wrapper). Use whenever the user handles an API key, token, password, or 2FA backup / recovery code — setting up a new API, running a command that needs a key, editing a .env file, spotting a hardcoded secret in code, or storing single-use recovery codes. Secrets never enter chat, shell history, or plaintext files. macOS only. | |||||
| tags |
|
|||||
| allowed-tools |
|
Store API keys and tokens in the macOS Keychain so you and agents like Claude, Cursor, or Codex can load them on demand — never pasted into chat, shell history, .env files, or source code.
What this skill does: wraps the macOS security CLI with an mxkey command for setting, reading, and injecting secrets into dev commands, plus a backup subcommand for single-use 2FA recovery codes. Secrets are named <category>.<name> (categories by convention: api, db, oauth, project, infra, backup). Encrypted at rest by the OS Keychain, unlocked by your login password.
After this skill, you can run mxkey run -- <cmd> to inject secrets into any process, edit .env files safely with placeholder refs, and stop worrying about hardcoded keys. macOS only.
Every Mac already ships with a secrets vault: the login Keychain. It's
encrypted at rest, unlocked by your login password, access-controlled per
app, and battle-tested for decades. mxkey reuses it — adding a naming
convention for dev secrets and a safe way to inject them into commands —
rather than inventing a new storage layer.
The core workflow is two commands:
mxkey set <name> <ENV_VAR>— prompts for the value at a hidden terminal read, stores it in Keychain.mxkey run <name>... -- <cmd>— reads the value back and launches<cmd>with the env var set. The secret lives in that process's memory only.
For projects that need several secrets at once, names double as group
prefixes: mxkey run project.myapp -- pnpm dev loads every
project.myapp.* secret in one call. mxkey init project.myapp writes a
.env.mxkey manifest in the project root listing those names (no values),
so mxkey run-here -- pnpm dev works from any subdirectory.
For high-value keys, mxkey set --require-auth <name> stores the secret
with a Keychain ACL that prompts macOS (Touch ID or password) on every
read — useful for production DBs and billing APIs, silent for low-stakes
keys.
Under the hood it's a bash script wrapping /usr/bin/security, macOS's
built-in Keychain CLI. install.sh installs it by creating one symlink at
~/.local/bin/mxkey.
With mxkey active, the agent will:
- Migrate
.envfiles into Keychain underproject.<repo>.*names and tell you which keys to trim out of the file. - Write
.env.mxkeymanifests (mxkey init project.<repo>) so a project has a committable list of which secrets it needs, then wrap dev commands withmxkey run-here -- <cmd>. - Wrap ad-hoc commands with
mxkey run <name>... -- <cmd>instead ofexport FOO=... && cmdor reading from.env. - Suggest
--require-authfor production DBs, billing APIs, and any key where silent theft would hurt — each read triggers a Touch ID / macOS password prompt. - Flag hardcoded secrets and
exportlines it spots in source or shell rc files, and offer to move them into Keychain. - Refuse to echo, store, or commit secret values pasted into chat — it
tells you to rotate at the provider and re-save via
mxkey set. - Handle rotation and removal (
mxkey setoverwrites;mxkey rmdeletes) and remind you to revoke at the provider afterwards. - Store 2FA backup / recovery codes under
backup.<service>.<n>viamxkey backup add <service>, with single-usemxkey backup use <service>consumption (read-then-delete) and require-auth on every read. - Ask before running
install.sh(the one step that creates the~/.local/bin/mxkeysymlink).
Single-user, single-machine. For team-shared secrets, use 1Password, Doppler, or a vault service; for Linux/Windows, use something else.
Fire on any of these:
- User mentions an API key, token, bearer, client secret, access token, or password
- User says "set up the X API", "add my Y key", "I got a new key for Z"
- User mentions 2FA backup codes, recovery codes, one-time codes, or "I have a list of codes from "
- User is editing or about to edit a
.env,.env.local, or.env.*file - Agent is writing a command that needs an API key
- Agent sees a hardcoded-looking secret in source files or shell rc files
- User pastes something that looks like a secret (
sk-...,gsk_..., JWT, 30+ char token)
If which mxkey fails, the script is bundled but not on PATH. Run the setup
script once:
bash "$(dirname "$0")/install.sh"The agent must ask the user before running install.sh — it creates a symlink
in ~/.local/bin/. See references/setup.md for details.
Installing this skill inside a git worktree: the mxkey CLI is a symlink
to the mxkey script in this repo. If the skill was installed project-scope
into a disposable worktree (e.g. a Claude Code .claude/worktrees/...
session), the symlink breaks when the worktree is removed. Install at user
scope (~/.claude/skills/mxkey/) instead so the symlink survives worktree
cleanup.
<category>.<name> — the category prefix keeps things tidy. Pick from:
api— third-party API keys (api.openai,api.stripe)db— database credentials (db.prod-postgres)oauth— OAuth client IDs / secrets / refresh tokens (oauth.google)project— project-scoped secrets (project.myapp.stripe)infra— infra credentials (infra.aws-deploy)backup— single-use 2FA recovery codes, indexed (backup.github.1,backup.porkbun.2). Managed via themxkey backupsubcommand, notmxkey setdirectly.
The env var name is uppercase (OPENAI_API_KEY, STRIPE_SECRET_KEY). For
backup codes the env var is auto-generated as <SERVICE>_BACKUP_<N>.
| Command | What it does |
|---|---|
mxkey set [--require-auth] <name> <ENV_VAR> |
Save a secret (hidden prompt). --require-auth triggers a macOS / Touch ID prompt on every read. |
mxkey run <name>... -- <cmd> |
Run <cmd> with the named secrets loaded as env vars. A name with no exact match is a group prefix. |
mxkey init <group-prefix> |
Write .env.mxkey in the current folder listing every matching name (values stay in Keychain). |
mxkey run-here -- <cmd> |
Walk up to find .env.mxkey, run <cmd> with every listed secret loaded. |
mxkey migrate <path-to-.env> [project-slug] |
Read a .env file and import every key into Keychain under project.<slug>.*. Values are piped via stdin so they never touch disk or shell history. |
mxkey list [prefix] |
Show stored names, optionally filtered. |
mxkey rm <name> |
Delete a secret from Keychain. |
mxkey get / mxkey export |
Print a value / ENV_VAR=value. Escape hatches — prefer run. |
mxkey backup add [--show] <service> |
Add 2FA recovery codes one per line at a prompt (hidden by default; --show echoes input). Stored under backup.<service>.<n>, always require-auth. |
mxkey backup use <service> |
Print and atomically delete the next backup code for a service (single-use consumption). |
mxkey backup list [service] |
Show how many backup codes remain (no values). |
mxkey backup rm <service> |
Delete every backup code for a service. |
When the agent encounters a .env, .env.local, or similar in a project:
- Offer to migrate: "I can move these N keys into mxkey under
project.<repo>.*." - On approval, run
mxkey migrate <path-to-env> <repo>. - Offer to add
.env*to.gitignore. - After a successful migration, trim the migrated keys out of
.env. The command prints the exact list at the end — remove those lines (or the whole file if nothing non-secret remains). Leaving both copies means secrets still exist in plaintext and the two stores can drift. - Suggest wrapping dev commands:
mxkey run project.<repo> -- <cmd>, or regenerating the.envon demand frommxkey export.
Don't migrate silently — some .env files are loaded by tooling that can't
read env vars from the parent process. Ask first.
Full walkthrough: references/migrate-from-env.md.
If the agent is reading or editing ~/.zshrc, ~/.bashrc, ~/.profile, a
direnv .envrc, etc. and sees export SOMETHING_API_KEY=...:
- Flag it: "I see a secret exported in your shell rc. That leaks into every process you spawn."
- Offer to migrate: move the value into mxkey, remove the
exportline, wrap the consuming command(s) inmxkey run.
Mention it once per session — don't nag.
If the agent sees a hardcoded-looking secret in source files (e.g.
Authorization: Bearer xyz, const API_KEY = "...", apiKey: "sk-..."):
- Warn before suggesting or writing a commit.
- Suggest the
mxkey run+process.env.FOO_API_KEYpattern instead.
- User generates a new key at the provider.
- Run
mxkey set <name> <ENV_VAR>again — overwrites the stored value. - Remind the user to revoke the old key at the provider.
- Confirm with
mxkey listwhich name to remove. - Run
mxkey rm <name>. - Remind the user: the key is still valid at the provider until they revoke it there.
When the user mentions backup codes, recovery codes, one-time codes, or has just generated a list of them at a provider (GitHub, Google, Porkbun, 1Password, etc.):
- Suggest
mxkey backup add <service>— service name is a single word, no dots (e.g.github,google,porkbun). - Default is hidden input. Mention
--showif the user wants to see what they paste — but warn that visible input lands in terminal scrollback and tmux/screen buffers, so a fresh window orclearafterward is wise. - Each code is stored as
backup.<service>.<n>with require-auth (Touch ID on every read) — no opt-out, recovery codes always need the prompt. - To consume one later:
mxkey backup use <service>prints the next code and atomically deletes it (single-use). Never usemxkey get/exporton a backup code — that doesn't delete it and you'll re-use a burned code. mxkey backup listshows remaining counts across all services;mxkey backup list <service>for one. Remind the user to regenerate at the provider when the count gets low.
Don't migrate backup codes silently — confirm the service name first, and
if there's already a backup.<service>.* group, ask before adding (the
new codes append; old codes stay unless the user runs mxkey backup rm).
If the user pastes something that looks like a secret into chat:
- Do not act on it. Do not store it. Do not echo it back.
- Tell the user: "That looks like a secret. Because it's now in this
conversation, rotate it at the provider, then run
mxkey set <name> <ENV_VAR>and type the new value at the hidden prompt."
- NEVER echo a secret value in a response
- NEVER write a secret to a file in plaintext
- NEVER commit a secret, the
~/.config/mxkey/indexfile (if tracked), or any backup dump - NEVER ask the user to paste a secret into chat — always use
mxkey set's hidden prompt - NEVER set long-lived bearer tokens via
export FOO=...in shell rc files — defeats the point - NEVER use
mxkey getwheremxkey runworks - If the user pastes a secret by mistake: refuse, warn, tell them to rotate
See references/troubleshooting.md for:
- Repeated Keychain GUI prompts
~/.local/binnot on PATH- "not in index" errors
- Reconciling Keychain entries with the on-disk index
references/keychain-deep-dive.md — how mxkey wraps /usr/bin/security,
why the on-disk index exists, the -T /usr/bin/security ACL flag, and how
mxkey run injects env vars via exec.