首页
/ Headroom 差分网络捕获:用 mitmproxy 对比 Claude Code 直连与经代理流量的完整方法

Headroom 差分网络捕获:用 mitmproxy 对比 Claude Code 直连与经代理流量的完整方法

2026-09-04 19:21:41作者:齐添朝

本文介绍 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_BUNDLESSL_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-directclaude-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、自增 sequencetimestampmethod、脱敏后的 urlhost、脱敏后的 request_headers / response_headersrequest_body_sizerequest_body_sha256、截断后的 request_body_b64request_body_truncated、解析后的 request_json,以及响应侧的 response_statusresponse_headersresponse_body_sizeresponse_body_sha256

脱敏规则同样在插件中定义,且与报告侧完全对齐:

  • 敏感请求头:键名包含 authorizationapi-keyapikeytokensecretcookie 之一的,值替换为 <redacted>
  • 敏感查询参数:键名包含 keytokensecretsignaturecode 之一的,值替换为 <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 的比对流程是:

  1. 配对_pair_exchanges--pair-by 指定的 key(path_keyroute_key)把两条通道的记录分组,组内按出现顺序两两 zip;多出来的记录进入 only_direct / only_headroom 列表。
  2. JSON 字段级 diff_json_paths 把两侧请求体递归展开成 JSON 路径字典($.key$.messages[0].content 这种形式),再做集合差集与逐路径值比较,得到 only_directonly_headroomchanged 三类字段。
  3. 请求头 diff_header_delta 以不区分大小写的方式比较键集合与值,输出仅 direct 侧存在、仅 headroom 侧存在、以及值发生变化的头列表。
  4. 请求体指纹:报告同时给出两侧 request_body_size(含差值 delta)与 request_body_sha256 是否一致,用于快速判断"代理是否改动了请求体"。
  5. 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%3Eauthorization 头被替换为 <redacted>x-headroom-mode 出现在 headroom 侧独有头中、$.metadata$.messages[0].content 分别落入 only_headroomchanged,以及 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.jsonldirect.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_ARGSCLAUDE_HEADROOM_ARGS

这些变量的执行语义由 run-claude-lane.sh 决定,它作为 runner 容器的入口点:

  1. 若设置了 NODE_EXTRA_CA_CERTS(direct 通道用于信任 mitmproxy CA),先轮询最多 100 次(每次 0.1 秒)等待 CA 证书文件出现,规避容器启动时序问题;
  2. CLAUDE_COMMAND 非空,以 sh -lc "$CLAUDE_COMMAND" 原样执行并直接返回其退出码;
  3. 否则拼接 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.pyheadroom/cli/capture.py,行为基线由 tests/test_network_diff_capture.py 锁定,可作为代理兼容性调查与回归取证的标准工作流。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
982
502
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384