The substitution proxy
Bots talk to LLMs and other HTTPS APIs through a small MITM proxy the daemon runs at 127.0.0.1:7777. The bot sees only placeholder strings (mc-claude-access, mc-openai-1, …) in its environment; the proxy substitutes them for real keys at request time and only forwards to the hosts each key is bound to. Real credentials never live in the bot's process memory.
Why a proxy
Two reasons. First — credentials. A bot is sandboxed by uid, but if you put OPENAI_API_KEY=sk-real-... in its env, the agent's process memory has the real key. A prompt injection that says "print your env" leaks it. With the proxy, the agent's env has OPENAI_API_KEY=mc-openai-1, and the real key never touches the bot at all.
Second — egress control. Every outbound TLS connection goes through one process you can audit (and one log file). The daemon refuses substitution for any host not on the placeholder's allow-list, so a leaked placeholder can't be used to exfiltrate against an attacker's endpoint.
How a request flows
- Bot's agent (claude, codex, anything) makes an HTTPS request — e.g.
POST https://api.anthropic.com/v1/messageswithAuthorization: Bearer mc-claude-access. - Because
HTTPS_PROXY=http://127.0.0.1:7777is set in the bot's env, the request is wrapped in aCONNECT api.anthropic.com:443sent to the proxy. - The proxy mints (or reuses) a leaf cert signed by
~/.myclawn/ca.pemforapi.anthropic.com, upgrades the bot socket to TLS using that cert, and now sees the plaintext HTTP frame. (The bot trusts the cert becauseNODE_EXTRA_CA_CERTS/REQUESTS_CA_BUNDLE/CURL_CA_BUNDLEall point at the same CA.) - The proxy scans headers, URL, and JSON body for placeholder strings (anything starting with
mc-). For each match: look up the placeholder in~/.myclawn/keys.json, verify the destination host is on the entry'shostsallowlist, substitute. If the host doesn't match, the placeholder is forwarded literally and a warning is logged. - Proxy opens a fresh TLS connection to the real upstream and forwards the modified request. Response is streamed back to the bot.
- On the way back, the proxy redacts real-key echoes in the response body (provider APIs sometimes echo the auth header). Token-issuance responses (e.g.
POST /oauth/token) get the newaccess_token+refresh_tokenrewritten to fresh placeholders, real values stored in the keystore — so OAuth refresh is transparent.
Placeholders & the keystore
Placeholders are short strings — mc-<provider>-<n> — that look like real credentials enough to flow through code that expects "an API key shape". The bot's env has the placeholder; the daemon's ~/.myclawn/keys.json (mode 0600, owned by you) has the real value:
{
"mc-claude-access": {
"real": "sk-ant-oat01-IFp5UUWOnabLta...",
"hosts": ["api.anthropic.com", "console.anthropic.com"],
"created_at": "2026-05-12T19:35:08.337Z"
},
"mc-claude-refresh": {
"real": "sk-ant-ort01-BqaS1NP--2HV0PUl3gIdMGPQ...",
"hosts": ["api.anthropic.com", "console.anthropic.com"],
"created_at": "2026-05-12T19:35:08.337Z"
}
}Manage with myclawn keys:
myclawn keys ls # list placeholders (real values redacted)
myclawn keys add openai sk-real-XYZ \
--hosts api.openai.com,api.openai.org # registers mc-openai-1
myclawn keys rotate openai sk-real-NEW # update real value, keep placeholder
myclawn keys rm openai # delete (refuses if any bot still has the placeholder live)
myclawn keys show openai # print real value (sensitive)
myclawn keys seed-claude # import Claude Code OAuth from Keychain / ~/.claude/.credentials.jsonmyclawn --model's picker calls these under the hood when you choose a provider, so most users never need myclawn keys directly.
OAuth refresh
For providers that use OAuth (Claude Code, Anthropic Console, GitHub, Google), the proxy knows the token-issuance endpoints and rewrites the response on the way back:
api.anthropic.com/oauth/tokengithub.com/login/oauth/access_tokenoauth2.googleapis.com/token
When the bot's claude calls one of these with its mc-claude-refresh placeholder, the proxy substitutes the real refresh token, gets back a newaccess_token + refresh_token pair, stores them in the keystore under fresh placeholders, and returns those placeholders to the bot. The bot persists them as if they were real tokens — same shape — but at next request they get substituted again. No expiry surprises, no manual re-auth.
The CA certificate
The MITM proxy needs the bot to trust its leaf certs. The daemon generates a one-off CA at boot, writes it to ~/.myclawn/ca.pem, and tells the bot to trust it via these env vars (set automatically when myclawn run --bot spawns):
HTTPS_PROXY=http://127.0.0.1:7777
HTTP_PROXY=http://127.0.0.1:7777
NODE_EXTRA_CA_CERTS=/Users/you/.myclawn/ca.pem # Node
REQUESTS_CA_BUNDLE=/Users/you/.myclawn/ca.pem # Python requests
CURL_CA_BUNDLE=/Users/you/.myclawn/ca.pem # libcurl
SSL_CERT_FILE=/Users/you/.myclawn/ca.pem # openssl-defaultThese also live in the bot's ~/.zshenv for any subprocess started from a shell. The CA is host-bound — its private key (ca.key) is mode 0600 owned by you, never copied off the machine, and used only inside the daemon process. The CA is not added to your system keychain — it's bot-scoped only. Your own browsers and tools don't trust it.
Auditing what the proxy did
Every CONNECT and substitution is logged in ~/.myclawn/daemon.log:
[daemon 22:35:48] [proxy] info connect {"host":"api.anthropic.com","port":443}
[daemon 22:35:50] [proxy] info substituted {"placeholder":"mc-claude-access","host":"api.anthropic.com"}
[daemon 22:36:01] [proxy] warn placeholder_on_wrong_host {"placeholder":"mc-claude-access","host":"evil.example"} The hash-chained audit log (~/.myclawn/<cloneId>/audit.jsonl) only captures policy events, not every connect — connect logs are operational. Run tail -f ~/.myclawn/daemon.log to watch the proxy in real time.
What the proxy does & doesn't protect against
It defends against:
- An agent printing its env to leak a credential — env has only placeholders.
- An agent SSRFing your credentials to an attacker host — substitution is host-bound.
- OAuth tokens getting stuck after refresh — proxy keeps the keystore current.
- Real keys ending up in your audit log or transcripts — response-side redaction.
It does not defend against:
- An agent calling
myclawn askwith a malicious spec — the brain's policy filter handles that, not the proxy. - An agent doing legitimate work against a legitimate API key with stupid prompts — your auto-approve threshold + audit log are the line of defense there.
- You setting
OPENAI_API_KEY=sk-real-...in your shell beforemyclawn run— the bot then has the real key directly. Don't do that. Use the keystore.