跳到內容
OmniRoute source

MITM TPROXY Transparent Decrypt (中文 (繁體))

其他四種擷取模式各有一項限制:

模式 流量導向方式 限制
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 專用的 socket 選項。在其他所有平台上,載入器都會回傳「不可用」。
權限 需要 CAP_NET_ADMIN capability,才能建立透明 socket 並套用 iptables/ip 規則 — 實務上應以 root 身分執行。
原生 addon 必須建置或以預先建置版本提供一個小型 N-API addon(src/mitm/tproxy/native/transparent.c)。請參閱 §3。
核心模組 iptables 須支援 TPROXY、mangle 與 mark 比對功能(已針對 kernel 6.8.0 驗證)。

優雅降級:若缺少任何需求(非 Linux、無工具鏈、 addon 未建置),addon 載入器(src/mitm/tproxy/transparentSocket.ts::loadTransparentAddon) 會回傳 null,而不是擲出例外。接著,擷取模式狀態會回報 available: false,儀表板切換開關會被停用,並顯示工具提示 「TPROXY 解密需要 Linux + root + 原生 addon」,而 OmniRoute 的其餘部分 仍會繼續運作。


Node 的 net 模組無法在 bind() 之前 呼叫 setsockopt(IP_TRANSPARENT),而這是 TPROXY 的必要條件(否則核心會丟棄重新導向的封包)。此 addon (src/mitm/tproxy/native/transparent.c,透過 binding.gyp 建置)是一個小型 N-API 模組,公開三個函式,並透過 transparentSocket.ts 使用:

Addon 函式 Socket 操作 用途
createTransparentListener(ip, port) socket() + SO_REUSEADDR + IP_TRANSPARENT + bind() + listen(),回傳原始 fd 透明擷取監聽器(Node 透過 server.listen({ fd }) 接管 fd)
setSocketMark(fd, mark) 對現有 fd 執行 setsockopt SO_MARK 防止迴圈(標記 proxy 自身的 socket)
connectMarked(ip, port, mark) socket() + 在非阻塞 connect() 之前設定 SO_MARK,回傳 fd 重新加密後的上游轉送(SYN 會攜帶該標記)

原始目的地會從 socket.localAddress/localPort 讀取 — TPROXY 會保留該資訊,因此不需要查詢 SO_ORIGINAL_DST/NAT。

Terminal window
npm run build:native:tproxy # cd 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 身分執行時,則會完全忽略該值。


