這一篇原本的題目是「兩種對話,兩種距離 —— WebSocket protocol 與 bridge protocol」。
我在 docs/gateway/ 底下看到兩個檔名:protocol.md 和 bridge-protocol.md,直覺就是「同一個系統有兩套協定,一定有原因,這題可以寫」。
然後我打開 bridge-protocol.md,第一段是一個警告框:
📄 文件:
docs/gateway/bridge-protocol.md
The TCP bridge has been **removed**. Current OpenClaw builds do not ship the
bridge listener, and `bridge.*` config keys are no longer in the schema.
This page is historical reference only.
題目當場作廢。
我去原始碼確認了一下,確實乾淨:
$ ls src/gateway/server-bridge.ts
ls: src/gateway/server-bridge.ts: No such file or directory
$ ls src/gateway/ | grep -i bridge
(沒有輸出)
那份文件自己都說了,allowlist 的執行邏輯「lived in src/gateway/server-bridge.ts (removed)」。
但我決定還是寫這一篇,而且比原本的題目更值得寫。
因為一個被刪掉的東西,會留下一個很有價值的問題:
它當初為了解決什麼而存在?那些問題現在跑到哪裡去了?
這比「兩套協定有什麼不同」有意思多了。一個系統刪掉一整條傳輸層,代表它想通了某件事。
先看 bridge 當初存在的理由。文件自己列了四條:
📄 文件:
docs/gateway/bridge-protocol.md
## Why it existed
- **Security boundary**: exposed a small allowlist instead of the full gateway API surface.
- **Pairing + node identity**: node admission was owned by the gateway and tied to a per-node token.
- **Discovery UX**: nodes could discover gateways via Bonjour on LAN, or connect directly over a tailnet.
- **Loopback WS**: the full WS control plane stayed local unless tunneled via SSH.
這四條講的其實是同一件焦慮:
我不敢讓外面的裝置直接碰那個完整的 WS API。
上一篇講過,gateway 光 server-methods/ 就有 174 個檔案,等於一百多種操作。那是給 CLI、給 macOS app 用的完整控制面。
現在想像一支放在客廳的 Android 手機,當成 node 接進來。你會願意讓它拿到那一百多種操作嗎?
不會。所以早期的答案是:另外開一個門。
18790,WS 走 18789)兩個 port、兩套協定、兩種信任等級。用「距離」換「安全」。
這是我覺得最有意思的部分。四個責任沒有消失,它們被搬到四個不同的地方。
以前靠「你走哪個 port」決定你能做什麼。現在靠你在 handshake 時宣告自己是誰。
📄 文件:
docs/gateway/protocol.md
{
"type": "req",
"method": "connect",
"params": {
"minProtocol": 4,
"maxProtocol": 4,
"client": { "id": "cli", "version": "1.2.3", "platform": "macos", "mode": "operator" },
"role": "operator",
"scopes": ["operator.read", "operator.write"],
"caps": [],
"commands": [],
"permissions": {}
}
}
role 和 scopes 取代了「哪個 port」。
而且注意 caps 和 commands 這兩個欄位——一個 node 連進來的時候,是自己申報「我有鏡頭、我能錄螢幕」。它不是拿到一份通用能力清單,而是宣告自己能提供什麼。
方向整個反過來了:以前是 gateway 決定「這個門後面能做什麼」,現在是連線者宣告「我是誰、我要什麼、我能提供什麼」,由 gateway 審。
以前是一套獨立的四步握手:hello → NOT_PAIRED → pair-request → pair-ok。
現在配對資訊直接長在 connect 裡:
"auth": { "token": "…" },
"device": {
"id": "device_fingerprint",
"publicKey": "…",
"signature": "…",
"signedAt": 1737264000000
}
從 token 變成公鑰 + 簽章 + 簽署時間。
而且 gateway 會先發一個挑戰:
{ "type": "event", "event": "connect.challenge", "payload": { "nonce": "…", "ts": … } }
先給 nonce,你簽回來。這是標準的防重放做法,而以前那個 per-node token 是做不到的——token 被側錄就等於身分被拿走。
安全等級其實是提升的。少一套協定,不代表少一層防護。
這個轉向我覺得最漂亮。
📄 文件:
docs/gateway/bonjour.md
the bundled `bonjour` plugin owns LAN advertising, auto-starting on macOS hosts
and opt-in on Linux, Windows, and containerized gateway deployments.
Bonjour 還在,但它不再是傳輸層的一部分,而是一個內建 plugin。
這件事完全呼應我在 18th 鐵人賽講 plugin 角色那篇的結論:plugin 是會改變系統運作方式的模組,不是內容包。
「在區網上廣播自己」是一個可選的、依平台而異的行為——macOS 自動開、Linux 和容器環境要自己選。這種東西本來就不該焊死在核心的傳輸層裡。
而且文件講得很誠實:
Discovery is best-effort and does **not** replace SSH or Tailnet-based connectivity.
它只是方便,不是連線方案。這也是原本的 bridge 混淆掉的事——把「找得到」和「連得上」綁在同一層。
原本 WS 預設只聽 loopback(127.0.0.1:18789),要遠端就得自己開 SSH 隧道。
現在這件事交給 Tailscale、SSH、trusted proxy 這些本來就該做這件事的東西。
docs/gateway/ 底下有 tailscale.md、trusted-proxy-auth.md、remote.md、multi-tenant-hosting.md——整整一組文件在講「怎麼安全地讓它被遠端連到」。
這比「自己做一個比較安全的 TCP 協定」務實太多。
既然只剩一套,它的規矩就要更嚴。幾個值得記的細節:
第一個 frame 一定要是 connect。 不能先做別的事。
握手前的 frame 有大小上限。 原始碼裡就是一個常數:
📄 原始碼:
src/gateway/server-constants.ts:5
export const MAX_PREAUTH_PAYLOAD_BYTES = 64 * 1024;
64 KiB。在你還沒證明自己是誰之前,只能送這麼多——這是一道很便宜但很有效的防線,擋掉「未認證的連線灌爆記憶體」這種攻擊。
握手之後改看 hello-ok.policy.maxPayload,由伺服器告訴你新的額度。認證前後兩套限制,這個分界很乾淨。
frame 只有三種形狀:
Request: {type:"req", id, method, params}
Response: {type:"res", id, ok, payload|error}
Event: {type:"event", event, payload, seq?, stateVersion?}
會產生副作用的方法需要冪等鍵。 因為 WS 會斷線重連,重送是常態——這跟我在講重試那篇談的是同一個世界觀:失敗和重複不是例外,是預設會發生的事。
還有一個我很喜歡的細節。超大 frame 和緩慢的輸出緩衝會發出 payload.large 診斷事件,而文件特別說明那個事件裡有什麼、沒有什麼:
These events carry `surface`, byte sizes, limits, and a safe reason code, never
message bodies, attachment contents, raw frame bytes, tokens, cookies, or secrets.
診斷事件本身也是一個洩漏面。 會特地寫下「這裡面絕對不會有什麼」的專案不多。
最後一條其實是這個決定最貴的部分。但從結果看,他們換到的是一個只有一個地方需要做對的架構。
兩套協定意味著兩套認證、兩套 scope 檢查、兩套速率限制——而且它們一定會不同步。真正的風險從來不是「協定不夠多」,是兩條路的安全等級悄悄長得不一樣。
18789)和 TCP JSONL bridge(18790),bridge 已完全移除role / scopes;配對搬進同一個 connect frame,並升級成 nonce + 簽章connect,認證前 64 KiB 上限,三種 frame 形狀,副作用方法要冪等鍵既然一台主機只能有一個 gateway,而且它掛了所有東西一起掛,那就有兩個很現實的問題:
它怎麼確保自己是唯一的?重開之後,跑到一半的工作怎麼辦?
下一篇講那三層鎖,還有一份「什麼東西活得過重啟」的清單。