Guidant AI 資安檢視總報告 · 連線清單

C20 代理程式 → 主系統前門(控制面,HTTPS :443)

裝在客戶機房的代理程式主動撥出、連回我們雲端的那條線——報到、心跳領工作、確認收到、回報結果、下載檔案全走它。這條線的另一端在我們控制之外,而且不帶任何使用者登入:對方是機器不是人。這一頁回答:這條線怎麼連、哪些功能走它、它帶來什麼威脅、駭客怎麼打、我們要怎麼防、目前做到哪。

裝了 agent 才有 不帶使用者登入 威脅 4 種 掃描命中 4 條

這一頁怎麼來的:DFD Level 0 把產品運作時的連線編成 C01~C25;STRIDE 六頁每一條問題都標了發生在哪幾條線。這裡以連線為單位,把這條線的特性、掃描在這條線上抓到的攻擊手法、以及由此該有的防線整理成一頁。個別問題修了沒不在這頁講,請點條目連回 STRIDE 頁。

§1

一、連線圖

%%{init: {'theme':'base','themeVariables':{'primaryColor':'#E2F0F1','primaryTextColor':'#14201F','primaryBorderColor':'#0E7C86','secondaryColor':'#EEF2F3','secondaryTextColor':'#14201F','tertiaryColor':'#FBFCFC','tertiaryTextColor':'#14201F','lineColor':'#4A5A5C','textColor':'#14201F','mainBkg':'#E2F0F1','nodeBorder':'#0E7C86','nodeTextColor':'#14201F','edgeLabelBackground':'#FBFCFC','titleColor':'#14201F','clusterBkg':'#FBFCFC','clusterBorder':'#E4EAEB'}}}%%
flowchart LR
    subgraph AGENTBOX["🏢 客戶機房(我們控制不到)"]
        AG["evidence-agent<br/>持有:通行證、憑證、<br/>註冊 token"]
    end
    ATK(["🕵 攻擊者<br/>任何連得到 :443 的位置"])
    subgraph MAIN["🏠 主系統 stack"]
        direction TB
        FE["guidant-fe<br/>nginx 前門 :443<br/>TLS 終止・不驗身分"]
        subgraph API["guidant-api · 控制面端點"]
            direction TB
            REG["/agents/register<br/>報到:驗註冊 token"]
            CP["/agents/heartbeat 領工作<br/>/agents/tasks/…/ack 確認<br/>/agents/tasks/…/result 回報<br/>/agents/files/… 下載檔案<br/>驗控制面通行證+查撤銷"]
        end
        DB[("guidant-db<br/>remote_agents<br/>agent_tasks")]
        FE --> REG
        FE --> CP
        CP --> DB
        REG --> DB
    end
    AG == "C20 · HTTPS :443<br/>Bearer 通行證" ==> FE
    ATK -. "C20 · 同一條線<br/>不需帳號" .-> FE
    classDef ext fill:#FBFCFC,stroke:#9AA8AA,stroke-dasharray:3 2
    classDef open fill:#FDECEC,stroke:#C0392B
    class ATK ext
    class REG open
C20 — 代理程式到前門。agent 從客戶機房撥出,經前門 :443 進 api 的「控制面」五支端點。前門不驗身分,api 入口也不驗使用者 JWT——這五支靠「控制面通行證」認機器。紅色是只憑一組註冊 token 就能打的入口。
§2

二、這條線怎麼連