處理管線(全部位於 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 產生的分支憑證終止用戶端的 TLS(dynamicCert)
2. 內部 http.Server 解析解密後的明文
3. 擷取 → globalTrafficBuffer.push(),source: "tproxy"
(套用 sanitizeHeaders + maskSecret)
4. 透過帶有繞過標記的 socket,重新加密並轉送至原始目的地
(connectMarked,防迴圈)
│
▼
原始上游(api.example.com)
  • TLS 終止(createTlsCaptureServer):使用動態 CA 的 SNI 回呼,將攔截到的原始 socket 包裝於伺服器端 tls.TLSSocket 中,接著把解密後的串流交給內部 http.Server(標準的 MITM 終止技巧)。Socket 的存續時間受 MITM_IDLE_TIMEOUT_MS 限制,因此掛起的隧道無法耗盡檔案描述符。
  • 擷取(handleDecryptedRequest):推送一個 InterceptedRequest,其中 source: "tproxy"、狀態起始為 "in-flight";標頭會先經過 sanitizeHeaders(),本文則會先經過 maskSecret(),之後才進入緩衝區。 接著會以回應、大小與延遲資訊更新該項目。
  • 重新加密轉送(createForward / realForward):重新加密後轉送至原始 目的地。rejectUnauthorized 預設為 true(預設安全)——上游憑證會依據 用戶端所要求的 SNI/Host 進行驗證,因此原始用戶端會拒絕的情況,代理伺服器也會拒絕。

由於規則會標記新的本機對外連線,因此代理伺服器本身經重新加密的轉送通常也會被再次攔截, 從而形成無限迴圈。轉送路徑使用繞過 socket 標記(SO_MARK)來防止此問題:

  • realForward 透過 connectMarked(ip, port, DEFAULT_BYPASS_MARK) 開啟其上游 socket ——DEFAULT_BYPASS_MARK = 0x539——此函式會在 connect() 之前設定 SO_MARK,因此轉送連線的 SYN 會帶有繞過標記。
  • mangle OUTPUT 規則會排除已帶有繞過標記的連線 (-m mark ! --mark &lt;bypassMark&gt;),因此代理伺服器的轉送連線不會被重新標記, 也不會再次進入 TPROXY。

實作注意事項:帶有繞過標記的 socket 必須安裝於 agent 的 createConnection 上(當存在 agent 時,https.request({ createConnection }) 會被無提示地忽略),否則轉送會開啟未標記的 socket,導致迴圈再次出現。 這是經端對端驗證的防迴圈修正。


控制措施 詳細資訊
僅限迴路位址的 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 擷取模式使用,預設為關閉,且當您停止此模式時, 信任存放區項目也會被移除。


當程式崩潰時,絕不能遺留 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 不同(防止迴圈)。

Terminal window
ip rule add fwmark &lt;mark&gt; lookup &lt;routeTable&gt;
ip route add local 0.0.0.0/0 dev lo table &lt;routeTable&gt;
iptables -t mangle -A OUTPUT -p tcp --dport &lt;dport&gt; -m mark ! --mark &lt;bypassMark&gt; -j MARK --set-mark &lt;mark&gt;
iptables -t mangle -A PREROUTING -p tcp --dport &lt;dport&gt; -m mark --mark &lt;mark&gt; -j TPROXY --on-port &lt;onPort&gt; --tproxy-mark &lt;mark&gt;

還原會以相反順序刪除它們: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 Proxy 為其自身上游連線設定的略過通訊端標記(SO_MARK);在 OUTPUT 中予以排除(防止迴圈)
sudoPassword string — 僅適用於非 root 桌面環境:授權安裝信任儲存區;以 root 身分執行時會忽略

TPROXY 沒有任何環境變數——所有組態皆透過 POST 主體或上述預設值提供。


  1. 開啟 流量檢查器(/dashboard/tools/traffic-inspector)。
  2. 在擷取模式工具列中,找到 「TPROXY 解密」 ⚠ 按鈕 (src/app/(dashboard)/dashboard/tools/traffic-inspector/components/CaptureModesToolbar.tsx)。
    • 如果該按鈕為停用狀態,且工具提示顯示「TPROXY 解密需要 Linux + root + 原生附加元件」,表示此主機無法使用原生附加元件(非 Linux、 沒有工具鏈,或尚未建置附加元件)。請參閱 §2 和 §3。
  3. 按一下該按鈕。它會透過 startTproxyCaptureMode() (src/lib/inspector/tproxyCaptureApi.ts)呼叫 POST /api/tools/agent-bridge/tproxy,其會: 建置動態 CA、開啟透明監聽器、套用防火牆規則, 並將 CA 安裝至作業系統的信任存放區。
  4. 執行時,切換按鈕會變為琥珀色,並顯示即時攔截計數 (· &lt;interceptCount&gt;)。遭攔截的請求會出現在請求清單中,並帶有 source: "tproxy"。
  5. 再按一下即可停止——透過 stopTproxyCaptureMode() 呼叫 DELETE /api/tools/agent-bridge/tproxy,這會關閉監聽器、解除安裝 CA, 並還原防火牆規則。

擷取模式狀態(執行中/可用性/攔截計數/監聽器連接埠)來自 GET /api/tools/agent-bridge/tproxy(位於 src/mitm/tproxy/captureManager.ts 的 getCaptureStatus())。一次只能執行一個 TPROXY 工作階段—— 嘗試啟動第二個工作階段時,會遭到拒絕並顯示「TPROXY capture mode is already running」。


原生附加元件無法載入。請確認:您使用的是 Linux、已建置附加元件 (npm run build:native:tproxy),且程序能夠載入 transparent.node。 isTransparentSocketAvailable() 會控制切換按鈕;缺少附加元件時,GET /api/tools/agent-bridge/tproxy 會傳回 available: false。

  • 確認遭攔截的程序確實連線至已設定的 dport (預設為 443)。
  • 確認該程序信任動態 CA。CA 會以 omniroute-tproxy-ca.crt 的名稱安裝;使用自身信任存放區的應用程式(Firefox/Chrome NSS) 可能也需要將憑證加入該存放區。
  • 執行 AgentBridge 的 Diagnose 自我測試(請參閱 AGENTBRIDGE.md),以進行憑證信任/伺服器 健康狀態檢查。

revertTproxy() 與套用操作完全相反,且具有冪等性。停止該模式會還原規則; 如果 OmniRoute 在工作階段進行期間遭到終止,請使用 AgentBridge 的 Repair 動作(POST /api/tools/agent-bridge/repair)復原遺留的系統 狀態(DNS 欺騙、根 CA、系統代理伺服器)。TPROXY mangle 規則和路由也會在 重新開機時自動清除。

無限迴圈/代理伺服器攔截自身的轉送

Section titled “無限迴圈/代理伺服器攔截自身的轉送”

這是防迴圈情況。請確認 bypassMark 與 mark 不同(驗證會 強制執行此條件),且轉送使用 connectMarked(realForward 中確實如此)。 請參閱 §5 防迴圈。


檔案 職責
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 用戶端擷取輔助函式(fetchTproxyStatus / startTproxyCaptureMode / stopTproxyCaptureMode)

OmniRoute 原始碼 (a58000c7685f)

HagiCode

HagiCode 是智慧代理程式開發工作台,結合結構化工作流程、多代理程式執行與 Hero Dungeon 介面,將想法化為交付成果。

以更聰明、更快速且更有趣的智慧代理程式工作流程,打造實用的軟體。

HagiCode 淺色主題介面畫面
  • Smart結構化流程將意圖轉化為從構想到交付的可執行步驟。
  • Efficient多代理程式工作流程讓研究、實作與審查並行進行。
  • FunHero Dungeon 讓長時間的程式協作更直覺、更有參與感。
造訪 HagiCode