MITM TPROXY Transparent Decrypt (中文 (简体))
§1 它是什么以及何时使用
Section titled “§1 它是什么以及何时使用”其他四种捕获模式各有一项局限:
| 模式 | 流量引导方式 | 局限 |
|---|---|---|
| AgentBridge | 通过 /etc/hosts 对固定主机集进行 DNS 欺骗 |
仅支持已注册的 IDE 代理主机 |
| 自定义主机 | 通过 /etc/hosts 对每个主机进行 DNS 欺骗 |
每个主机需要一个条目;编辑 hosts 需要 sudo |
| HTTP_PROXY | HTTP_PROXY/HTTPS_PROXY 环境变量 |
仅支持遵循该环境变量的应用 |
| 系统级代理 | 操作系统代理设置 | 会修改全局状态;需要还原 |
TPROXY 透明解密改为在内核层引导流量。它在
mangle OUTPUT 链中标记发往目标端口(默认为 443)的新建本地出站 TCP 连接,
ip rule 将已标记的数据包重新路由至本地交付,
重新进入后,mangle PREROUTING 中的 TPROXY 目标会将其交给
IP_TRANSPARENT 监听器——该监听器随后终止 TLS 并捕获明文。
当你希望捕获并解密来自符合以下条件的进程的流量时,请使用此模式:
- 连接到 AgentBridge 未注册的主机,并且
- 不遵循
HTTP_PROXY,并且 - 你不希望通过更改系统级代理来干扰该进程。
由于拦截发生在内核中,发起连接的进程无需进行任何配置更改 ——但该进程必须信任 OmniRoute 安装的动态 CA (请参阅§4)。
| 要求 | 详情 |
|---|---|
| 操作系统 | 仅限 Linux — IP_TRANSPARENT 是 Linux 独有的套接字选项。在其他所有平台上,加载器均返回“不可用”。 |
| 权限 | 需要 CAP_NET_ADMIN 能力才能创建透明套接字并应用 iptables/ip 规则 — 实际使用中应以 root 身份运行。 |
| 原生插件 | 必须构建一个小型 N-API 插件 (src/mitm/tproxy/native/transparent.c),或将其作为预构建版本提供。请参阅 §3。 |
| 内核模块 | iptables 需支持 TPROXY、mangle 和 mark 匹配(已针对内核 6.8.0 验证)。 |
优雅降级:如果缺少任何要求(非 Linux、没有工具链、插件未构建),
插件加载器 (src/mitm/tproxy/transparentSocket.ts::loadTransparentAddon)
会返回 null,而不是抛出异常。随后,捕获模式状态会报告
available: false,仪表板开关将被禁用,并显示工具提示
“TPROXY 解密需要 Linux + root + 原生插件”,OmniRoute 的其余功能则会
继续正常运行。
§3 原生 IP_TRANSPARENT 插件
Section titled “§3 原生 IP_TRANSPARENT 插件”Node 的 net 模块无法在 bind() 之前 调用 setsockopt(IP_TRANSPARENT),
而这是 TPROXY 的要求(否则内核会丢弃重定向的数据包)。该插件
(src/mitm/tproxy/native/transparent.c,通过 binding.gyp 构建)是一个小型 N-API
模块,公开三个函数,并通过 transparentSocket.ts 使用:
| 插件函数 | 套接字操作 | 用途 |
|---|---|---|
createTransparentListener(ip, port) |
socket() + SO_REUSEADDR + IP_TRANSPARENT + bind() + listen(),返回原始 fd |
透明捕获监听器(Node 通过 server.listen({ fd }) 接管该 fd) |
setSocketMark(fd, mark) |
在现有 fd 上调用 setsockopt 设置 SO_MARK |
防止循环(为代理自身的套接字设置标记) |
connectMarked(ip, port, mark) |
在非阻塞 connect() 之前 执行 socket() + SO_MARK,返回 fd |
重新加密后的上游转发(SYN 携带该标记) |
原始目标地址从 socket.localAddress/localPort 读取 — TPROXY
会保留它,因此无需执行 SO_ORIGINAL_DST/NAT 查找。
npm run build:native:tproxy # 进入 src/mitm/tproxy/native 并执行 node-gyp rebuild # -> native/build/Release/transparent.node- 执行
npm run build期间,scripts/build/build-tproxy-native.mjs会运行node-gyp rebuild。此过程仅限 Linux 且失败不会中止构建 — 缺少工具链只会使 捕获模式不可用。 assembleStandalone.mjs会将build/Release/transparent.node复制到 独立运行包中;transparentSocket.ts会同时通过相对于模块和相对于 cwd 的路径 (<cwd>/src/mitm/tproxy/native/...) 解析该文件。build/和prebuilds/已被 git 忽略 — 二进制文件会被构建,但绝不 提交。
加载器按以下优先顺序探测:
native/build/Release/transparent.node,然后是 native/prebuilds/transparent.node
(同时检查相对于模块的路径和 <cwd>/src/mitm/tproxy/ 下的路径)。
§4 按 SNI 动态生成的 CA 与信任库安装程序
Section titled “§4 按 SNI 动态生成的 CA 与信任库安装程序”#6684 更新: AgentBridge 静态服务器(
src/mitm/server.cjs)现在 也采用相同的 CA/叶证书架构模式,而不再使用单个静态 自签名叶证书。它使用一个独立的 CA 实例 (src/mitm/cert/rootCa.ts,持久化于<DATA_DIR>/mitm/ca.key/ca.crt) 并安装到已有的omniroute-mitm.crt信任库槽位中 (替换其中旧的单一叶证书——无需清理双重信任), 与下文 TPROXY 自己的omniroute-tproxy-ca.crt槽位完全隔离。 全新安装的 AgentBridge 会自动使用 CA 模型;已经 信任旧静态叶证书的安装则会继续使用它,直到操作员通过MITM_ROOT_CA_ENABLED=true主动选择启用(参见src/mitm/cert/migration.ts)—— 一个能够为任何主机签发叶证书的可信 MITM CA,其能力实质上 远强于旧的固定 SAN 叶证书,因此对于已建立信任的安装, 绝不会静默切换。
过去,静态 AgentBridge MITM 证书之所以可用,只是因为 AgentBridge
仅对一组固定的主机进行 DNS 欺骗(现在已与下文模型统一)。TPROXY
会拦截任意主机,因此其监听器必须针对客户端请求的任意 SNI
提供有效的叶证书——AgentBridge 现在也有同样的要求:需覆盖完整的
MITM_TOOL_HOSTS 集合(9 个工具条目),而不再只是 4 个
antigravity 主机。
动态 CA(src/mitm/tproxy/dynamicCert.ts)
Section titled “动态 CA(src/mitm/tproxy/dynamicCert.ts)”DynamicCertStore 运行一个本地 CA(基于 selfsigned 依赖项构建),它:
- 通过
generateMitmCa()生成一个长期有效的 CA(CN"OmniRoute MITM CA", 有效期 10 年,basicConstraints CA=true+keyUsage keyCertSign,cRLSign, 2048 位 RSA / SHA-256)。 - 通过
issueLeafCert()按需为每个 SNI 主机名签发叶证书(有效期 1 年,subjectAltName= SNI 主机),并为每个主机名缓存一个tls.SecureContext。 - 为终止 TLS 的服务器提供
createSNICallback()(参见 §5)。 - 可通过
existingCa构造,以确保重启后 CA 保持不变 (因此无需重新安装到信任库)。
CA 私钥绝不会离开本机。
信任库安装程序(src/mitm/tproxy/caTrust.ts)
Section titled “信任库安装程序(src/mitm/tproxy/caTrust.ts)”被拦截的客户端必须信任动态 CA,因此启动捕获模式时,
会将 CA 证书安装到操作系统信任库中的一个专用槽位——
omniroute-tproxy-ca.crt(常量 TPROXY_CA_CERT_NAME)——该槽位与
静态 MITM 证书的槽位(omniroute-mitm.crt)保持独立,确保两者
绝不会相互覆盖。
installTproxyCa(caPem, sudoPassword?) 会检测发行版的锚点目录
(按顺序检测:优先 Debian 风格),并运行对应的刷新命令:
| 锚点目录 | 刷新命令 |
|---|---|
/usr/local/share/ca-certificates |
update-ca-certificates |
/etc/ca-certificates/trust-source/anchors |
update-ca-trust |
/etc/pki/ca-trust/source/anchors |
update-ca-trust |
/etc/pki/trust/anchors |
update-ca-certificates |
安装时会先将 PEM 暂存到临时文件,然后以特权方式对锚点目录执行
mkdir -p,使用 cp 将暂存文件复制到其中,并运行刷新命令。
uninstallTproxyCa() 只会移除专用槽位(不影响静态 MITM 证书)
并刷新信任库——在非 Linux 系统上不执行任何操作。
所有特权命令都通过 execFileWithPassword(src/mitm/systemCommands.ts)
运行——使用带有参数数组、无 shell、无字符串插值的 spawn
(硬性规则 #13)。当进程以 root 身份运行时(例如在 VPS 上),
目标命令会直接运行,无需密码;在非 root 桌面环境中,sudoPassword
会通过 stdin 传递给 sudo -S。
桌面端的
sudoPassword通过 POST 请求正文提供,用于授权安装 信任库;当进程以 root 身份运行时,会完全忽略该参数。
§5 解密和捕获的工作原理
Section titled “§5 解密和捕获的工作原理”处理管线(全部位于 src/mitm/tproxy/ 下):
本地应用 ──TCP/443──▶ mangle OUTPUT 标记连接 (fwmark) ip rule → 本地路由表 → lo mangle PREROUTING TPROXY → IP_TRANSPARENT 监听器(端口 8443) │ captureMode.ts:从 socket.localAddress 读取原始目标地址 ▼ tlsCapture.ts: 1. 使用按 SNI 生成的叶证书 (dynamicCert) 终止客户端的 TLS 2. 内部 http.Server 解析解密后的明文 3. 捕获 → globalTrafficBuffer.push(),source: "tproxy" (应用 sanitizeHeaders + maskSecret) 4. 通过带绕过标记的套接字重新加密并转发到原始目标 (connectMarked,防循环) │ ▼ 原始上游 (api.example.com)- TLS 终止 (
createTlsCaptureServer):使用动态 CA 的 SNI 回调,将原始拦截套接字封装到服务端tls.TLSSocket中,然后将解密后的流交给内部http.Server(标准的 MITM 终止技巧)。套接字生命周期受MITM_IDLE_TIMEOUT_MS限制,因此挂起的隧道无法耗尽文件描述符。 - 捕获 (
handleDecryptedRequest):推送一个InterceptedRequest,其中source: "tproxy",状态初始为"in-flight";标头先经过sanitizeHeaders(),正文先经过maskSecret(),然后才进入缓冲区。随后使用响应、大小和延迟信息更新该条目。 - 重新加密转发 (
createForward/realForward):重新加密并转发到原始目标。rejectUnauthorized默认为true(默认安全)——根据客户端请求的 SNI/Host 验证上游证书,因此原始客户端会拒绝的内容,代理也会同样拒绝。
防循环 (SO_MARK)
Section titled “防循环 (SO_MARK)”由于规则会标记新的本地出站连接,代理自身重新加密后的转发通常也会被再次拦截,从而形成无限循环。转发路径使用绕过套接字标记 (SO_MARK) 来防止这种情况:
realForward通过connectMarked(ip, port, DEFAULT_BYPASS_MARK)打开其上游套接字——DEFAULT_BYPASS_MARK = 0x539——它会在connect()之前设置 SO_MARK,因此转发连接的 SYN 会携带绕过标记。mangle OUTPUT规则排除已经携带绕过标记的连接 (-m mark ! --mark <bypassMark>),因此代理的转发连接不会被重新标记,也不会再次进入 TPROXY。
实现说明:必须在代理的
createConnection上安装带绕过标记的套接字(存在代理时,https.request({ createConnection })会被静默忽略),否则转发过程将打开一个未标记的套接字,循环便会再次出现。这是经过 e2e 验证的防循环修复。
§6 安全性
Section titled “§6 安全性”| 控制措施 | 详细说明 |
|---|---|
| 仅限环回的 API | /api/tools/agent-bridge/tproxy 由 LOCAL_ONLY_API_PREFIXES(src/server/authz/routeGuard.ts)中的 /api/tools/agent-bridge/ 前缀覆盖。环回限制在身份验证之前执行(硬性规则 #15 + #17)——通过隧道泄露的 JWT 无法启动 TPROXY 捕获;该捕获会应用 iptables 规则,并通过子进程安装信任存储区 CA。 |
| 专用 CA 位置 | 动态 CA 安装到 omniroute-tproxy-ca.crt,绝不会覆盖静态 MITM 证书。 |
| CA 密钥绝不离开主机 | DynamicCertStore 将 CA 密钥保存在内存中;不会将其导出。 |
| 敏感信息掩码 | 在调用 globalTrafficBuffer.push() 之前,对请求/响应正文运行 maskSecret(),并对标头运行 sanitizeHeaders()。 |
| 无 shell 插值 | 所有 iptables/ip/信任存储区命令均通过带有参数数组的 execFile/execFileWithPassword 运行(硬性规则 #13)。 |
| 上游证书验证 | 重新加密的转发默认验证上游证书(rejectUnauthorized: true)。 |
| 错误信息净化 | 该路由的错误响应会经过 sanitizeErrorMessage() 处理(硬性规则 #12)。 |
MITM CA 是一项强大的能力。 受操作系统信任且能够为任意 主机签名的 CA,意味着 OmniRoute 拦截的任何内容都可以被解密。它被限制在 显式启用且仅限本地的 TPROXY 捕获模式之后,默认关闭,并且在停止该模式时会从信任存储区中 移除相应条目。
§7 事务性防火墙应用 / 回滚
Section titled “§7 事务性防火墙应用 / 回滚”崩溃绝不能遗留 mangle 规则或陈旧路由。命令构建器
(src/mitm/tproxy/commands.ts)和执行器(src/mitm/tproxy/setup.ts)保证
回滚操作严格按照相反顺序执行应用操作的精确逆操作。
applyTproxy(cfg) 按顺序执行应用命令;发生任何失败时,它都会尽力执行完整的
revertTproxy(cfg),然后重新抛出异常——因此,防火墙要么完全应用,
要么完全回滚,绝不会处于部分应用状态。revertTproxy(cfg) 按相反顺序执行
逆向命令,并忽略失败(具有幂等性——可安全地无条件调用,
例如在 AgentBridge 的 repairMitm() 清理流程中调用)。
validateTproxyConfig(cfg) 会在任何命令执行前运行:端口必须为 1–65535,
mark/routeTable/bypassMark 必须为正整数,并且 bypassMark 必须
不同于 mark(防止循环)。
应用命令(按顺序)
Section titled “应用命令(按顺序)”ip rule add fwmark <mark> lookup <routeTable>ip route add local 0.0.0.0/0 dev lo table <routeTable>iptables -t mangle -A OUTPUT -p tcp --dport <dport> -m mark ! --mark <bypassMark> -j MARK --set-mark <mark>iptables -t mangle -A PREROUTING -p tcp --dport <dport> -m mark --mark <mark> -j TPROXY --on-port <onPort> --tproxy-mark <mark>回滚操作按相反顺序删除它们:PREROUTING -D、OUTPUT -D、ip route del、ip rule del。
此方案基于 OUTPUT,因为 MITM 的使用场景是_本机_出站 流量(同一主机上的应用),而仅使用
PREROUTING中的 TPROXY 无法 看到这些流量——PREROUTING只能看到转发流量。OUTPUT链会标记新的 本地连接,ip rule将其重新路由到本地交付(lo),随后PREROUTING将其分配给透明监听器。
启动请求(POST /api/tools/agent-bridge/tproxy)接受以下字段,
这些字段由 StartTproxyBodySchema(tproxy/route.ts)验证。所有字段均为可选,
未提供时将回退到其默认值:
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| dport | int (1–65535) | 443 |
要透明拦截的目标 TCP 端口 |
| mark | int (≥1) | 0x2333 |
在 OUTPUT 中设置的防火墙标记,由 ip rule + PREROUTING 匹配 |
| onPort | int (1–65535) | 8443 |
透明(IP_TRANSPARENT)监听器绑定的端口 |
| routeTable | int (≥1) | 233 |
保存 local 0.0.0.0/0 路由的策略路由表 id |
| bypassMark | int (≥1, ≠ mark) |
0x539 |
代理为其自身上游连接设置的绕过套接字标记(SO_MARK);在 OUTPUT 中排除(防止循环) |
| sudoPassword | string | — | 仅适用于非 root 桌面环境:授权安装信任存储;以 root 身份运行时忽略 |
TPROXY 没有环境变量——所有配置均通过 POST 请求体或上述默认值提供。
§9 从流量检查器启用
Section titled “§9 从流量检查器启用”- 打开流量检查器(
/dashboard/tools/traffic-inspector)。 - 在捕获模式工具栏中,找到 “TPROXY 解密” ⚠ 按钮
(
src/app/(dashboard)/dashboard/tools/traffic-inspector/components/CaptureModesToolbar.tsx)。 - 点击该按钮。它通过
startTproxyCaptureMode()(src/lib/inspector/tproxyCaptureApi.ts) 调用POST /api/tools/agent-bridge/tproxy,该调用会: 构建动态 CA、打开透明监听器、应用防火墙规则, 并将 CA 安装到操作系统信任存储区。 - 运行时,该切换按钮会变为琥珀色并显示实时拦截计数
(
· <interceptCount>)。被拦截的请求会出现在请求列表中, 并带有source: "tproxy"。 - 再次点击可停止——通过
stopTproxyCaptureMode()调用DELETE /api/tools/agent-bridge/tproxy,关闭监听器、卸载 CA, 并还原防火墙规则。
捕获模式状态(运行中 / 可用 / 拦截计数 / 监听器端口)来自
GET /api/tools/agent-bridge/tproxy(src/mitm/tproxy/captureManager.ts 中的
getCaptureStatus())。同一时间只能运行一个 TPROXY 会话——
启动第二个会话时将被拒绝,并返回“TPROXY 捕获模式已在运行”。
§10 故障排除
Section titled “§10 故障排除”切换按钮已禁用
Section titled “切换按钮已禁用”无法加载原生插件。请确认:当前系统为 Linux、已构建插件
(npm run build:native:tproxy),且进程可以加载 transparent.node。
isTransparentSocketAvailable() 控制该切换按钮;缺少插件时,
GET /api/tools/agent-bridge/tproxy 返回 available: false。
未捕获任何内容
Section titled “未捕获任何内容”- 确认被拦截的进程确实连接到配置的
dport(默认为443)。 - 确认该进程信任动态 CA。CA 以
omniroute-tproxy-ca.crt的名称安装;使用自身信任存储区的应用(Firefox/Chrome NSS) 可能还需要将证书添加到其信任存储区中。 - 运行 AgentBridge 诊断自检(请参阅
AGENTBRIDGE.md),以执行证书信任状态 / 服务器 健康检查。
崩溃后残留防火墙规则
Section titled “崩溃后残留防火墙规则”revertTproxy() 与应用操作完全相反,并且具有幂等性。停止该模式会还原
规则;如果 OmniRoute 在会话期间被终止,请使用 AgentBridge
修复操作(POST /api/tools/agent-bridge/repair)撤销残留的系统
状态(DNS 欺骗、根 CA、系统代理)。TPROXY mangle 规则和路由也会
在重启时自动清除。
无限循环 / 代理拦截自身的转发
Section titled “无限循环 / 代理拦截自身的转发”这是反循环场景。确认 bypassMark 与 mark 不同(验证逻辑会强制执行
此要求),并确认转发使用 connectMarked(realForward 中确实如此)。
请参阅§5 反循环。
§11 源码映射
Section titled “§11 源码映射”| 文件 | 职责 |
|---|---|
src/mitm/tproxy/commands.ts |
纯 iptables/ip 应用及还原命令构建器;validateTproxyConfig |
src/mitm/tproxy/setup.ts |
事务式 applyTproxy / revertTproxy 运行器(失败时回滚) |
src/mitm/tproxy/transparentSocket.ts |
原生插件加载器(loadTransparentAddon)、createTransparentListenerFd、connectMarked、setSocketMark、isTransparentSocketAvailable |
src/mitm/tproxy/native/transparent.c |
N-API 插件:createTransparentListener(IP_TRANSPARENT)、setSocketMark、connectMarked |
src/mitm/tproxy/native/binding.gyp |
node-gyp 构建清单 |
src/mitm/tproxy/dynamicCert.ts |
DynamicCertStore——按 SNI 生成动态 CA 并缓存叶证书 |
src/mitm/tproxy/caTrust.ts |
操作系统信任存储区安装/卸载(installTproxyCa / uninstallTproxyCa,专用槽位) |
src/mitm/tproxy/tlsCapture.ts |
TLS 终止解密引擎及重新加密的反循环转发 |
src/mitm/tproxy/captureMode.ts |
透明监听器编排;从 socket.localAddress 读取原始目标地址 |
src/mitm/tproxy/captureManager.ts |
单例生命周期:startCaptureMode / stopCaptureMode / getCaptureStatus |
src/app/api/tools/agent-bridge/tproxy/route.ts |
GET / POST / DELETE 路由(LOCAL_ONLY) |
src/lib/inspector/tproxyCaptureApi.ts |
客户端 fetch 辅助函数(fetchTproxyStatus / startTproxyCaptureMode / stopTproxyCaptureMode) |
HagiCode
HagiCode 是一套智能体编码工作台:结构化工作流、多 Agent 并行执行与 Hero Dungeon 视图,把想法变成真正交付的软件。
让想法更快变成好用的软件,让智能编码更聪明、更高效,也更有趣。

- Smart结构化工作流将意图转化为从想法到交付的可执行路径。
- Efficient多 Agent 工作流让调研、实现与审阅并行推进。
- FunHero Dungeon 让长时间编码协作更直观、更有参与感。