Skip to content
源码分析手册

Upstream 代理:容器里的 TLS 隐身术

先搞清楚这是什么

Upstream 代理(upstream-proxy)是 Claude Code 在远程开发环境(CCR, Claude Code Remote)中实现网络管控的核心组件。它本质上是一个带中间人攻击(MITM)能力的 TLS 代理

当你在 CCR 容器里执行 curlgit push 时,请求并不会直接飞往互联网。相反,它会被路由到一个本地监听的代理服务器,通过 WebSocket 隧道加密传回 Anthropic 的服务器,并在那里被注入必要的企业凭证(如 Datadog API Key)后,再转发给真正的上游目标。

实现机制

  1. 双引擎中继(Dual-runtime Relay): 为了适配不同的运行环境,代理中继层实现了两套逻辑。在 Bun 运行时下使用 Bun.listen() 并配合手动实现的写入缓冲(防止内核缓冲区满导致丢包);在 Node.js 环境下则回退到经典的 net.createServer()。这种设计确保了代理在各种容器镜像中都能稳定运行。

  2. 硬核 Protobuf 编码: 为了极致的性能和减少依赖,relay.ts 并没有使用庞大的 protobuf 库,而是手动实现了 UpstreamProxyChunk 的二进制编码。它直接操作 Uint8Array,按照 [0x0a (tag)] + [varint (length)] + [data] 的格式拼接字节流。

  3. 内存级别的安全防护: 为了防止 Prompt 注入攻击通过 gdb -p 等手段在内存堆栈中窃取会话令牌(Session Token),代码通过 Bun FFI 调用了 Linux 原生的 prctl(PR_SET_DUMPABLE, 0)。这会禁止其他进程对该进程进行 ptrace 调试。

  4. 证书注入与环境变量: 启动时,它会从服务端下载专用的 CA 证书,将其与系统原有的 CA Bundle 合并,并强制注入到子进程的 SSL_CERT_FILEHTTPS_PROXY 等环境变量中。这样,即使是 Python 的 requests 或 Go 的 http.Client 也能无缝信任这个 MITM 代理。

  5. 令牌自毁: 一旦代理启动成功并确认可以建立连接,它会立即执行 unlink 删除磁盘上的 Token 文件。Token 从此只存在于堆内存中,最大限度缩短了敏感信息的暴露时长。

别踩这些坑

  • Fail-open 设计:代理遵循“失败即放行”原则。如果证书下载失败或中继启动报错,它会记录警告并禁用代理,而不是让整个会话崩溃。
  • 显式绕过清单NO_PROXY 列表非常关键,它显式排除了 anthropic.com(防止套娃)、github.com 以及各大包管理器镜像(npm, PyPI),确保核心通信不受拦截干扰。
  • 仅限 HTTPS:该代理主要处理 CONNECT 请求,明文 HTTP 默认不参与凭证注入。

推荐阅读路径

如果你对 Claude 如何在受限网络环境下与外界通信感兴趣,建议阅读 src/utils/proxy.ts 了解基础代理配置,或者查看 src/utils/mtls.ts 了解背后的双向 TLS 认证细节。

源码锚点

  • src/upstreamproxy/upstreamproxy.ts: 容器侧的编排逻辑、prctl 调用、CA 合并。
📄 src/upstreamproxy/upstreamproxy.ts — 容器侧的编排逻辑、`prctl` 调用、CA 合并。L231-234 of 286
typescript
      prctl: {
        args: ['int', 'u64', 'u64', 'u64', 'u64'],
        returns: 'int',
      },
  • src/upstreamproxy/relay.ts: CONNECT-over-WebSocket 的具体实现,包括手写 Protobuf 编码。
📄 src/upstreamproxy/relay.ts — CONNECT-over-WebSocket 的具体实现,包括手写 Protobuf 编码。L3-15 of 456
typescript
 * CONNECT-over-WebSocket relay for CCR upstreamproxy.
 *
 * Listens on localhost TCP, accepts HTTP CONNECT from curl/gh/kubectl/etc,
 * and tunnels bytes over WebSocket to the CCR upstreamproxy endpoint.
 * The CCR server-side terminates the tunnel, MITMs TLS, injects org-configured
 * credentials (e.g. DD-API-KEY), and forwards to the real upstream.
 *
 * WHY WebSocket and not raw CONNECT: CCR ingress is GKE L7 with path-prefix
 * routing; there's no connect_matcher in cdk-constructs. The session-ingress
 * tunnel (sessions/tunnel/v1alpha/tunnel.proto) already uses this pattern.
 *
 * Protocol: bytes are wrapped in UpstreamProxyChunk protobuf messages
 * (`message UpstreamProxyChunk { bytes data = 1; }`) for compatibility with

基于 Claude Code v2.1.88 开源快照的深度分析