You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(launcher): coexist with another MITM via ca-trust.d (cnighswonger#283)
NODE_EXTRA_CA_CERTS takes exactly one file, so on a host where something else also MITMs api.anthropic.com, whoever assigns it last silently untrusts every other CA. Measured 2026-07-30: two such components took turns breaking each other's TLS with neither logging anything.
Each component now publishes only <config>/ca-trust.d/<component>.pem; one external builder merges them into <config>/ca-trust.pem; consumers read the merged bundle if usable, else fall back to their own CA. Fixed names on both halves — a knob on one lets a component publish where no builder looks while still consuming the canonical bundle.
The reader is fail-closed by parsing, not substring matching: every PEM block must construct an X509Certificate and one must equal ours by DER. Accepting a bad bundle makes the client distrust its own proxy; rejecting a good one costs only the other components' CAs.
Gated behind --remote-control, which defaults off. A host with no other MITM and no bundle builder is byte-for-byte unchanged.
Verified locally merged onto main: full suite 1499/0. Codex approved at a329f21 after both round-1 contract blockers were fixed (temp reaper age-gated; publish-dir override removed).
Live multi-MITM validation is NOT complete — see the follow-up issue.
Closescnighswonger#283
Copy file name to clipboardExpand all lines: CHANGELOG.md
+8Lines changed: 8 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2,6 +2,14 @@
2
2
3
3
## [Unreleased]
4
4
5
+
### Fixed
6
+
7
+
- **`--remote-control` no longer clobbers another component's `NODE_EXTRA_CA_CERTS`.** That variable takes exactly one file, so on a host where something else also MITMs `api.anthropic.com` (a corporate agent, an account-pinning proxy) the last writer won and every other CA was silently untrusted — measured breaking Remote Control inbound. The launcher now publishes its own CA to `${CLAUDE_CONFIG_DIR:-~/.claude}/ca-trust.d/ccf.pem` (own filename only, never a sibling's, rewritten every launch, atomically via temp + `rename`) and reads a merged `ca-trust.pem` if one exists. It never writes the merged bundle: merging needs ambient corporate-root discovery, which is environment-specific and belongs outside this repo. The bundle is used only when every PEM block in it parses **and** one of them is our own CA (compared by DER) — a bundle that is torn or predates our publish is worse than none, since it makes the client distrust the very proxy it is routed through. On a host with no other MITM and no bundle, behavior is byte-identical to before. Both paths are fixed names under the config dir with no env override: they are two halves of one rendezvous, so a knob on either half alone would let a participant drop out of the contract while appearing to implement it. See [Coexisting with another MITM](README.md#coexisting-with-another-mitm-on-the-same-machine-ca-trustd).
8
+
9
+
### Documentation
10
+
11
+
-**`CACHE_FIX_DOWNLOAD_REWRITE=on` disables `claude update` entirely**, which the flag's name does not suggest. Rewriting a download URL requires MITM-ing `downloads.claude.ai`, whose release client pins public roots only, so the version check fails before anything downloads. It cannot be narrowed to the binary path (MITM is decided per host at `CONNECT`, and the version check shares the host) and no client-side override reaches that client. Documented with the measurement in the README.
12
+
5
13
## [4.3.0] - 2026-07-17
6
14
7
15
Headline: **Remote Control works through the proxy.** Claude Code ≥ 2.1.196 disables Remote Control / mobile session visibility (and `/schedule`, claude.ai MCP connectors) whenever `ANTHROPIC_BASE_URL` is set — which is exactly how reverse-proxy mode routes the client. This release adds an opt-in **forward-proxy mode** that keeps the client first-party (`ANTHROPIC_BASE_URL` unset, `HTTPS_PROXY` set) so those features keep working while the proxy still sees and transforms `/v1/messages`. All changes are additive and backward-compatible; every new mode is opt-in and defaults are unchanged.
-**It cannot be narrowed to the binary download.** MITM is decided per host at
180
+
`CONNECT` time, and the version check shares `downloads.claude.ai` with the
181
+
download itself. It is all-or-nothing per host.
182
+
-**No client-side override reaches that client.**`HTTPS_PROXY` / `ALL_PROXY`,
183
+
`/etc/hosts`, `/etc/resolv.conf`, and `NODE_EXTRA_CA_CERTS` were each
184
+
disproved against a control on the identical path — a local resolver logged 0
185
+
queries and a TCP forwarder logged 0 connects across a full `claude update`,
186
+
while a plain `node https.get` through that same forwarder returned 200. So no
187
+
amount of CA injection can make the rewrite work. Only not intercepting works.
188
+
189
+
Other hosts are unaffected: `github.com` through the same proxy returns its real
190
+
certificate and verifies. The flag is off by default; keep it that way unless you
191
+
are prepared to update Claude Code some other way.
192
+
102
193
### What the proxy does
103
194
104
195
On every `/v1/messages` request, the pipeline runs an ordered chain of extensions covering cache stability, observability, thinking-desync mitigation, image, microcompact, breakpoint, bootstrap-channel, and other surfaces. Several are gated behind env vars documented in their own sections below; bootstrap-channel handling defaults to `audit` mode. The headliners:
0 commit comments