Headroom 差分网络捕获:用 mitmproxy 对比 Claude Code 直连与经代理流量的完整方法
本文介绍 Headroom 提供的差分网络捕获(Differential Network Capture)工具链:一个基于 Docker Compose 与 mitmproxy 的容器化测试台架,用于把 Claude Code 直连 Anthropic 的流量与经过 Headroom 代理转发后的流量逐请求对比,生成包含路由、请求头、请求体大小、请求体哈希和 JSON 字段级差异的脱敏报告。读完本文后,你将能够独立搭建双通道捕获环境、执行 Claude Code 对照实验、运行 headroom capture network-diff 生成 Markdown/JSON 差分报告,并理解每一环(mitmproxy 插件、CLI 报告器、配对与 JSON 路径展开逻辑)的源码实现细节。
工具链总览:双通道、双代理、三份捕获文件
Headroom 仓库在 wiki/network-diff-capture.md 中描述了这套差分捕获台架的定位:用一个容器化的 harness 比较同一 Claude Code 命令分别"直连 Anthropic"和"经过 Headroom 代理"时产生的真实网络流量。台架使用两个相互隔离的 mitmproxy 通道(lane),写出脱敏后的 JSONL 捕获文件,然后生成 Markdown/JSON 报告,报告内容包括请求路由、请求头差异、请求体字节数、请求体 SHA-256 哈希以及 JSON 载荷的字段级差异。
从 docker/differential-network-capture/docker-compose.yml 的编排结构看,整套 harness 由 6 个服务组成:
| 服务 | 作用 | 关键配置 |
|---|---|---|
mitm-direct |
direct 通道的 mitmdump(regular 模式) | 宿主机端口 ${DIRECT_MITM_PORT:-18080},CAPTURE_LANE=direct,输出 direct.jsonl |
mitm-headroom-client |
Headroom 通道入口,reverse 模式转发到 headroom-proxy:8787 |
宿主机端口 ${HEADROOM_CLIENT_MITM_PORT:-18082},输出 headroom-client.jsonl |
mitm-headroom-upstream |
位于 Headroom 代理与 Anthropic 之间的上游 mitmdump(regular 模式) | 宿主机端口 ${HEADROOM_MITM_PORT:-18081},输出 headroom-upstream.jsonl |
headroom-proxy |
用仓库根 Dockerfile 构建的 Headroom 代理 | --host 0.0.0.0 --port 8787 --backend anthropic |
claude-direct |
在 direct 通道中运行 Claude Code(run profile) |
通过 HTTPS_PROXY/HTTP_PROXY 指向 mitm-direct:8080 |
claude-headroom |
在 Headroom 通道中运行 Claude Code(run profile) |
通过 ANTHROPIC_BASE_URL=http://mitm-headroom-client:8080 接入 |
其中有一个值得注意的设计:headroom-proxy 容器本身也通过 HTTPS_PROXY/HTTP_PROXY 环境变量指向 mitm-headroom-upstream:8080,并配置了 REQUESTS_CA_BUNDLE 与 SSL_CERT_FILE 指向 mitmproxy CA 证书卷。也就是说,Headroom 代理发往 Anthropic 的出站流量同样被第三个 mitmdump 拦截并记录,形成 headroom-upstream.jsonl——这就是文档中提到的"Headroom 在处理之后转发给 Anthropic 的请求"的捕获来源。
两个 Claude 执行容器都通过 Dockerfile.runner 构建,基础镜像为 node:24-bookworm-slim,安装 ca-certificates curl git python3 后执行 npm install -g ${CLAUDE_CODE_PACKAGE}(默认为 @anthropic-ai/claude-code,可通过构建参数覆盖),并将仓库根目录以只读方式挂载为 /workspace,保证两条通道跑在同一份代码、同一工作区上。
运行台架:命令、环境变量与捕获输出
基本运行步骤
按文档给出的最小流程,在仓库根目录执行:
cd docker/differential-network-capture
mkdir -p captures
export ANTHROPIC_API_KEY=...
export CLAUDE_PROMPT="Summarize this repository in one sentence."
docker compose up --build mitm-direct mitm-headroom-upstream headroom-proxy mitm-headroom-client
docker compose --profile run run --rm claude-direct
docker compose --profile run run --rm claude-headroom
要点说明:
docker compose up --build只启动 4 个"常驻"服务(两个 mitmproxy、一个上游 mitmproxy、一个 Headroom 代理),Claude 执行器通过--profile run run --rm按需拉起,跑完即删。ANTHROPIC_API_KEY是必填项:headroom-proxy服务在 compose 文件中以${ANTHROPIC_API_KEY:?Set ANTHROPIC_API_KEY}形式声明,未设置时 compose 会直接报错退出。- 两条通道的
claude-direct与claude-headroom都会注入同一个CLAUDE_PROMPT(默认值即Summarize this repository in one sentence.),确保对照组与实验组执行相同的任务。 headroom-proxy的上游目标默认为ANTHROPIC_TARGET_API_URL=https://api.anthropic.com,可按需覆盖。
捕获文件与主机过滤
运行完成后,主捕获文件写入:
docker/differential-network-capture/captures/direct.jsonl(direct 通道)docker/differential-network-capture/captures/headroom-client.jsonl(Claude Code 发给 Headroom 客户端入口的请求)
Headroom 通道额外产出:
docker/differential-network-capture/captures/headroom-upstream.jsonl(Headroom 处理后发往 Anthropic 的最终请求)
默认只记录 api.anthropic.com 主机上的流量。该过滤逻辑实现在 mitmproxy 插件 docker/differential-network-capture/mitm_capture.py 中:插件在启动时解析环境变量 CAPTURE_INCLUDE_HOSTS(逗号分隔)构建白名单集合,response 钩子里对每条 flow 的 pretty_host 做小写比较,不在集合内直接丢弃:
INCLUDE_HOSTS = {
host.strip().lower()
for host in os.environ.get("CAPTURE_INCLUDE_HOSTS", "api.anthropic.com").split(",")
if host.strip()
}
...
host = flow.request.pretty_host.lower()
if INCLUDE_HOSTS and host not in INCLUDE_HOSTS:
return
compose 文件中各服务对应的覆盖变量:direct 与 headroom-upstream 用 ${CAPTURE_INCLUDE_HOSTS:-api.anthropic.com},而客户端入口 mitm-headroom-client 用 ${CAPTURE_CLIENT_INCLUDE_HOSTS:-mitm-headroom-client,headroom-proxy,api.anthropic.com}——因为它工作在 reverse 模式,代理上游的流量以内部主机名呈现。另外 CAPTURE_BODY_BYTES(默认 262144,即 256 KiB)控制每条记录中请求体 base64 预览的截断长度,超过部分以 request_body_truncated: true 标记。
JSONL 记录结构与脱敏机制
插件每条记录写入一行紧凑 JSON(json.dumps(..., separators=(",", ":"), sort_keys=True)),字段包括:lane、自增 sequence、timestamp、method、脱敏后的 url、host、脱敏后的 request_headers / response_headers、request_body_size、request_body_sha256、截断后的 request_body_b64、request_body_truncated、解析后的 request_json,以及响应侧的 response_status、response_headers、response_body_size、response_body_sha256。
脱敏规则同样在插件中定义,且与报告侧完全对齐:
- 敏感请求头:键名包含
authorization、api-key、apikey、token、secret、cookie之一的,值替换为<redacted>; - 敏感查询参数:键名包含
key、token、secret、signature、code之一的,值替换为<redacted>,URL 重新编码后写回记录。
生成报告:headroom capture network-diff
捕获完成后,使用 CLI 生成差分报告:
headroom capture network-diff \
--direct docker/differential-network-capture/captures/direct.jsonl \
--headroom docker/differential-network-capture/captures/headroom-client.jsonl \
--output docker/differential-network-capture/captures/report.md \
--json-output docker/differential-network-capture/captures/report.json
该命令由 headroom/cli/capture.py 注册(capture 分组下的 network-diff 子命令),完整参数为:
| 参数 | 必填 | 说明 |
|---|---|---|
--direct |
是 | direct 通道的 JSONL 捕获文件(click.Path(exists=True),路径必须存在) |
--headroom |
是 | Headroom 通道的 JSONL 捕获文件 |
--output |
否 | Markdown 报告输出路径;不指定时打印到 stdout |
--json-output |
否 | 机器可读的 JSON diff 输出路径 |
--pair-by |
否 | 配对粒度,path(method+path)或 route(method+host+path),默认 path |
报告生成前会对敏感请求头值与敏感查询值再做一次脱敏,因此即便直接查看报告文件也不会泄露凭证。文档同时提醒:请求体会被完整捕获以便识别结构差异,生成的 captures/ 目录可能包含提示词、工具输出和仓库上下文,务必不要提交进版本库。
配对与差异计算:源码视角
报告的核心逻辑在 headroom/capture/network_diff.py。加载阶段 load_capture_file 逐行解析 JSONL,对损坏行(例如 mitmproxy 写一半被终止的捕获)只跳过并记 warning,而不是让整个 diff 因 JSONDecodeError 中断;每条记录经 exchange_from_record 转成不可变的 CapturedExchange 数据类,缺省字段(如缺失的 request_body_sha256)会从 base64 请求体现场补齐。
compare_captures 的比对流程是:
- 配对:
_pair_exchanges按--pair-by指定的 key(path_key或route_key)把两条通道的记录分组,组内按出现顺序两两 zip;多出来的记录进入only_direct/only_headroom列表。 - JSON 字段级 diff:
_json_paths把两侧请求体递归展开成 JSON 路径字典($.key、$.messages[0].content这种形式),再做集合差集与逐路径值比较,得到only_direct、only_headroom、changed三类字段。 - 请求头 diff:
_header_delta以不区分大小写的方式比较键集合与值,输出仅 direct 侧存在、仅 headroom 侧存在、以及值发生变化的头列表。 - 请求体指纹:报告同时给出两侧
request_body_size(含差值 delta)与request_body_sha256是否一致,用于快速判断"代理是否改动了请求体"。 - Anthropic 专项摘要:
_anthropic_request_summary额外提取anthropic-beta请求头、顶层tools数组的数量与序列化字节数。
render_markdown_report 最终渲染出 Summary(direct/headroom 交换次数、配对数、单边记录数)、Only Direct / Only Headroom 列表,以及 Paired Exchanges 表格,表列为 Route | Status | Body Bytes | Body SHA | Header Delta | JSON Delta,其中 JSON Delta 一列末尾会追加 tools=0->N (+X bytes) 形式的工具统计。
测试用例 tests/test_network_diff_capture.py 验证了上述行为:?api_key=secret 查询参数被改写为 %3Credacted%3E、authorization 头被替换为 <redacted>、x-headroom-mode 出现在 headroom 侧独有头中、$.metadata 与 $.messages[0].content 分别落入 only_headroom 和 changed,以及 tools=0->1 出现在 Markdown 输出中。
用例:排查 Claude Code 延迟工具(deferred-tools)问题
文档给出了这套台架的一个典型调查场景:对 Claude Code 的延迟工具机制做取证。配对的交换表中包含 Anthropic 顶层 tools 的数量与序列化字节数(即上文第 4 点提到的 _anthropic_request_summary 输出)。如果在 Headroom 客户端通道出现 tools=0->N 的跳变——direct 侧请求体 tools 为空而 headroom-client 侧已携带 N 个工具 schema——这就是 Claude Code 在请求到达 Headroom 之前就急切地物化了工具 schema(eagerly materialized tool schemas)的证据,而不是由 Headroom 注入的。这一判断依据完全来自 headroom-client.jsonl 与 direct.jsonl 的字节级对比,无需修改 Claude Code 源码。
自定义 Claude 调用命令
当需要复现特定命令行为时,可用 CLAUDE_COMMAND 让两条通道执行完全相同的测试命令:
CLAUDE_COMMAND='claude -p "read README.md and summarize the proxy setup"' \
docker compose --profile run run --rm claude-direct
CLAUDE_COMMAND='claude -p "read README.md and summarize the proxy setup"' \
docker compose --profile run run --rm claude-headroom
若两条通道需要不同的命令行参数(例如实验组追加代理相关 flag),则分别使用 CLAUDE_DIRECT_ARGS 与 CLAUDE_HEADROOM_ARGS。
这些变量的执行语义由 run-claude-lane.sh 决定,它作为 runner 容器的入口点:
- 若设置了
NODE_EXTRA_CA_CERTS(direct 通道用于信任 mitmproxy CA),先轮询最多 100 次(每次 0.1 秒)等待 CA 证书文件出现,规避容器启动时序问题; - 若
CLAUDE_COMMAND非空,以sh -lc "$CLAUDE_COMMAND"原样执行并直接返回其退出码; - 否则拼接
claude ${CLAUDE_ARGS:-} -p "${CLAUDE_PROMPT:-...}",其中CLAUDE_ARGS由 compose 从CLAUDE_DIRECT_ARGS/CLAUDE_HEADROOM_ARGS注入。
小结
Headroom 的差分网络捕获把"代理是否改动了客户端流量"这一原本只能靠日志猜测的问题,变成了可重复、可取证的字节级实验:mitmproxy 插件负责双通道(外加代理上游)的脱敏 JSONL 落盘,headroom capture network-diff 负责配对、JSON 路径展开与多粒度差异渲染,Docker Compose 负责把 Claude Code、Headroom 代理与三台 mitmdump 编排到确定性的网络拓扑中。相关实现集中在 docker/differential-network-capture/、headroom/capture/network_diff.py、headroom/cli/capture.py,行为基线由 tests/test_network_diff_capture.py 锁定,可作为代理兼容性调查与回归取证的标准工作流。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00