我看完了這個 commit 的原生配對橋(`shell/darwin/Pairing.swift`、`PairingDialogs.swift`、`CloudPairing.swift`、`Browser.swift`)以及它呼叫的 daemon 端程式。結論是有一個重要問題要處理,另有幾個中低風險。WebView 的 origin 檢查本身寫得不錯。 先說明限制:我沒有 clone repo,是透過網頁抓取工具逐段讀程式。下面的結論都有對照原始碼片段,但 control plane 伺服器端、agent 啟動參數、`requireLocal` 的實作和 daemon 監聽的位址,我都沒看到。 ## 1. 【高】CloudPairLocal 可以把帳號主金鑰交給任何 offer 流程是:Cloud 頁面呼叫 `CloudPairLocal`,原生端跳出確認框,使用者按同意後 POST 到 `/v1/cloud/pairing/offer`,進入 `internal/transport/cloud/pairing.go` 的 `Complete()`,再由 `deliver()` 把資料封給 offer 裡的金鑰。 - `Complete()` 送出前只檢查三件事:offer 能 decode、還沒過期、`offer.AccountID == p.AccountID`。 - offer 沒有簽章。`internal/domain/cloud/pairing.go` 只確認指紋和 offer 內的公鑰一致,註解自己也寫了 "The fingerprint is not authority"。 - 不會檢查 offer 是否來自本機發出、還在等待中的邀請,也不會用 `OpenEncryptedOffer` 解開邀請密鑰。這兩個檢查只存在於另一條 `answer()` 路徑。 - `deliver()` 封裝的 `PairingHandover` 含有 `MasterSecret`(帳號主金鑰或 content key)。 - 確認框只顯示 daemon 回報的機器名稱,沒有任何可以讓兩台裝置互相比對的碼或指紋。相比之下,`IdentityPairing` 路徑有六位數比對碼。 **影響:** 如果 `app.clawdline.com` 被入侵、遭供應鏈污染,或營運方本身惡意,只要讓使用者在對話框按一次同意,就能拿到可以解密所有機器內容的金鑰。README 說「Cloud 只轉送密文」,這條路徑讓這個 E2E 承諾變成取決於那一次點擊。 **建議:** 貼上路徑也要求本機有對應的待處理邀請,或驗證邀請密鑰。確認框加上 offer 指紋或比對碼,並要求使用者在另一台裝置上核對。 ## 2. 【中】CloudPairAgent 一鍵啟動 full 權限的 AI 助手 - 核准後,daemon(`cloud_agent.go` 呼叫 `orchestrator/detached_pairing.go`)會啟動一個 `"permission_mode": "full"` 的助手。指令範本說明沒有人在看,它會執行 `clawdline cloud pair -offer '%[3]s'`。 - **Shell injection 目前擋得住。** `DecodeCanonicalBase64URL` 對每個字元做 `[A-Za-z0-9_-]` 白名單檢查,還會重新編碼比對,所以單引號進不去。但這個保護完全靠上游,`DispatchPairingAgent` 自己只檢查非空。建議改用環境變數或檔案傳遞 offer,或在 dispatch 時再驗證一次。 - **Prompt injection 的空間有限,但仍然存在。** `machine_name` 由頁面提供,經過清理,最多 64 字元並做 JSON 引號處理,但還是會被放進 full 權限 agent 讀的 prompt。 - 確認框顯示 offer 的前 12 個字元,使用者其實無法用它判斷任何事。建議改顯示 daemon 驗證過的資訊。 ## 3. 【中低】本機配對對話框 - **預設按鈕給最高權限。** `PairingDialogs.swift` 的 `chooseAlert` 第一個按鈕是「讀取並操作」,按 Return 就會直接授予。建議預設改成「唯讀」或「忽略」。 - **裝置名稱沒有清理。** `notice.name` 在 Swift 端沒有長度限制,也沒有過濾控制字元或 bidi 字元,會直接進入對話框標題和 `shellLog`。CLI 的 `clawdline pair`(`cmd/clawdline/auth.go`)也會原樣印到終端機,可能被 ANSI escape 注入或誤導。 - 每次彈窗都會執行 `NSApp.activate(ignoringOtherApps: true)`。我沒看到有限速,可能被用來反覆搶焦點。 ## 4. 【低】其他 - `CLAWDLINE_NEXT_CLOUD` 環境變數可以把受信任的 Cloud origin 改成任意 http(s) 位址,release build 也沒有擋。前提是攻擊者已經能設定這個 App 的環境變數。 - `CloudPairLocal` 的 `status` 不需要確認就會回傳 `machine_id`。Cloud 本來就知道這個值,影響可以忽略。 - `PairingPermissionClient` 只把 `/` 從允許字元中移除,`..` 不會被編碼。`pairing_id` 來自本機 daemon,所以風險很低。 ## 做得好的部分 - 兩個 handler 只註冊在 Cloud WebView 上。每次收到訊息都會檢查 `isMainFrame`、`securityOrigin` 的 scheme、host、port,以及 `webView.url`,subframe 和其他 origin 都會被拒絕。 - 確認框關閉後會重新檢查 origin,防止期間頁面被換掉。 - local token 用 HttpOnly、`SameSite=Strict` 的 cookie 注入,不出現在 URL 或頁面 script 中,log 裡也沒有 token 或 offer。 - 設定寫入只接受本機 token,已配對的瀏覽器會收到 403。 **建議優先處理第 1 點。** 可以這樣驗證:用自己帳號的 AccountID,在沒有發出邀請的情況下產生一個 offer,看 `/v1/cloud/pairing/offer` 會不會成功交出主金鑰。也要確認 control plane 的 `CompletePairing` 有沒有檢查 `PairingID` 屬於這台機器,不過如果 Cloud 本身是威脅來源,這個伺服器端檢查也沒有保護作用。