Security model
keymitt lets agents use credentials without the values entering their context. This page states precisely what that protects against — and what it does not.
What keymitt protects against
| Threat | Mitigation |
|---|---|
| A secret ends up in the agent's context, transcript or logs | Agents only see {{secret:NAME}}. Everything returned — output, response headers and bodies, file contents, error messages — is redacted: the values of all secrets plus base64 (any alignment), URL-encoded (several styles), hex and JSON-escaped forms. When output is cut at a size limit, bytes that could start a secret cut off by the limit are dropped first, so no fragment is returned. |
| A secret is visible to other processes | Never placed in command-line arguments. Temp files are owner-only, overwritten and deleted after the run. |
| A secret is used without you knowing | Every use needs approval through a prompt the agent cannot click. It names the secrets and the resolved program (or destination) first, then shows arguments, stdin, environment, temp files and directory — or method, URL, headers and the start of the body — with invisible characters escaped. Requests too long to show in full are refused. |
| A secret is handed to an impostor program | Programs are found only in absolute PATH directories; --command stores full paths. |
| A secret is sent somewhere it shouldn't be | Per-secret allowlists of programs and hosts. http_request refuses placeholders and credentials in the URL, header injection, transport headers such as Host, non-HTTP(S) schemes and redirects, and ignores ~/.curlrc. |
| An allowed program is hijacked through its environment | Variables may only carry a secret, as exactly one placeholder, and never loader, interpreter, tool, proxy, CA or PATH-like variables. Policies can pin the allowed names. |
| A secret outlives the command | On macOS and Linux each run's process group is killed when it ends. |
read_file_redacted sidesteps your client's file rules | Reads are confined to the workspace and configured roots; credential stores, key files, FIFOs and devices are refused. |
| Mass export of all secrets | Backups and restores are CLI-only, need an interactive terminal, and always need strong approval. |
What keymitt does not protect against
These follow from running as the same OS user as the agent.
- A malicious agent with an unrestricted shell. It could bypass keymitt: read the Linux Secret Service or Windows Credential Manager directly (any process running as you can), edit
policy.toml, or write a terminal-approval decision file. On macOS, another program reading keymitt's Keychain items triggers a system prompt — a higher bar, not a guarantee. Keep your client's own prompts for shell commands on. - What an approved program does with a secret. The arguments and stdin decide where it goes — both are in the prompt, so read it. Prefer
http_request, which is host-restricted. Never allowlist shells or interpreters. - Programs that read configuration from the working directory, such as
gitwith.git/config. The prompt shows the directory; be careful in untrusted repositories. - Weak approval methods confirm intent without authenticating you. Use
allow_weak = falseormethod = "native"to forbid them. - The polkit dialog is generic; on Linux the details appear in a notification just before it.
- Masking in
read_file_redactedis not a boundary. A credential in an unusual format inside an allowed directory can still be shown; keep credential files out of the read roots. - Transformed values. A program that hashes, encrypts, reverses, wraps or splits a value defeats redaction.
- Windows process trees. Only the direct child is stopped at the end of a run.
- A compromised OS account or keychain.
Design decisions
- The MCP server reads the policy once, at start-up, and never writes it. Agents have no tool to change the policy or the approval settings.
- Approvals are not cached by default. With
ttl_secs, only byte-identical requests to the same resolved program are remembered. - Cancelling an authenticating prompt denies the request; there is no retry with a weaker method.
- Each decision records
elapsed_ms, so an implausibly fast approval stands out in the audit log. - Inputs are bounded: arguments, stdin, each temp file and request bodies are limited to 1 MiB.
- Values are read from the keychain per request and dropped afterwards. Rendered templates and backup plaintext use zeroizing buffers.
- The
testingbuild feature must never be enabled in builds that are installed or distributed.
Releases and installers
Release archives are built by GitHub Actions with pinned actions, read-only build tokens and no build cache. Each release carries SHA256SUMS.txt and signed build-provenance attestations (gh attestation verify <archive> --repo walisayu/keymitt). The one-line installers verify the checksum, refuse plain HTTP, check every archive path before extracting only the binary, and the shell installer runs nothing if its download is cut short.
Reporting a vulnerability
Please report vulnerabilities privately through GitHub security advisories, not in public issues.