# Mxkey

> 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.

- Skill: `maxmealing/mxkey` (Agent Skill, multi-file: 7 files)
- Install (CLI): `npx skillmds@latest add maxmealing/mxkey`
- Raw SKILL.md: https://api.skillmd.com/api/skills/maxmealing/mxkey/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Security
- Author: maxmealing (https://skillmd.com/u/maxmealing)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/maxmealing/mxkey

---


# mxkey — macOS dev-secrets workflow

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.

## Why Keychain

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.

## How it works

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`.

## What happens when the agent uses this skill

With `mxkey` active, the agent will:

- **Migrate `.env` files** into Keychain under `project.<repo>.*` names
  and tell you which keys to trim out of the file.
- **Write `.env.mxkey` manifests** (`mxkey init project.<repo>`) so a
  project has a committable list of which secrets it needs, then wrap dev
  commands with `mxkey run-here -- <cmd>`.
- **Wrap ad-hoc commands** with `mxkey run <name>... -- <cmd>` instead of
  `export FOO=... && cmd` or reading from `.env`.
- **Suggest `--require-auth`** for 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 `export` lines 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 set` overwrites; `mxkey rm`
  deletes) and remind you to revoke at the provider afterwards.
- **Store 2FA backup / recovery codes** under `backup.<service>.<n>` via
  `mxkey backup add <service>`, with single-use `mxkey 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/mxkey` symlink).

## Scope

Single-user, single-machine. For team-shared secrets, use 1Password,
Doppler, or a vault service; for Linux/Windows, use something else.

## When to activate

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 <provider>"
- 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)

## First-run setup

If `which mxkey` fails, the script is bundled but not on PATH. Run the setup
script once:

```bash
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.

## Naming convention

`<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 the `mxkey backup` subcommand, not
  `mxkey set` directly.

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 cheatsheet

| 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. |

## Agent playbooks

### Detecting `.env` files

When the agent encounters a `.env`, `.env.local`, or similar in a project:

1. Offer to migrate: "I can move these N keys into mxkey under `project.<repo>.*`."
2. On approval, run `mxkey migrate <path-to-env> <repo>`.
3. Offer to add `.env*` to `.gitignore`.
4. 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.
5. Suggest wrapping dev commands: `mxkey run project.<repo> -- <cmd>`, or
   regenerating the `.env` on demand from `mxkey 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`.

### Detecting `export KEY=` in shell rc files

If the agent is reading or editing `~/.zshrc`, `~/.bashrc`, `~/.profile`, a
`direnv` `.envrc`, etc. and sees `export SOMETHING_API_KEY=...`:

1. Flag it: "I see a secret exported in your shell rc. That leaks into every
   process you spawn."
2. Offer to migrate: move the value into mxkey, remove the `export` line, wrap
   the consuming command(s) in `mxkey run`.

Mention it once per session — don't nag.

### Detecting hardcoded secrets in code

If the agent sees a hardcoded-looking secret in source files (e.g.
`Authorization: Bearer xyz`, `const API_KEY = "..."`, `apiKey: "sk-..."`):

1. Warn before suggesting or writing a commit.
2. Suggest the `mxkey run` + `process.env.FOO_API_KEY` pattern instead.

### Rotating a secret

1. User generates a new key at the provider.
2. Run `mxkey set <name> <ENV_VAR>` again — overwrites the stored value.
3. Remind the user to revoke the old key at the provider.

### Removing a secret

1. Confirm with `mxkey list` which name to remove.
2. Run `mxkey rm <name>`.
3. Remind the user: **the key is still valid at the provider** until they
   revoke it there.

### Storing 2FA backup / recovery codes

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.):

1. Suggest `mxkey backup add <service>` — service name is a single word, no
   dots (e.g. `github`, `google`, `porkbun`).
2. Default is hidden input. Mention `--show` if 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 or `clear` afterward is
   wise.
3. 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.
4. To consume one later: `mxkey backup use <service>` prints the next code
   and atomically deletes it (single-use). Never use `mxkey get`/`export`
   on a backup code — that doesn't delete it and you'll re-use a burned
   code.
5. `mxkey backup list` shows 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`).

### Refusing a pasted secret

If the user pastes something that looks like a secret into chat:

1. **Do not** act on it. **Do not** store it. **Do not** echo it back.
2. 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."

## Hard rules

- NEVER echo a secret value in a response
- NEVER write a secret to a file in plaintext
- NEVER commit a secret, the `~/.config/mxkey/index` file (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 get` where `mxkey run` works
- If the user pastes a secret by mistake: refuse, warn, tell them to rotate

## Troubleshooting

See `references/troubleshooting.md` for:
- Repeated Keychain GUI prompts
- `~/.local/bin` not on PATH
- "not in index" errors
- Reconciling Keychain entries with the on-disk index

## Further reading

`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`.

