找到 1 個中風險問題:主系統容器啟動時,把「發給 Agent 用的那把憑證簽發章」加進了所有對外連線的信任清單;拿得到 Agent 註冊碼的人可以替自己蓋一張「我是 Anthropic/客戶郵件伺服器」的假證,搭配網路中間人位置就能偷走 AI 金鑰與郵件密碼。換鑰匙腳本本身沒掃到問題。
三把舊加密鑰匙(Drive、檢測工具、AI)併成一把 APP_ENCRYPTION_KEY:升級時由 migrate_encryption_keys.py 以超級管理員身分把全部客戶的密文換新鑰重鎖;另外看整合憑證加密怎麼讀這把鑰匙、設定載入器怎麼退回舊鑰,以及正式容器啟動腳本做了什麼。4 檔、約 753 行。
| # | 這是什麼問題 | 出事會怎樣 | 要先有什麼才打得到 | 在哪裡 | 怎麼修 | 嚴重度 |
|---|---|---|---|---|---|---|
| F1 | 主系統對外連線「信任誰發的證」的清單裡,混進了一把任何 Agent 都能請它蓋章的章 | AI 金鑰、郵件密碼、物件儲存密鑰、送去 AI 的證據內容可被偷看與竄改 | ① 一組 Agent 註冊碼(租戶管理員就能產)② 能攔截主系統主機對外網路(區網 DNS/ARP 偽冒、被控制的閘道) | docker/production/entrypoint.sh:48-51 |
不要把 Agent CA 放進整個程式共用的 SSL_CERT_FILE;只給需要的物件儲存客戶端與 Redis |
中(要同時滿足兩個前提) |
白話:電腦連 HTTPS 網站時,會檢查對方出示的「身分證」是不是可信的發證單位蓋的章。我們的容器啟動時,為了讓主系統認得內部的 Redis 與檔案儲存(SeaweedFS),把內部憑證章(CA,就是「發證單位」)加進信任清單。問題是這把章就是 Agent CA——Agent 註冊時,它會依 Agent 自己報的網址替它簽一張伺服器身分證,而且還沒被核准的 Agent 也簽(這是已知的 #249)。兩件事加起來:攻擊者自稱網址是 https://api.anthropic.com,就拿到一張主系統會相信的「我是 Anthropic」身分證。
出事會怎樣:攻擊者站在主系統對外連線的中間(網路中間人),出示這張假證,主系統的 AI 呼叫(litellm)、更新站與授權站呼叫(httpx)、外部 S3(minio)、郵件寄送(SMTP STARTTLS)全部照單全收。AI 金鑰(Authorization 標頭)、郵件密碼、物件儲存密鑰、送去 AI 的證據內容都會外流。
要先有什麼才打得到:
在哪裡:
docker/production/entrypoint.sh:44-51(合併信任庫、export SSL_CERT_FILE)scripts/installer/internal_tls.sh:15(把 pki/agent/ca.crt 複製成內部 CA——首腦核對:開檔確認,確實是同一把)jedi-remote-agent 的 issue_credentials/san_from_base_url(已知 #249)怎麼修:
entrypoint.sh 不再 export SSL_CERT_FILE;改成只把內部 CA 交給需要的客戶端:內建 SeaweedFS 的 minio 客戶端自帶 http_client(urllib3 PoolManager(ca_certs=/etc/guidant/internal-ca.crt)),Redis 用既有的 REDIS_SSL_CA_CERTS。首腦核對註記:中風險,非 HIGH,依卡片規則不強制逐條核;仍已開 internal_tls.sh:15 與 entrypoint.sh:48-51 確認信任鏈成立。與 §3.1 #249 是同一根源的新後果(#249 原本只講「CA 幫未核准者簽證」,本條補上「主系統對外全面信任這張證」),建議併卡處理。
| 卡片要我看的 | 人工結果 |
|---|---|
舊鑰從命令列參數還是環境變數帶入;docker/init/entrypoint.sh:134 怎麼呼叫 |
不成立(沒問題)。腳本支援 --old-*-key 參數(ps 看得到),但 init 容器呼叫時只帶 --dry-run(docker/init/entrypoint.sh:134),鑰匙全走環境變數(installer 以 -e 變數名 不帶值帶入)。命令列參數只剩手動跑時的選項,腳本檔頭已註明建議走環境變數。 |
_scrub(:200)遮 psql 錯誤回顯是否完整 |
大致成立、殘留低。只遮 gAAAAA… 形狀的密文;錯誤回顯裡的明文欄位值不會出現(UPDATE 寫的都是密文),舊值也是密文。另截斷 500 字。新鑰匙本身從不進 SQL。沒發現外洩路徑。 |
build_apply_sql:隨機 tag dollar quote、SET LOCAL app.is_super_admin='t' |
不成立(沒問題)。tag 每次隨機且會檢查不在內容裡;row_id 經 int();表名欄名來自寫死的 TARGETS。SET LOCAL 只在 --single-transaction 這一個交易有效(:212);讀取那段用的是 SET(:225)但每次 psql 是獨立連線,結束即消失。腳本只在 init 容器(cmmgr 身分)或手動執行,主系統服務不會呼叫。 |
rekey_token:新鑰解得開當「已是新的」;can_apply 遇 bad 整批不套還是跳過 |
不成立(行為正確)。只要有一列三把舊鑰都解不開,can_apply=False,整批一列都不寫、回 exit 2(:301-305)——預設擋下來,不會半套。另外讀與寫之間有人改資料時,WHERE 欄=舊值 對不上會整批 RAISE 回滾(:194)。 |
APP_ENCRYPTION_KEY 沒設時「警告後明文落庫」,正式安裝會不會走到而沒人發現 |
成立但屬設計取捨、低風險。config_secret_crypto.py:65-69 沒鑰就原樣存明文,core/app_factory.py:397 只記一行 warning。正式安裝 install.sh:1305 一律產生這把鑰並寫進 env 檔,升級也會沿用(:1229、:2444),正常路徑走不到。只有有人手動刪掉 env 檔那行才會發生,且只有 log 一行警告、畫面無提示。建議(非本棒範圍):非 DEV 環境缺鑰時拒絕啟動。 |
config_loader.py 新增 36 行:讀 /run/guidant/guidant.env,不存在時退回哪裡 |
卡片前提與現況不符。本版 config/config_loader.py 沒有讀 /run/guidant/guidant.env 的程式碼(全檔 159 行,新增的是 resolve_encryption_key:新鑰優先、沒設才退回各用途自己的舊鑰)。所以不存在「退回磁碟舊明碼檔」這條路。舊鑰退回只回鑰匙字串,不讀任何檔。 |
docker/production/entrypoint.sh:非 root 後還做什麼;合併 CA 的來源能不能被可寫掛載塞假 CA |
成立,即 F1。非 root 後只做 chdir、合併 CA、exec binary,沒有 chown/降權。CA 來源 /etc/guidant/internal-ca.crt 是 installer 從 Agent CA 複製來的唯讀掛載,一般使用者塞不進假 CA;真正的問題是這把 CA 本身會替 Agent 簽任意網址的證(見 F1)。 |
| 項目 | 值 |
|---|---|
| run ID | wf_e3e13c9e-e7c |
| 報告目錄 | CLAUDE-SECURITY-20261108-191411(已歸檔至 docs/features/security-scan-consolidated/runs/be/) |
| revision | 1c9be8d84fb96afcaf2af91af2c4380f9cb6f8ec(工作樹乾淨) |
| 範圍 | 4 檔,effort low,focus attack-surface |
| verification.status | verified |
| reason_kind | 無 |
| candidates | 1 |
| panel_votes | 3(三個檢查員對 1 條各投一票,3 票全投出) |
| researchers_dispatched / returned | 2 / 2 |
| 耗時 | 約 67 分鐘 |