項目 現況
誰 → 誰 客戶機房的 evidence-agent 容器 → 我們的 guidant-fe 前門(nginx :443)→ 轉給 guidant-api。方向是客戶撥出,所以客戶防火牆只要放行出站 443,不必開任何入站埠
協定/埠 HTTPS :443,路徑都在 /api/1.0/agents/… 下。agent 端 CLOUD_ENDPOINT 指定雲端位址
傳什麼 五件事:① 報到(送憑證申請、拿回簽好的憑證與通行證);② 心跳領工作(agent 定期來問「有沒有派給我的單」,回應裡帶任務參數——含當場解密的客戶主機帳密與工具權杖);③ 確認收到;④ 回報結果(掃描成功/失敗、報告檔編號);⑤ 下載檔案(規則包、使用者上傳的源碼包)
怎麼驗身分 不是使用者登入。報到那一支靠註冊 token(管理員在畫面產生、每家客戶同時只有一組有效,DB 只存雜湊);其餘四支靠控制面通行證——報到時由後端用簽章私鑰簽發的長效 JWT(效期同憑證,預設 10 年),每個請求帶 Authorization: Bearer,後端驗簽後只認證裡的機器編號(請求裡自報的一律不採信),並每個請求查一次資料庫確認該台沒被撤銷或停用——撤銷靠查表即時生效,不靠票過期
雙向憑證(mTLS)的實況 agent 端每次呼叫都會出示自己的客戶端憑證(cert=),但前門 nginx 沒有設 ssl_verify_client,從頭到尾不看它。所以這條線上實際生效的身分驗證只有通行證那一層;客戶端憑證在這條線是白帶的(它真正發揮作用的地方是 C21 資料面)。DFD 寫「mTLS 客戶端憑證+控制面簽章通行證」,前半句不符合現況,已回寫母卡
agent 怎麼信雲端 落地版客戶的雲端幾乎都是自簽憑證。安裝時 install.sh 抓雲端憑證、把指紋印給安裝人員確認、存成 cloud-ca.pem;之後 agent 只接受這一張(「首次信任、之後釘住」,同 SSH 的做法)。程式刻意不提供任何「關閉驗證」開關——雲端換憑證會當場連不上,要重跑安裝確認
何時存在 客戶有裝代理程式才有。心跳間隔由雲端報到時回傳(預設 300 秒)

依據:jedi_remote_agent/api/routing.py(路由表三級認證宣告)、jedi_remote_agent/api/guards.py 與 app/service/agent_control_auth_service.py(通行證守門)、jedi_remote_agent/common/agent_auth/jwt_util.py(通行證效期=cert_valid_days,預設 3650 天)、agent repo core/task_executor.py/core/cloud_trust.py/core/agent_auth.py、FE nginx.onprem.conf(無 ssl_verify_client)、主專案 api/remote_agent/routes/agent_file_route.py。

§3

三、哪些功能會走這條線

代理程式對雲端說的每句話都走這裡。對威脅分析來說分三群,因為三群的守門方式與後果都不同:

群 功能 守門靠什麼 對威脅的意義
① 報到 第一次安裝、換雲端重新註冊、憑證遺失重註冊 註冊 token(一家客戶一組)+憑證申請檔的名稱必須等於本機指紋+帶舊編號要附舊私鑰簽的持有證明 這是整條線身分的起點——token 流出去,攻擊者就能登記一台「假代理程式」進客戶名下,從此拿到真通行證
② 領工作與回報 心跳領單、確認收到、回報結果 通行證+每請求查撤銷+「這張單是不是派給這台」 領單的回應裡有客戶主機帳密的明文(當場解密);回報結果決定稽核證據長什麼樣——被冒充就是帳密外流+證據造假
③ 下載檔案 規則包、使用者上傳的源碼包 通行證+「這個檔是不是這台名下未完成任務的」 下載的東西會在客戶內網被當成程式執行(規則包就是掃描邏輯),檔案來源要是我們、內容要是當初上傳的那份
§4

四、這條線會帶來什麼威脅、駭客怎麼打

掃描在這條線上抓到 4 條問題(M01-1、M03-13、M03-14、M03-9),歸納起來是四種攻擊手法。前提都一樣:這條線上沒有人在登入,認的是機器——認錯機器,後面全部失守。

