本卡屬 FR-077(母卡 CM-1591),修 R1(CM-1592)的 F1+F3(心跳)與 F2+F5(ack/result)四條。面板未跑成(額度全滅),runner 逐條開檔核對+首腦親自複核屬實。決策者 2026-09-08 裁 CRITICAL(原 runner 判 HIGH),四條併一張因為修法是同一套機制。
我們發給客戶裝在機房的 agent,會定期撥回雲端「報到」(心跳)、領工作、回報結果。這四個網址(register/heartbeat/ack/result)完全不掛任何認證——程式碼註解說「不用驗,因為外層 nginx 會用 mTLS 驗客戶端憑證」。但出貨的 nginx 設定根本沒有那一行。 三個 nginx 設定檔全部 grep 不到 ssl_verify_client。
所以實際上:請求裡自己寫「我是哪台 agent」,系統就信。 後果分四層:
/api/1.0/agents/heartbeat,body 帶任一台 agent 的 agent_uid,回應就是那台 agent 待辦工作的內容——含 Fernet 解密後的明文客戶帳密(SonarQube token、WinRM/SSH 帳密,通往客戶自己的基礎設施)。不需要任何帳號、token 或憑證。heartbeat() 兩處查 agent 的查詢都沒帶 tenant_id,而同一支檔案的 register() 有帶。加上控制面跑在無 user context 的 super admin scope(繞 RLS),知道別租戶的 agent_uid 就能拿別租戶的憑證。/agents/tasks/<uid>/ack 與 /result 只認路徑上的 task uid,不驗身分、不查「這張單是不是派給你的」。任何人知道一個 task uid 就能宣告它「成功」並塞任意 result_ref/summary,雲端會把它轉成合規稽核證據存進客戶紀錄。業界對照(決策者要求的分析,摘要):Elastic Agent/Kubernetes kubelet/AWS SSM 的共同做法是——註冊後每台 agent 持有自己的金鑰,之後每次通訊都用它證明身分,應用層自己驗;TLS 層的客戶端憑證是加分項,不能當唯一防線。我們已經有 80% 零件(註冊時簽憑證給 agent、agent 端 _post() 也真的用 cert= 出示了憑證),缺的只是「雲端反過來驗」這一段。
首腦核對(2026-09-08,不信 runner 自述):
api/remote_agent_route.py:220-223 四條 ROUTE_TABLE 第三欄確為 False;plugin.py:create_blueprint() 對 False 直接 api.add_resource(resource, path) 不經 _guard()。app/service/agent_enrollment_service.py:285 get_one_remote_agent(uid=agent_uid) 與 :288 get_one_remote_agent(device_fingerprint=device_uuid) 皆無 tenant_id;對照 :94-99 _resolve_agent() 有 found.tenant_id == tenant_id。app/service/agent_task_service.py:73-107 ack_task(uid)/receive_result(uid, payload) 全程無 agent_id/tenant_id 比對。nginx.onprem.conf:63 有 listen 443 ssl、:71-72 有 server 憑證,但 BE、FE 兩個 repo 零行 ssl_verify_client(grep -rn 全 repo)。app/remote_agent/adapter/detection_task_payload_provider.py:68,110-127 _resolve_tool_credentials() 確實 self._crypto.decrypt(config.credentials_encrypted) 後合併進心跳回應。core/task_executor.py:_post() 用 httpx.Client(cert=(state.cert_file, state.key_file)) 出示憑證——agent 端已經在出示,是雲端沒收。remote_agents 表沒有存公鑰/憑證的欄位(migrations/001-remote-agent-tables.sql 與主專案 2026-06-19-fr039-remote-agents-auth-columns.sql 都 grep 不到 cert/public_key)——修法需要加欄位。~/Projects/Jedicogy/module/jedi-python-package/jedi-remote-agent/jedi_remote_agent/api/remote_agent_route.py:220-223 ROUTE_TABLE 四條控制面端點第三欄 False
~/Projects/Jedicogy/module/jedi-python-package/jedi-remote-agent/jedi_remote_agent/plugin.py:create_blueprint() False → 不套任何 decorator;RemoteAgentAdapters 沒有 agent 認證插槽
~/Projects/Jedicogy/module/jedi-python-package/jedi-remote-agent/jedi_remote_agent/app/service/agent_enrollment_service.py:274-311 heartbeat():身分只看 payload 自報,兩處查詢無 tenant_id
~/Projects/Jedicogy/module/jedi-python-package/jedi-remote-agent/jedi_remote_agent/app/service/agent_enrollment_service.py:186-272 register():簽完憑證後沒把公鑰/憑證指紋存進 remote_agents
~/Projects/Jedicogy/module/jedi-python-package/jedi-remote-agent/jedi_remote_agent/app/service/agent_task_service.py:73-107 ack_task/receive_result:不驗身分、不查歸屬
~/Projects/Jedicogy/module/jedi-python-package/jedi-remote-agent/jedi_remote_agent/domain/agent_task/service/agent_task_domain_service.py 狀態機入口 update_status(),歸屬檢查可放這層
~/Projects/Jedicogy/module/jedi-python-package/jedi-remote-agent/jedi_remote_agent/infra/remote_agent/model/remote_agent.py 要加公鑰欄位
~/Projects/Jedicogy/module/jedi-python-package/jedi-remote-agent/jedi_remote_agent/migrations/ 隨包 migration 要加一支
~/Projects/Billows/Audit-Manager/compliance-manager-be/scripts/sql/ 主專案對應 migration
~/Projects/Billows/Audit-Manager/compliance-manager-be/app/remote_agent/adapter/detection_task_payload_provider.py:110-127 明文憑證落點(本卡不動它,記 follow-up)
~/Projects/Billows/Audit-Manager/compliance-manager-fe/nginx.onprem.conf:63-95 連帶:補 ssl_verify_client(加分項,非唯一防線)
~/Projects/Billows/Audit-Manager/evidence-agent/core/task_executor.py:_post() agent 端已用 cert= 出示;需配合改成對請求簽章(見怎麼修)
核心原則:身分是「證明」不是「宣稱」。應用層自己驗,fail closed。 分五步,①②③是必要條件,④⑤是連帶。