git:20260918.2be94f0 to git:20260918.0f7899e

34 added, 122 removed. Audit A to A.

---
name: credential-usage
- description: Use whenever a task needs an API key, token, password, database URL, certificate, or any other credential. That includes configuring an MCP server, calling an authenticated API with curl, running a CLI like gh/aws/openai, running psql or docker compose against a real service, filling in a .env, or when the user pastes a secret into chat or asks where to keep one. Consult it even when the user never says "secret" or "tsm". macOS with the tsm vault installed.
+ description: Use whenever a task needs an API key, token, password, database URL, certificate, or any other credential. That includes configuring an MCP server, calling an authenticated API with curl, running gh/aws/openai, running psql or docker compose against a real service, filling in a .env, or when the user pastes a secret into chat or asks where to keep one. Consult it even when the user never says "secret" or "tsm". macOS with the tsm vault installed.
---
# Using credentials from the tsm vault
- `tsm` is a Touch ID-gated secrets vault on this Mac. The user installed this plugin so you pull credentials from it instead of asking for them, and so you handle them the way this skill describes. `tsm list`, `tsm get`, `tsm run`, and `tsm status` are allowlisted for you. Every other `tsm` subcommand (`add`, `edit`, `remove`, `reset`, `init`, `config set`) changes the vault and is the user's to run.
-
- The first vault access in a session pops a system Touch ID dialog, and the command blocks until the user responds. That is normal. Do not kill or retry it. Later accesses inside the unlock window do not prompt.
+ `tsm` is a Touch ID-gated secrets vault on this Mac. The user installed this plugin so you pull credentials from it instead of asking for them. `tsm list`, `tsm get`, `tsm run`, and `tsm status` are allowlisted for you; every other subcommand (`add`, `edit`, `remove`, `reset`, `init`, `config set`) changes the vault and is the user's to run. The first vault access in a session blocks on a system Touch ID dialog until the user responds. Do not kill or retry it.
## Workflow
- 1. **Look before asking.** `tsm list --json` returns names, display names, descriptions, tags, and the `confirm` flag. Never values. Match on any of those fields.
- ```bash
- tsm list --json
- # [{"name":"gh-pat","display_name":"GitHub PAT","description":"...","confirm":false,"tags":["github","git"]}]
- ```
- One match: use it. Several plausible matches: ask which. None: say so, propose a kebab-case name, and stop. Do not ask for the value while a match exists.
+ 1. **Look before asking.** `tsm list --json` returns names, display names, descriptions, tags, and the `confirm` flag, never values. Match on any of them. One match: use it. Several plausible: ask which. None: say so, propose a kebab-case name, and stop. Do not ask for a value while a match exists.
2. **Pick the carrier** for the tool (below). Prefer `tsm run` whenever the tool reads an environment variable.
- 3. **Do the task, then report the task.** The credential handling is yours to get right and not something the user needs to hear about. See "What to tell the user".
+ 3. **Do the task, then report the task**, not the credential handling (see "What to tell the user").
## The one rule: the value never lands in an argument list
- Anything in a process's argv is visible in `ps` to every user on the machine and is written to shell history. So the value has to travel by environment variable, file descriptor, or a 0600 temp file, never as part of a command line. `$(tsm get x)` expanded inside a flag value breaks the rule even though it looks tidy.
+ Anything in a process's argv is visible in `ps` to every user on the machine and lands in shell history. So the value travels by environment variable, file descriptor, or 0600 temp file, never as part of a command line. `$(tsm get x)` inside a flag value breaks the rule even though it looks tidy, and `eval "$(tsm get x --format 'env X')"` plants it in the parent shell for its whole lifetime.
Three carriers that respect it:
- - **`tsm run --env VAR=name -- cmd`** sets VAR in the child process only. The parent shell is untouched and the variable is gone when the child exits.
- - **`<(tsm get name)`** process substitution. The tool receives a `/dev/fd/N` path and the value never touches disk. Works when the tool reads the path once. Your Bash tool runs bash, so this syntax is available to you.
- - **`mktemp`** gives a 0600 file under the per-user `$TMPDIR`. Redirect into it and `rm` it when done. There is no `/dev/shm` on macOS.
+ - **`tsm run --env VAR=name -- cmd`** (`--env` repeats) sets VAR in the child only; gone when the child exits.
+ - **`<(tsm get name)`** hands the tool a `/dev/fd/N` path; the value never touches disk. Works when the tool reads the path once. Your Bash tool runs bash, so the syntax is available.
+ - **`mktemp`** gives a 0600 file under the per-user `$TMPDIR` (there is no `/dev/shm` on macOS). Redirect into it and `rm` it when done.
## Patterns by tool type
- ### Anything that reads an env var (gh, aws, openai, anthropic, PGPASSWORD, most SDKs)
+ **Anything that reads an env var** (gh, aws, openai, anthropic, PGPASSWORD, most SDKs):
```bash
tsm run --env GITHUB_TOKEN=gh-pat -- gh pr list
tsm run --env PGPASSWORD=pg-prod-password -- psql -h db.example.com -U app mydb -f migrate.sql
- tsm run --env A=key-a --env B=key-b -- ./deploy.sh prod
```
- ### MCP servers in `.mcp.json`
-
- Wrap the server command so it inherits the credential at startup:
+ **MCP servers in `.mcp.json`**: wrap the server command so it inherits the credential at startup.
```json
- {
- "mcpServers": {
- "github": {
- "command": "tsm",
- "args": ["run", "--env", "GITHUB_PERSONAL_ACCESS_TOKEN=gh-pat", "--", "github-mcp-server"]
- }
- }
- }
- ```
-
- ### docker compose and docker run
-
- A bare key under `environment:` passes the variable through from the parent process, so `tsm run` covers compose with no `env_file:` at all:
-
- ```yaml
- services:
- worker:
- environment: [SENTRY_DSN]
- ```
- ```bash
- tsm run --env SENTRY_DSN=sentry-dsn -- docker compose up worker
- ```
-
- Plain `docker run` has no pass-through, so generate an `--env-file` in a temp file:
-
- ```bash
- F=$(mktemp) && tsm get gh-pat --format "env GITHUB_TOKEN" > "$F" && docker run --env-file "$F" some-image; rm -f "$F"
+ { "mcpServers": { "github": {
+ "command": "tsm",
+ "args": ["run", "--env", "GITHUB_PERSONAL_ACCESS_TOKEN=gh-pat", "--", "github-mcp-server"] } } }
```
- ### curl and other HTTP clients
+ **docker compose**: a bare key under `environment:` (`environment: [SENTRY_DSN]`) passes the variable through from the parent, so `tsm run --env SENTRY_DSN=sentry-dsn -- docker compose up worker` needs no `env_file:`. Plain `docker run` has no pass-through: `F=$(mktemp) && tsm get gh-pat --format "env GITHUB_TOKEN" > "$F" && docker run --env-file "$F" img; rm -f "$F"`.
- curl reads extra headers from a file with `-H @file`. `printf` is a bash builtin, so the header line is assembled inside the shell and reaches curl only through a file descriptor:
+ **curl** reads extra headers from a file with `-H @file`. `printf` is a bash builtin, so the header line is assembled inside the shell and reaches curl only through a file descriptor:
```bash
curl -H @<(printf 'Authorization: Bearer %s\n' "$(tsm get gh-pat)") https://api.github.com/user
```
- `curl -H "Authorization: Bearer $(tsm get gh-pat)"` is the tempting version, and it puts the token in curl's argv.
-
- ### Tools that take a file path (`--cacert`, `--key-file`, `PGPASSFILE`)
-
- If the tool reads the path once, process substitution keeps the value off disk:
-
- ```bash
- curl --cacert <(tsm get ca-cert) https://internal.example.com
- ```
-
- If the tool insists on a regular file, or re-reads it, use a temp file. libpq is the usual case: it ignores a `PGPASSFILE` that is not a plain 0600 file, so `<(...)` does not work for pgpass.
-
- ```bash
- F=$(mktemp) && tsm get pg-prod --format pgpass > "$F" && PGPASSFILE="$F" psql --no-password "service=mydb" -f migrate.sql; rm -f "$F"
- ```
-
- The `pgpass` formatter expects the stored value to already be a `host:port:db:user:password` row. If the vault holds only the password, skip the file and use `PGPASSWORD` with `tsm run` as shown above.
-
- ### Wire formats: `tsm get --format`
-
- Built-in formatters: `env VAR`, `pgpass`, `aws-credential-process`. `--format` refuses to write to a TTY and cannot be combined with `--to-file`, so redirect into a `mktemp` file; the redirect keeps the file's 0600 mode. AWS is the exception: the CLI runs `credential_process` itself and reads stdout, so the command goes in `~/.aws/config` with no redirect:
+ `curl -H "Authorization: Bearer $(tsm get gh-pat)"` looks equivalent and puts the token in curl's argv.
- ```ini
- [profile prod]
- credential_process = tsm get aws-prod --format aws-credential-process
- ```
+ **Tools that take a file path** (`--cacert`, `--key-file`): `<(tsm get ca-cert)` when the path is read once; `mktemp` when the tool re-reads it or insists on a regular file. libpq is the usual case: it ignores a `PGPASSFILE` that is not a plain 0600 file, so `tsm get pg-prod --format pgpass > "$F"` into a `mktemp` path, then `PGPASSFILE="$F" psql --no-password …`. The `pgpass` formatter expects the stored value to already be a `host:port:db:user:password` row; if the vault holds only the password, use `PGPASSWORD` with `tsm run` instead.
- ### Tools that write their own env file
+ **`tsm get --format`** offers `env VAR`, `pgpass`, and `aws-credential-process`. It refuses a TTY and cannot be combined with `--to-file`, so redirect into a `mktemp` file. AWS is the exception: `credential_process = tsm get aws-prod --format aws-credential-process` under a profile in `~/.aws/config`, and the CLI runs it itself.
- Some tools dump their environment to a fixed project path on startup: a test harness that writes `.env.test`, a script that materializes `.env` from `process.env`. If the tool you are about to launch does this, say so before launching, and delete that file when the process exits. Otherwise the vault's protection ends the moment the tool starts.
+ **Tools that write their own env file** on startup (a test harness dumping `.env.test`): say so before launching, and delete that file when the process exits.
## Confirm-gated secrets
- Entries with `"confirm": true` prompt Touch ID on every access, even inside the unlock window. The daemon presents the dialog in the user's GUI session, so it works from your non-TTY shell. A dialog that appears with no explanation is alarming, so when you are about to run a command that uses one, say so in a line first:
-
- > Running the migration now; you'll get a Touch ID prompt for `pg-prod`.
-
- That is the only time Touch ID belongs in your output. Do not mention it for entries that are not gated, and do not warn about prompts a command the user runs later might raise.
-
- When there is no GUI login session (ssh without a console session, cron, CI), `tsm run` refuses with:
-
- ```
- refusing to run: secret(s) require Touch ID confirmation but no biometric prompt can be presented here (no GUI login session): <name>
- ```
+ Entries with `"confirm": true` prompt Touch ID on every access, even inside the unlock window; the dialog appears in the user's GUI session, so it works from your non-TTY shell. An unexplained dialog is alarming, so say so in one line right before the command: "Running the migration now; you'll get a Touch ID prompt for `pg-prod`." That is the only time Touch ID belongs in your output.
- Hand the user a command to run where Touch ID is available. Dropping the gate with `tsm edit` is their decision. You can suggest it, never run it.
+ With no GUI login session (ssh without a console session, cron, CI), `tsm run` refuses and names the secret. Hand the user a command to run where Touch ID is available; dropping the gate with `tsm edit` is their call.
- ## Saving a credential the user shares with you
+ ## A credential the user pastes into chat
- When the user pastes a credential into chat, or asks you to store one, use it for the task in hand and then hand off the save. The value must not pass through a command line at any step:
+ Use it for the task in hand, then hand off the save. The value must not pass through a command line at any step:
- 1. Run `mktemp` for a 0600 path and write the raw value there with your file-editing tool. Not `echo`, not `printf`, not a heredoc; all of those put the value in argv or shell history.
- 2. Use that file wherever you would have used `tsm get`: `-H @<(printf 'Authorization: Bearer %s\n' "$(cat /path/from/mktemp)")` for curl, or the path itself for a file-flag tool.
- 3. Give the user one command that saves it, with the real temp path filled in:
+ 1. `mktemp` a 0600 path and write the raw value there with your file-editing tool. Not `echo`, `printf`, or a heredoc; those put it in argv or shell history.
+ 2. Use that file wherever you would have used `tsm get` (`"$(cat "$F")"` in the curl form, or the path itself for a file-flag tool).
+ 3. Give the user one command that saves it, with the real path filled in, and mention that the transcript still holds the value, so rotating it is worth considering:
```bash
tsm add --name <kebab-id> --display-name "<Display Name>" --from-file /path/from/mktemp && rm /path/from/mktemp
```
- If they would rather copy the value from chat than trust your file:
- ```bash
- pbpaste | tsm add --no-input --name <kebab-id> --display-name "<Display Name>"
- ```
- 4. Delete the temp file once it is saved or no longer needed, and mention that the chat transcript still holds the value, so rotating it is worth considering.
+ (`pbpaste | tsm add --no-input --name <kebab-id> --display-name "<Display Name>"` if they would rather copy it from chat.)
- If the user needs to supply a value you do not have (a missing entry), give them the `tsm add` line to run. Without `--from-file` it opens an interactive prompt that never touches argv.
+ For a missing entry whose value you do not have, give the user a `tsm add --name <kebab-id> --display-name "…"` line; without `--from-file` it prompts interactively and never touches argv.
## What to tell the user
- The user trusts you to follow this skill. They do not need to be told that you did. A reply that explains the argv rule, environment scoping, what the value never touched, or which tsm subcommand you ran reads as padding at best and as a lecture at worst, and it buries the result they asked for.
-
- - **Lead with the result of the task.** The customers, the migration output, the file you changed.
- - **Name the entry in passing, in a few words, and say it came from tsm**, so the user can tell which credential was used and where it lives: "fetched with `stripe-live-sk` (tsm)", "as `app`, using the `pg-prod-password` tsm credential", "your tsm vault". Explain the choice only when there were several plausible matches, and then briefly: "your tsm vault's only GitHub entry" is enough; its description and scopes are not.
- - **Do not narrate commands.** "I checked the tsm vault" rather than "I ran `tsm list --json`".
- - **Do not explain the mechanics.** Nothing about argv, shell history, environment variables being scoped to the child, temp files, file descriptors, or the value never landing somewhere. Nothing about Touch ID beyond the one-line heads-up above.
- - **Show a command only when the command is the deliverable**: the `.mcp.json` block you wrote, a `tsm add` for a value the user has and you do not, a `tsm run` line for something they will run themselves later.
-
- Two examples, before and after:
-
- > Your vault has one GitHub entry, `gh-token` ("GitHub token", classic PAT with repo + read:org), so I used that. The token itself never appears in `.mcp.json`, so the file is safe to commit, and it isn't in any argv. The first start in a session will pop a Touch ID prompt; `gh-token` isn't confirm-gated, so later starts won't prompt again.
-
- becomes
-
- > Added the GitHub MCP server to `.mcp.json`, using your tsm vault's only GitHub entry, `gh-token`. Restart Claude Code to pick it up.
-
- and
-
- > How I got it: the vault has a `stripe-live-sk` entry, so I used that rather than asking you for the key. The `-H @<(...)` form hands curl the auth header through a file descriptor, so the key never appears in the command line or shell history.
+ The user trusts you to follow this skill and does not need to be told that you did. Explaining the argv rule, environment scoping, or what the value never touched reads as a lecture and buries the result they asked for.
- becomes
+ - **Lead with the result of the task**: the customers, the migration output, the file you changed.
+ - **Name the entry in passing and say it came from tsm**: "fetched with `stripe-live-sk` (tsm)", "using the `pg-prod-password` tsm credential". Explain the choice only when several entries plausibly matched, and briefly: "your tsm vault's only GitHub entry" is enough.
+ - **Do not narrate commands or mechanics.** "I checked the tsm vault", not "I ran `tsm list --json`". Nothing about argv, shell history, child environments, temp files, or file descriptors; nothing about Touch ID beyond the one-line heads-up above.
+ - **Show a command only when it is the deliverable**: the `.mcp.json` block you wrote, a `tsm add` for a value you do not have, a `tsm run` line they will run themselves later.
- > Here are the 5 most recent customers from the live Stripe account, fetched with `stripe-live-sk` (tsm):
+ Not "Your vault has one GitHub entry, `gh-token` (classic PAT with repo + read:org), so I used that. The token never appears in `.mcp.json` or any argv. The first start will pop a Touch ID prompt; later starts won't." but "Added the GitHub MCP server to `.mcp.json`, using your tsm vault's only GitHub entry, `gh-token`. Restart Claude Code to pick it up."
## Never
- Print, log, or quote a secret value in your reply. Not even a prefix.
- Write a value into `.env`, `.envrc`, a project config file, or any path that is not a `mktemp` file.
- - `eval "$(tsm get x --format 'env X')"`. That plants the secret in the parent shell for its whole lifetime, which is exactly what `tsm run` exists to avoid.
- - Guess at a value. If no entry matches and the user has not given you one, stop and ask.
## When tsm is not the answer
The tool owns its own OAuth flow (`gcloud auth login`, `gh auth login`). Use that; the vault adds nothing.