# 風險 威脅 駭客怎麼打 得手什麼 STRIDE 實例
T1 🔴 最嚴重 冒充代理程式——後端認不出對方是哪台機器 ① 站在任何連得到雲端 :443 的位置,不需要帳號;② 照代理程式的格式打「心跳」說「我是客戶 X 的機器 Y」;③ 後端若只讀請求裡自報的編號就相信,就把派給 Y 的工作連同解密後的主機帳密交出來;④ 再打「回報結果」替 Y 回「掃描全部正常」,或把真實結果蓋掉;⑤ 管理員按「撤銷」後若兩條通道不查撤銷,被撤銷的機器還能繼續送假資料 冒充任一家客戶的機器、拿走客戶主機明文帳密、偽造或抹除弱點掃描的稽核證據 S 冒充身分、I 資料外洩、T 竄改資料 M01-1 代理程式五條通訊管道全部不檢查對方是誰 🔴
T2 🟠 高 回報的內容被當成可信的——檔名、格式照單全收 ① 控制得了代理程式回應的人(被入侵的 agent、或 T1 冒充成功的人)回報一份掃描報告,檔名自己取(含路徑片段、或 .html 裡藏程式);② 後端照外面給的檔名存成證據,不去掉路徑、不查副檔名;③ 稽核人員在證據清單點「預覽」,報告裡的程式在他的瀏覽器、以他的身分跑起來 稽核人員的登入身分(通往 C01 T4) T 竄改資料、E 權限提升 M03-13 掃描報告「檔名叫什麼就存什麼」,走同一條預覽路徑 🟠
T3 🟡 中 從畫面那頭發動——不該動手的人派出帶帳密的掃描 ① 以唯讀角色登入畫面(這一步在 C02);② 按「開始掃描」,端點只確認他是專案成員就放行;③ 派工單寫進資料庫,代理程式下次心跳經這條線領走,帶著客戶主機帳密連進正式機器;④ 反覆取消再重派干擾檢測,或刪掉執行紀錄 以不該有的身分發動帶帳密的掃描、改掉掃描執行紀錄 E 權限提升、R 事後無法追查 M03-14 唯讀角色可以對客戶機器發動掃描、刪掉執行紀錄 🟡
T4 🟡 中 領工作時要用的祕密,在資料庫裡多留了一份明文 ① 派工時系統把客戶主機帳密解密,準備讓代理程式心跳時領走;② 解密後的明文順手寫進派工單的參數欄位、落到資料庫;③ 拿到資料庫備份或任一唯讀帳號的人查派工表——每次掃描都留一份客戶正式機房的密碼,且不會清理 客戶正式機房的可用主機密碼與掃描器管理員權杖 I 資料外洩 M03-9 開始掃描時把解密後的主機帳密多存一份明文 🟡

四種手法的共同點:這條線上流的是客戶正式機房的鑰匙(主機帳密)與稽核證據的原料(掃描結果),而對方是一台我們管不到的機器。T1 是「認錯機器」;T2 是「認對了機器、但信了它說的每一個字」;T3、T4 是「鑰匙在送到機器之前,就已經在我們這邊被不該碰的人碰到」。前兩個是這條線的本質風險——機器身分一旦認錯,後面沒有第二道門。

§5

五、我們要怎麼防、目前做到哪

對應四種威脅與這條線的本質,防線分七條。D1~D4 直接對應掃描抓到的四種手法;標 ◇ 的(D5~D7)是依這條線的特性補的標準防線,掃描範圍沒涵蓋、沒有實例。「目前」欄寫程式與部署裡實際有的機制;⚠️ 表示目前只靠慣例或只守到局部,沒有機制保證。

