FR-177.B7 掃描報告:祕密保管、診斷包、維運與稽核、裝置指紋(CM-3523)

⚠️ 歷史紀錄:本報告描述的問題已於 2026-11-07 全部修正並合回 Agent 主線(修正卡 CM-3559~3564、3573~3576、3584,驗收紀錄見母卡 CM-3507)。文中的檔名與行號是掃描當下(commit bab2aa3)的版本,修正後已變動,請勿拿來定位現行程式。

§1

一句話結論

工具找到 1 個中度(MEDIUM)問題,三位驗證員一致成立:容器形態升級時,主機用 root 去跑「新映像裡的 cat」把升級腳本讀出來執行,而新映像的作業系統層沒有被簽章涵蓋——控制了 Agent 容器(或更新管道)的人可以藉此拿到客戶主機 root。人工逐條查證「重點看什麼」另記 3 個低度觀察(不算正式發現,建議擇期處理)。

§2

這一棒檢查什麼

Agent 平台層 11 個檔(約 1388 行):Fernet 祕密保管(secret_store.py)、給原廠的診斷包(support_bundle.py)、重啟與本機維運端點(ops.py、local_ops.py)、維運 CLI(local_state_cli.py)、稽核寫入與舊檔匯入(audit_log.py、audit_pipeline.py、audit_import.py)、device_uuid 算法(fingerprint.py、host_identity.py)、升級驗章 CLI(agent_tree_verify.py)。

§3

總覽表

# 嚴重度 一句話 急不急
F1 中 升級時主機以 root 執行新映像內未驗章的 cat,可被換掉而拿到主機 root 下一個升級相關版本前修
人工 O1 低 金鑰檔權限不對只記 warning、照用 可排進一般改善
人工 O2 低 診斷包 _SECRET_NAME 沒涵蓋 DSN/URL 內嵌密碼的 env(目前清單內只有 DATABASE_URL,已涵蓋) 新增 env 時注意
人工 O3 低 device_uuid 兩來源都讀不到時退化成固定值(只 warning,不拒啟) 靠雲端撞號偵測守門,可接受

術語:root=主機最高權限帳號;映像(image)=docker 容器的整包檔案系統;驗章=用原廠公鑰確認檔案沒被改過;uid 1000=容器內 Agent 跑的一般帳號。

§4

F1:升級時主機以 root 執行新映像內未驗章的程式

  • 白話:升級時,主機先叫舊版 Agent 驗新版「agent 目錄」的簽章,這一步沒問題。但驗完後,主機用 docker run --entrypoint cat <新映像> 把新版的 upgrade_runner.sh 讀出來,用 root 的 bash 直接執行。cat 這支程式本身、映像的 PATH 設定都不在簽章範圍內,攻擊者可以塞一支假 cat,讓它印出任意腳本。
  • 出事會怎樣:攻擊者在客戶主機上拿到 root,整台機器(含其他服務)失守。這也違反了設計的承諾:「舊版驗過新版之前,主機不跑新版任何一行」。
  • 要先有什麼才打得到:①能把升級單與升級包寫進主機的 agent-order/ 目錄,也就是已控制 Agent 容器,或控制主系統的更新推送;②手上有一份原廠真簽章、版號較新的 agent 樹(或主機設了 AGENT_UPDATE_ALLOW_SAME_VERSION=1);③compose 安裝形態,且裝了升級單的 systemd path unit。門檻不低,所以評中度;但它正好是「容器被打穿後能不能逃到主機」的那道牆。
  • 在哪裡:deploy/install.sh:2363(run_upgrade 內的 source <(docker run --rm --entrypoint cat "$AGENT_IMAGE_TAG" ...));驗章範圍見 guidant_agent/platform/agent_tree_verify.py:22-29(只驗 manifest 列的檔)。install.sh 在範圍外,只為追蹤資料流而讀。
  • 怎麼修(方向,不在本卡修):主機端不執行未驗章映像裡的任何東西。改從已驗過的 staging 樹讀 install-lib/upgrade_runner.sh;或 build 時把映像 ID/config digest 一起簽進去,主機先 docker image inspect 比對再用。
§5

「重點看什麼」逐條人工查證

