Use GitHub's private vulnerability reporting on this repository (Security tab, "Report a vulnerability"). Do not open a public issue for a security problem. You will get an acknowledgement within a few days and a fix or a documented decision before any public disclosure.
keyfence is a man-in-the-middle proxy for the hosts it monitors. It reads
the full body of every request to those hosts, every text WebSocket frame
on a connection to them and, in placeholder mode, the full response. This
is the point of the tool, and it is also why it runs only on your machine,
listens only on 127.0.0.1 by default, and never sends anything anywhere
except to the provider you were already talking to.
On disk it keeps:
~/.keyfence/vault.json: keyed HMAC-SHA256 digests of your secrets, never the values, mode 0600. The key is a random 256-bit salt kept in the same file, so anyone who can read the file can test guesses against the digests offline, and a short or common password does not survive that. A slow password hash such as PBKDF2 or scrypt is not an option here: the proxy computes one digest per token of every request, a few hundred per request, and must answer in milliseconds. Protect the file like the CA key below, and prefer registering long random values.~/.keyfence/audit.log: one line per request with findings, with a masked preview and the JSON key, never the value. A preview is two characters at each end of a value of 24 characters or more; a shorter value is logged as its kind and its length alone, because a preview of a short secret is most of the secret. Mode 0600, like the vault.~/.keyfence/env/: the environment snapshot of a runningkeyfence exec, hashes only, deleted by the proxy at startup.~/.keyfence/proxy.log: the proxy's own output duringkeyfence exec: the startup summary (version, mode, host count, rule count, vault size) and one line per detection with the host, the number of secrets and their kinds. TheCANARY trippedline names the file the canary was planted in. Never a value. Mode 0600, like the vault.- the file given to
keyfence exec --record FILE, only when you ask for it: a mitmproxy flows file with every request that reached the proxy, headers and body in full, in the state keyfence left it. That includes theAuthorizationheader with the provider's own key in every mode. Responses are streamed through and not kept, except the ones keyfence produced or rewrote: the 403 of a blocked request and, inplaceholdermode, a response with the real values put back. Inredactandplaceholdermode the requests carry[REDACTED:...]or placeholders instead of the secrets. Inauditmode nothing is changed, so the file holds your secrets in clear text; inblockmode it holds the blocked request as the command sent it, secrets included.keyfence execsays so on stderr in those two modes. The file is created with mode 0600. Treat it as the transcript it is and delete it when you are done. ~/.mitmproxy/: the CA certificate and key generated by mitmproxy. Anyone who can read that key can intercept TLS on this machine for any client that trusts the CA. Protect it like an SSH key.
The notice feature appends a short text to the system prompt of requests
it changed. That is the only content keyfence adds to a request.
In scope: accidental leakage of secrets through prompts, tool results and files that coding agents send to LLM providers. keyfence is a last line of defence and works best with the Claude Code hook that stops the files from being read at all.
Out of scope: a program that deliberately hides secrets (encoding, splitting, encryption), certificate-pinning clients, traffic that bypasses the proxy, and secrets in headers or query strings, which are left alone because they usually carry the provider's own key. See docs/limitations.md.
- Releases are built and published by the
ReleaseGitHub Actions workflow through PyPI trusted publishing. No long-lived token exists. PyPI records a provenance attestation for every file, linking it to the workflow run and commit; it is shown on the PyPI file listing and can be checked withpypi-attestations. - CI builds a CycloneDX SBOM for every commit on
mainand every pull request; it is attached to the workflow run as an artifact. - Dependencies are
mitmproxyandPyYAML, both from PyPI. The bundled detection rules come from the gitleaks repository and are checked in, so nothing is fetched at runtime. - Tests run on Linux, macOS and Windows.
- A detector exception fails closed: the request gets a 403, and a WebSocket frame is dropped instead of forwarded.
- A corrupted vault stops the proxy at startup with a message and makes a running proxy fail closed until it is fixed.
- An empty or missing config file does not change a running proxy.
keyfence runreplaces its own process with mitmproxy, so killing it cannot leave a proxy behind.