檢查期間 2026-09-07~17|分五次檢查完成|首腦已驗收
分五次檢查了 99 個檔案(套件本體 72 個、主專案接線 16 個、借用的檢測套件 11 個),五次一共通過 24 條發現。去除重複之後:
| 幾條 | 說明 | |
|---|---|---|
| 併進既有的修正卡 | 22 | 反覆指向同一件事:客戶機房的代理程式跟雲端之間的通訊,雲端不檢查對方是誰 |
| 全新的問題 | 2 | 都是檢查範圍以外順手撈到的密碼外洩,跟遠端代理程式本身無關 |
這個套件真正的資安問題只有一個,就是 CM-1595。 它是六個資安檢查案裡唯一一件被評為「緊急」的。五次檢查有四次各自獨立地撞到它,每一次都從不同角度多補一塊拼圖——現在它的樣貌已經完整到可以直接動工修了(現況:已修,CM-2052)。
修正現況:當時(09-17)決策者決定修正卡先不派、等全部掃完分類後統一開工,避免同型問題散在不同卡片各修一半;09-21 起改照內化版問題總表(docs/security-report/SUMMARY.md)統一派工。結果:CM-1595(控制面不驗身分)已修(CM-2052,1.21.0 出貨);CM-1596 健康檢查 SSRF 於 1.21.1 補修(CM-2370);CM-1597 註冊 Token 裁定不修(落地版僅內網可達,SaaS 前再評估到期或一機一碼);CM-1598 版控 .env 已撤出(jedi 套件庫 f6459efd)。早期四張卡已作廢。
| 次別 | 檢查什麼(白話) | 檔數 | 通過幾條 | 範圍內新問題 |
|---|---|---|---|---|
| R1 | 代理程式的身分與註冊——它怎麼證明「我是誰」 | 42 | 7 | 6(開出 CM-1595~1598 四張修正卡) |
| R1b | 密碼學核心——簽憑證、簽通行證、連線加密 | 10 | 2 | 0(另撈到範圍外的 GitLab 權杖 → 總表第 83 項) |
| R2a | 任務下發與狀態機+對外介面與守門殼 | 30 | 5 | 0 |
| R2b | 代理程式來拿檔案、雲端連回代理程式 | 13 | 4 | 0(另撈到範圍外的 GitLab/GitHub 權杖) |
| R3 | 主專案這端怎麼把套件接上來 | 16 | 6 | 0(另撈到公開文件站洩密 → 總表第 84 項) |
怎麼讀這張表:R1 那次是第一次掃,所以問題都是新的;後面四次「範圍內新問題」全是 0,意思不是白跑——意思是那四次把同一條攻擊路徑翻來覆去驗證了四遍,都指向同一個結論。這反而是好消息:問題不是散的,是集中的。
| 歸到哪裡 | 幾條 | 哪幾次撞到 |
|---|---|---|
| CM-1595(緊急:控制面端點不檢查身分;✅ 已修 CM-2052) | 13 | R1 四條、R2a 四條、R2b 三條、R3 兩條 |
| CM-1596(測試連線可當內網跳板;✅ 已修 CM-2370) | 2 | R1、R2a |
| CM-1597(註冊通行碼永不過期;🚫 裁定不修) | 2 | R1、R1b |
CM-1598(套件的 .env 進了版控;✅ 已修 jedi f6459efd) |
1 | R1 |
| CM-1629(資料庫密碼散在 249 個檔;✅ 已清 CM-2048) | 1 | R3 的 F1 |
| 總表第 3 項(原廠管理員密碼每套安裝都一樣) | 1 | R3 的 F6 |
| GitLab/GitHub 權杖(併 CM-1573 那條線) | 2 | R1b、R2b |
| 🆕 總表第 83 項(jedi-issue 歷史檔裡的第二把 GitLab 權杖) | 1 | R1b,越界 |
| 🆕 總表第 84 項(公開文件站洩露資料庫超管密碼) | 1 | R3,越界 |
| 合計 | 24 |
整個 arc 的淨新增只有兩條,而且兩條都是「越界」撈到的密碼外洩——工具因為設了攻擊面重點,會順手跑一輪密碼掃描,這兩條就是那輪撈到的,跟遠端代理程式本身沒有關係。
第 84 項值得單獨看一眼。 它的密碼跟 CM-1629 是同一把,本來不該算新的。但它新在位置:那份寫著密碼的需求文件在 docs/features/ 底下,而那個目錄每次推上主線就會自動部署到公開文件網站。首腦 2026-09-17 親自打開那個網址看過——頁面在線上、密碼字串在上面、四個帳號名可見。CM-1629 原本的前提是「拿得到程式碼倉庫的人才看得到」,現在變成「任何人,不用帳號、不用內網,開瀏覽器就看得到」。這是性質改變,所以另立一項。
五次檢查把這件事拼完整了。它不是一個洞,是一整片沒上鎖的牆。
我們發給客戶、裝在他們機房的代理程式,跟雲端之間的五條通訊管道,全部不檢查對方是誰。 程式碼註解裡寫著「不用檢查,因為外層的 nginx 會驗客戶端憑證」——但出貨的 nginx 設定檔裡,那一行根本不存在(三個設定檔全部找不到)。
| 端點 | 做什麼的 | 打進去能拿到什麼 |
|---|---|---|
| 註冊(register) | 代理程式第一次安裝時報到 | 取得一張身分憑證 |
| 心跳(heartbeat) | 每隔一段時間報到、領工作 | 🔴 客戶自己機器的明文帳號密碼(SonarQube 權杖、SSH/WinRM 帳密) |
| 收到工作(ack) | 回報「我收到這份工作了」 | 搶走別台的工作 |
| 回報結果(result) | 回報「我做完了,結果在這」 | 🔴 偽造或抹掉稽核證據 |
| 下載檔案(files) | 來拿要掃描的源碼 | 🔴 客戶上傳來掃描的源碼壓縮包 |
前四條是套件那側的,第五條是主專案自己手動掛上去的裸端點。
這是這件事最麻煩的部分。伺服器決定「這次動作算在哪一家客戶頭上」的做法是:拿請求自己送上來的那個編號,在一個刻意繞過客戶隔離的查詢裡去找,找到之後把整個執行環境切換成那一家。
可以用來選的鍵有三種:
三種都要堵,只堵一種等於沒堵。
五次檢查陸續補上的細節,每一個都讓這件事更嚴重:
/agents/ 前綴。授權過期的環境不會因此變安全。四句話:
以下是掃描當時(2026-09-17)規劃的修法;原四張卡已作廢,實際處理現況見上方「修正現況」。
現況:已修(CM-2052,1.21.0 出貨;原卡作廢)。
分四步,順序不能顛倒。
第一步:先決定用哪一種身分證明 ⚠️ 這步要決策者拍板
有兩條路,選一條做主、另一條最多當輔助:
| 方案 | 怎麼運作 | 優點 | 代價 |
|---|---|---|---|
| 短效通行證(建議) | 平台本來就會簽這種通行證,讓代理程式每次呼叫都帶上 | 程式本身就有,不必動基礎設施;應用層自己驗得了,客戶怎麼架都不受影響 | 要處理通行證的發放與更新 |
| 客戶端憑證 | 由 nginx 在最外層驗,驗過的身分往後傳 | 連線層就擋掉,應用層負擔輕 | 要改客戶機房的 nginx 設定,落地版客戶各自裝機,改動成本高;且它只證明「你是一台登記過的機器」 |
建議走短效通行證當主軸——理由是不用碰客戶端的基礎設施。客戶端憑證可以之後再補,但不能拿它當唯一手段。
第二步:五個端點一起掛上驗證
第三步:歸屬比對要做進 domain 層
不是在路由上加一個裝飾器就好。要讓「這張工單是不是你的」變成狀態轉換函式的必填條件——任何呼叫端都不可能繞過。具體是兩個斷言:工單記的代理程式要等於呼叫者、工單的客戶要等於呼叫者的客戶。
還有一件事必做:那個繞過客戶隔離的查詢要收窄,只能用來「查呼叫者自己是誰」,絕對不能用來查一個由呼叫者指名的東西。這是整條攻擊路徑成立的關鍵。
第四步:把機器編號這條路作廢
心跳端點不得再接受「機器編號」當作身分的替代依據。這條一作廢,管理頁回傳裝置指紋就不再是問題(放大器③自動解除)。
做完怎麼驗:
現況:已修(CM-2370,1.21.1;原卡作廢)。
改動小,但要決定「限制到多嚴」。
管理頁上的「測試連線」按鈕,使用者填什麼網址就去打什麼,沒有任何限制。攻擊者可以拿我們的伺服器當跳板探測內網。
兩層做法,建議都做:
更嚴的選項(要決策者裁):乾脆只准打「已經登記在該客戶名下的代理程式位址」。好處是徹底堵死,代價是管理員沒辦法用這個功能測試一台還沒登記的機器——這是產品體驗的取捨。
做完怎麼驗:手動填幾個內網位址(資料庫、Redis、授權服務)進去測,應該全部被擋;填一台合法的代理程式位址,應該正常回報通或不通。
現況:🚫 裁定不修(落地版僅內網可達,SaaS 前再評估;原卡作廢)。
⚠️ 這張卡依賴 CM-1595,要等它先做完。
代理程式第一次安裝時要拿一組通行碼來註冊。現在這組碼整家客戶共用同一組、四層都沒有到期機制、用幾次都可以。撿到的人可以隨時註冊一台自己的代理程式進去。
三件事:
做完怎麼驗:手動測試——拿一組用過的碼再註冊一次,要被擋;拿一組過期的碼註冊,要被擋;正常的首次安裝流程要還能走完。
.env 檔進了版控現況:已修(jedi 套件庫
f6459efd;原卡作廢)。
最單純的一張。 jedi-common 的 .env 被提交進版控了,裡面有設定值。
做法:從版控裡移除(保留本機檔案)、改放一份不含真實值的範本、.gitignore 補上。
⚠️ 如果那個檔裡有真的密碼,要換——移除檔案不會讓版控歷史裡的值消失。動手前先確認裡面到底有什麼。
做完怎麼驗:確認版控裡查不到那個檔,且開發環境照範本設定後還能正常啟動。
要分兩件事回答。
| 次別 | 檔數 | 候選→通過 | 投票數 | 漏投 | 中斷 | 驗證章 |
|---|---|---|---|---|---|---|
| R1 | 42 | 7→7 | 0 | 🔴 21 | 🔴 全滅 | unverified |
| R1b | 10 | 5→2 | 15 | 0 | 0 | 渲染退件 |
| R2a | 30 | 7→5 | 15 | 0 | 0 | ✅ verified |
| R2b | 13 | — →4 | 12 | 0 | 0 | ✅ verified(工作區 dirty) |
| R3 | 16 | 8→6 | 21 | 0 | 0 | ✅ verified(工作區 dirty) |
怎麼讀這張表:工具會派三個獨立檢查員對每條發現各投一票。「投票數 21」就是「7 條候選 × 3 個檢查員,21 票全投出」。漏投 0、中斷 0,代表每一條都被完整審過。
R1 那次投票全滅(額度用盡),所以它的七條是靠人工逐條開檔核對出來的。但後面四次把同一條路徑又驗了四遍、每次都 3:0 通過——R1 的結論等於被重新背書了四次,這比它自己一次就投票成功還可靠。
而且首腦沒有只採信工具:CM-1595 的核心(「切換成哪一家客戶是由攻擊者填的值決定的」)是首腦 2026-09-16 自己開檔比對出來的,當時甚至推翻了自己前一天把它降級的決定;R3 那條公開網站洩密,是首腦親自打開網址看到頁面才登記的,不是讀程式碼推的。
好的一面:
不能宣稱完整的一面:
coverage.research 是 null),無法證明每個檔都被讀到結論結論:這些問題都是真的;「這條控制鏈只有這些問題」這句話不成立。
R1 那次一口氣掃 42 個檔,結果105 個檢查員全部因額度用盡掛掉、21 張票一張都沒投出來——工具的正式產出是空的,七條發現全靠人工補核才留下來。當時決策者因此把整個 arc 暫停,理由很直接:「這樣的產出無法用來證明這個套件檢查過了」。
續行時改了尺,拆成四小棒(10/30/13/16 檔),四棒的面板全部完整跑完。
差別不只在檔數——R2a 那棒也有 30 個檔,一樣跑完了。真正的差別是有沒有跟別的工作搶額度。所以規矩不是「檔數要少」,是「一次只跑一棒,不併行」。
如果只看 R1,CM-1595 是「四個端點沒認證」。五棒跑完之後它變成「五個端點、三種可選的身分鍵、四個放大器」——而這些不是靠更用力掃同一個地方掃出來的,是換角度看出來的:
「範圍內新問題 0」的四棒,價值全在這裡。 換角度比換力氣有用。
| 報告 | 檢查什麼 | 範圍內新問題 |
|---|---|---|
| R1 代理程式的身分與註冊 | 42 檔 | 6(開出四張修正卡,含唯一的「緊急」) |
| R1b 密碼學核心 | 10 檔 | 0(越界撈到總表第 83 項) |
| R2a 任務下發與狀態機 | 30 檔 | 0(補完 CM-1595 的四點細節) |
| R2b 檔案取用與 agent 連線 | 13 檔 | 0(找到第五個裸奔端點) |
| R3 主專案宿主接線 | 16 檔 | 0(越界撈到總表第 84 項,公開站洩密) |
🔴 五棒之中,三棒的 runner 四件全未交(沒寫報告、沒 commit、沒回寫 Notion、沒回報):R1b、R2a、R3。這三份報告都是首腦到掃描目錄自己撈產物、自己逐條開檔核對、自己補寫的。R2b 那棒交付齊全。
三次分別是全計畫的第九、第十、第十一次 runner 交付掛零。