# 防線 擋哪種威脅 目前做到哪
D1 每個請求都要有可驗證的機器身分,身分只認簽章、不認自報。報到之外的每一支端點都要帶後端簽發的通行證;後端驗簽後只用證裡的機器編號;每個請求查一次撤銷/停用狀態;「回報結果」要比對「這張單是不是派給這台」 T1 ✅ 路由表第三欄強制宣告每支端點的認證等級(管理員/代理程式/報到三級,認不得的在啟動時就報錯,不會有「忘了想」的公開端點);✅ agent_pass_required 守門+AgentControlAuthService 驗簽、只認 sub、每請求查 remote_agents;✅ 確認收到與回報結果在交易內再驗撤銷、比對派工對象,不符回「找不到」;✅ 主專案的檔案下載端點用同一支守門,不另寫一套
D2 報到是身分的起點,要比其他端點更嚴:憑證申請檔的名稱必須等於本機指紋(擋「用自己的機器登記、名字填受害機」);帶舊編號重新登記要附舊私鑰簽的持有證明;被撤銷的機器一律拒收、不再洗回正常 T1 ✅ 三件都在 agent_enrollment_service.py。⚠️ 註冊 token 一家客戶一組、長期有效、沒有過期時間與使用次數上限(enroll_token 實體只有 enabled 旗標)——token 一旦流出,在管理員手動「撤銷並重產」之前,任何人都能登記新機器進該客戶名下。這是報到入口的最大缺口
D3 代理程式回報的一切都是外部輸入:檔名由後端決定或消毒(切掉路徑、查副檔名白名單,不合格退回系統自產名);回報內容只更新該台名下的單 T2 ✅ 兩個檔名來源一律過 sanitize_report_filename(),不合格退回 detection_report_<任務編號>.html;✅ 預覽端另有 C01 D4 收口。⚠️ 回報 payload 裡其他欄位(摘要統計、訊息文字)怎麼進畫面沒逐欄盤過
D4 鑰匙只在領工作那一刻解密、只留記憶體:派工單落庫時不含明文帳密;代理程式領單時另一條路當場解密 T4 ✅ _dispatch_one() 寫明文那一行已刪,心跳時走 detection_task_payload_provider._resolve_tool_credentials() 當場解密;DEV 殘留查為 0 筆。⚠️ 心跳回應本身仍含明文帳密(這是設計:代理程式要拿它去連主機)——所以 D1 認錯機器的後果就是帳密外流,兩條防線是連體的
D5 ◇ 誰能派工,在畫面那頭就要守住。發動掃描、取消、刪紀錄只放行任務被指派人與專案管理者 T3 ✅ 八支寫入端點已換成 assert_job_operator(只放行被指派人與 manager),詳見 C02 D1。這條線本身不驗人,守門在前一條線
D6 ◇ 通行證效期與撤銷機制要配套:長效票必須搭配每請求查撤銷,否則撤銷形同虛設;撤銷後所有通道(含下載)要同時失效 T1 的延伸 ✅ 通行證預設效期 10 年(與憑證同),但每個請求都查表,撤銷即時生效;DEV 實測撤銷後心跳/確認/回報/下載全部 403。⚠️ 票本身不會過期輪替——若後端簽章私鑰外流,10 年內簽出的任何票都有效,只能靠換私鑰(讓所有 agent 重新註冊)補救
D7 ◇ 前門層的雙向憑證要嘛真驗、要嘛拿掉。目前 agent 出示憑證、nginx 不看——多一層「看起來有在驗」的假象,日後有人以為 mTLS 在守而放鬆通行證那層,就出事 T1 的延伸 ⚠️ 前門 nginx 沒有 ssl_verify_client,agent 端白帶憑證。要嘛在前門對 /api/1.0/agents/ 路徑開 ssl_verify_client optional 並把結果傳給 api 做第二道確認,要嘛在文件與 DFD 明寫「控制面只靠通行證」——二選一,不要維持現在的半套

最該補的一步:D2 的註冊 token。這條線所有身分都從報到那一步長出來,而報到只憑一組「一家一組、永不過期、不限次數」的 token。給它加過期時間(例如 24 小時)與一次性(用過即廢),是整條線上成本最低、擋得最前面的一刀。

§6

六、依這些防線,掃描還沒看過的地方

  • 註冊 token 的生命週期(D2):沒有過期、沒有次數上限、沒有「哪台機器用了它」的紀錄。掃描六頁沒有任何一條提到它。
  • 前門層 mTLS 的實況(D7):DFD 與多份文件寫「控制面 mTLS」,實際 nginx 不驗。掃描是讀程式碼不是讀部署設定,所以沒抓到;這是文件與現況的落差,不是程式 bug。
  • 回報 payload 的其他欄位(D3):檔名修了,摘要統計與錯誤訊息怎麼進畫面、有沒有跳脫,沒逐欄看。
  • 心跳頻率與速率限制:一台被入侵的 agent 用通行證每秒打心跳,後端每次都查一次 DB——有沒有上限?沒看。

依據:STRIDE 六頁信任邊界連線標記(CM-2403/2404 驗收後版本)、jedi_remote_agent/api/routing.py/guards.py/app/service/agent_control_auth_service.py/agent_enrollment_service.py/agent_enroll_token_service.py、agent repo core/task_executor.py/core/cloud_trust.py/core/agent_auth.py/deploy/docker-compose.yml、FE nginx.onprem.conf、主專案 api/remote_agent/routes/agent_file_route.py、DFD Level 0。