項目 結果 為什麼
fingerprint.py+host_identity.py:兩來源都讀不到退到什麼 成立(低,O3) fingerprint.py:58-66:以空字串參與雜湊,退化成固定值 `sha256("
路徑能不能被環境變數換成任意檔 不成立(有界) config/config.py:183-184 讀 HOST_PRODUCT_UUID_PATH/HOST_MACHINE_ID_PATH。但能改 Agent 環境變數的人本來就是主機 root/能改 .env 的人,而且註冊還要一次性 token,換路徑不會增加新能力。
容器內 machine-id 是掛主機的還是容器自己的 不成立 deploy/docker-compose.yml:203-204 唯讀掛宿主的 /etc/machine-id 與 host-id/(product_uuid 由 install.sh 複製成 0440),重建容器不會漂移。
support_bundle.redacted_settings 的 _SECRET_NAME 部分成立(低,O2) support_bundle.py:35 抓 `SECRET
_journal_tail 有沒有過 mask_text 不成立 support_bundle.py:171:log 不論來自 journald 或記憶體,每一行都過 mask_text。
recent_diagnostics 含內網拓撲要不要出去 設計取捨,非漏洞 診斷包本來就是給原廠支援看網路問題的,內容過 _masked_json;由客戶自己下載、自己決定給不給。
排除清單三項,build 有沒有讀到 allowed_targets/secrets/ 不成立 build(support_bundle.py:149-175)只寫 manifest/log/env 設定/儀表板/最近診斷五段,不讀資料目錄的任何檔。
secret_store._read_key(create=True) 建檔權限 不成立 secret_store.py:98:`os.open(..., O_CREAT
:107 權限不對只 warning 成立(低,O1) secret_store.py:106-107:權限不是 600 時只記 log,照讀照用。建議改成拒用或自動收緊。
金鑰與密文同目錄 設計取捨 防的是:「只拿到密文檔」、備份外流到沒有金鑰的地方、其他非 root 帳號(0600/0700)。不防的是:整個資料目錄被複製,或主機 root 被拿下。這點與 .claude/rules/security/secrets.md 的決定一致(不從 mTLS 私鑰派生)。
ops.request_restart 的 note 長度與遮罩 不成立 ops.py:157 截到 200 字;ops.py:164 的 log 不帶 note;log 全走 audit_pipeline 的 SecretMaskingFilter。
local_ops loopback 端點各自能做什麼 不成立 只有兩支:revoke-admin-sessions(登出本機管理員)與 cancel-running(取消工作),都是「收緊」動作,沒有改設定、讀祕密或觸發更新的端點。而且除了 loopback,還要帶 root 才讀得到的 0600 權杖(local_ops.py:58-61),所以容器內同 pod 的行程不夠打到。
local_state_cli 子命令有沒有印祕密 不成立 六個子命令(local_state_cli.py:134-141)都沒印密碼;set-admin-password 只從 stdin 讀。守門=能執行這支 CLI 且能寫 DB=root(資料目錄權限),符合設計。
audit() kwargs 有沒有帶密碼/token 不成立 範圍內的呼叫只帶 sub/first_time/method/reason/各表刪除筆數。
audit_import 會不會覆蓋既有紀錄 不成立 audit_import.py:154-175:只 append 新列,先查 AGENT_LEGACY_AUDIT_IMPORTED 防重複匯入,舊檔改名不刪。
agent_tree_verify 路徑參數有無限制、版號從哪來 部分成立(併入 F1) 能驗任意目錄是無害的(只讀、只回結果);版號來自已驗章的 manifest(agent_tree_verify.py:33),可信。問題在「驗完之後主機跑了什麼」,即 F1。
§6

可信度兩層

  • 「報出來的存在嗎」:F1 經三位驗證員(可達性、影響、防禦三個角度)3/3 確認,且人工開 deploy/install.sh:2363 核對過程式碼確實如此。
  • 「只有這些嗎」:low effort 只派 1 位研究員,沒有威脅建模與廣度掃描;人工逐條補查了「重點看什麼」。不能宣稱範圍內沒有其他問題,只能說這些重點都查過了。
§7

執行概況(照 stamp 抄)

  • Run ID:wf_68fd2842-89a;歸檔:docs/security-scan/FR-177/runs/CLAUDE-SECURITY-20261105-174556/
  • Revision 63334f4(工作樹乾淨),effort low,範圍 11 檔,耗時約 49 分鐘
  • verification.status: verified;reason_kind: null;candidates: 1;panel_votes: 3;researchers_dispatched: 1;researchers_returned: 1
  • 驗證面板完整跑完。整個過程只讀程式碼,沒有執行任何程式或實際攻擊。