Guidant AI 資安檢視總報告 · 連線清單
代理程式把掃描報告先放在自己旁邊的那條線——同一台機器上的兩個容器。它不出客戶機房,掃描六頁也沒有任何一條落在它上面;但它存的是稽核證據的原始本體,而產品曾對它有「防篡改(WORM)」的想像。這一頁回答:這條線怎麼連、哪些功能走它、依特性可能有什麼威脅、我們要怎麼防、目前做到哪。
這一頁怎麼來的:DFD Level 0 把產品運作時的連線編成 C01~C25;STRIDE 六頁每一條問題都標了發生在哪幾條線。這條線零命中——不是因為安全,是因為掃描範圍是主系統程式碼,agent 安裝包的 compose 與儲存設定不在裡面。第四段寫的是依這條線的特性可能的手法,沒有掃描實例。
%%{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 HOST["🏢 客戶機房・同一台主機(docker compose)"]
direction LR
AG["evidence-agent<br/>持 S3 讀寫金鑰"]
subgraph SW["guidant-agent-seaweedfs"]
direction TB
S3["S3 gateway :8333<br/>(綁 127.0.0.1)"]
BKT[("bucket guidant-evidence<br/>無版本控制・無 Object Lock")]
S3 --> BKT
end
ROOT(["🧑💻 主機擁有者/root<br/>讀得到 .env 的金鑰<br/>碰得到 volume"])
AG == "C22 · S3 HTTP :8333 · 金鑰簽章<br/>上傳報告・下載給雲端" ==> S3
ROOT -. "同一組金鑰<br/>或直接改 volume" .-> BKT
end
API["guidant-api"]
API -- "C21 · 取檔" --> AG
classDef ext fill:#FBFCFC,stroke:#9AA8AA,stroke-dasharray:3 2
classDef open fill:#FDECEC,stroke:#C0392B
class ROOT ext
class BKT open
| 項目 | 現況 |
|---|---|
| 誰 → 誰 | guidant-ai-agent 容器 → 同一份 compose 裡的 guidant-agent-seaweedfs 容器。走 docker 內部網路,位址 guidant-agent-seaweedfs:8333 |
| 協定/埠 | S3 相容 HTTP :8333(SEAWEEDFS_SECURE 預設 false,明文)。同機容器間,不經實體網路 |
| 對外暴露 | compose 把 8333(S3)、8888(filer)、9333(master)三個埠映射到主機,預設綁 127.0.0.1(SEAWEEDFS_BIND_ADDR),只有主機本機能連。改成 0.0.0.0 就全網可達 |
| 傳什麼 | 掃描報告(PDF/HTML/XML)、agent 自己的上傳檔。雲端要取時經 C21 向 agent 要,agent 再從這裡讀——雲端不直連 SeaweedFS |
| 怎麼驗身分 | S3 存取金鑰(SEAWEEDFS_ACCESS_KEY/SECRET_KEY)。compose 設為必填(:? 語法),沒設容器起不來——刻意的,因為 SeaweedFS 沒給金鑰就是匿名全開。金鑰由 install.sh 產生寫進 .env,agent 與 SeaweedFS 兩個容器讀同一組 |
| 也能換成 | STORAGE_TYPE=minio 指向客戶自備的 S3 端點(MinIO 等),欄位同一套;或 local 落容器內磁碟(無網路)。預設 seaweedfs |
| 防篡改(WORM) | 沒開。jedi-file-upload 建桶只呼叫 make_bucket(),沒帶 object_lock、沒設 retention 或 legal hold、沒開版本控制。SeaweedFS 3.99 實測這些能力都有(物件層的鎖是真的),但目前一項都沒用上;且實測 delete_bucket 連同鎖住的物件一起消失——桶層不設防 |
| 何時存在 | 客戶有裝代理程式、且用預設儲存 |
依據:agent repo deploy/docker-compose.yml(兩容器定義、:? 必填、127.0.0.1 綁定)、config/config.py(STORAGE_TYPE 三選一)、docs/fr066-6-t62-object-storage-and-worm.md(WORM 實測與「目前根本沒開」結論)、deploy/README.md §5(「本產品不宣稱 WORM」)。
| 群 | 功能 | 對威脅的意義 |
|---|---|---|
| ① 寫入 | 掃描完成,connector 產出的報告經 _upload_report() 寫進 bucket;雲端派工單的結果回報只帶檔案編號 |
寫進去的那一刻起,這份檔就是稽核證據的原始本體,雲端那份是從這裡複製過去的 |
| ② 讀出 | 雲端經 C21 /blob/<uid> 取檔、/blob/<uid>/sha256 對帳 |
雲端拿到後會算 sha256 當防竄改錨點(upload_files.sha256)——錨點是取回那一刻算的,取回前被換掉就錨到假的 |
| ③ 清理 | 目前沒有自動清理;第一期無容量上限 | 越掃越多,磁碟滿是可用性問題;但也意味著所有歷史證據都在這裡,一次拿走全部 |
這條線零命中。但它的特性很清楚:同一台機器、明文 S3、一組金鑰兩邊共用、持有金鑰者對桶內一切有完全控制。依這些特性,可能的手法有三種。風險欄寫「—(無掃描實例)」。
| # | 風險 | 威脅 | 駭客怎麼打 | 得手什麼 | STRIDE | 實例 |
|---|---|---|---|---|---|---|
| T1 | —(無掃描實例) | 主機擁有者或拿到主機 root 的人,改掉或刪掉證據,不留痕 | ① 在 agent 主機上拿到 root(客戶自己的維運人員、或入侵了那台機器的人);② 讀 .env 拿到 S3 金鑰,或直接進 seaweed-data volume;③ 覆寫某份掃描報告(例如把「3 個高風險」改成「0 個」)或刪掉它;④ 沒有版本控制、沒有 Object Lock,覆寫不留舊版、刪除不留紀錄;⑤ 雲端若還沒取回,取回時算的 sha256 錨的就是假檔 |
竄改或銷毀稽核證據,且事後查不出來 | T 竄改資料、R 事後無法追查 | — |
| T2 | —(無掃描實例) | 金鑰或埠外露,同網段的人直接讀整桶 | ① 客戶為了維運方便把 SEAWEEDFS_BIND_ADDR 改成 0.0.0.0,或 .env 被備份到共用位置;② 同內網的人用金鑰連 :8333,列出 bucket 全部物件;③ 下載所有歷史掃描報告——裡面是客戶每台主機的弱點清單、開放埠、組態缺失 |
客戶全部主機的弱點地圖(對攻擊者而言是現成的作戰計畫) | I 資料外洩 | — |
| T3 | —(無掃描實例) | 塞爆磁碟讓 agent 停擺 | ① 拿到金鑰的人(或 T1 的 root)往 bucket 狂寫;② 或單純等——沒有容量上限也沒有清理,掃得夠多磁碟自己滿;③ agent 寫不進報告,掃描全部失敗,但雲端只看到「上傳失敗」 | 讓該客戶的所有檢測停擺 | D 讓服務停擺 | — |
三種手法的共同點:全部以拿到那台主機為前提。這條線的本質是「資料存在客戶自己的機器上、用客戶自己機器上的金鑰保護」——機器擁有者永遠能改底層資料,這一點任何 S3 設定都擋不住(agent 部署手冊已明寫「本產品不宣稱 WORM」)。所以這條線的防線目標不是「讓機器擁有者改不了」,而是「改了會被發現」——那要靠雲端那頭盡早取回、算指紋、留副本。
零命中的線沒有「直接對應掃描手法」的防線,五條全部是依連線特性補的標準防線,全標 ◇。「目前」欄寫程式與部署裡實際有的機制;⚠️ 表示沒有機制保證。
| # | 防線 | 擋哪種威脅 | 目前做到哪 |
|---|---|---|---|
| D1 ◇ | S3 埠只綁本機、金鑰必填、金鑰不入版控 | T2 | ✅ compose 預設 127.0.0.1 綁定;✅ 金鑰 :? 必填,缺了容器不起;✅ install.sh 產生隨機金鑰寫 .env(.env.example 寫明「勿沿用範例值」)。⚠️ 綁定位址可被客戶改成 0.0.0.0,沒有任何警告 |
| D2 ◇ | 證據寫進去就不可覆寫、不可刪:建桶開 Object Lock(COMPLIANCE 模式)+版本控制,寫入時設 retention;桶本身的刪除也要擋 | T1 | ⚠️ 沒有。建桶只 make_bucket(),零 retention/versioning/legal hold。SeaweedFS 3.99 實測物件層鎖有效、但 delete_bucket 會連鎖住的物件一起刪——即使開了,持有金鑰者仍能整桶帶走。所以 D2 擋得住「改一份」,擋不住「全刪」;後者要靠 D3 |
| D3 ◇ | 雲端盡早取回、算指紋、留副本:掃描完成的回報一到,雲端立刻經 C21 取檔、算 sha256 落庫;之後 agent 側的檔只是備份,被改也不影響雲端那份 | T1(根本解法) | ✅ 回報結果後雲端會取檔並記 upload_files.sha256(FR-039 防竄改錨點)。⚠️ 取回與回報之間有時間窗;取回失敗時(雲端連不到 8443)證據只存在 agent 側,窗口無限長。沒有「取回失敗要告警」的機制 |
| D4 ◇ | 容量上限與清理:bucket 設配額或 agent 定期清已取回的檔;磁碟低於門檻告警 | T3 | ⚠️ 第一期無容量上限、無清理(設計文件明寫,清理指引在部署手冊,LRU 列 follow-up)。agent 上傳源碼包前有磁碟檢查(FR-058.7 D28),但那是「不夠就不掃」,不是「滿了會通知」 |
| D5 ◇ | 文件誠實:對客戶與稽核單位不宣稱 WORM/不可刪,說清楚「證據的可信副本在雲端、agent 側是暫存」 | 期望落差(不是技術威脅,但稽核場景裡最傷) | ✅ 部署手冊 §5 明寫「機器擁有者始終能改動底層資料,本產品不宣稱 WORM」。⚠️ DFD Level 0 的 C22 列「傳什麼」欄寫「證據暫存、WORM」——與程式現況不符,已回寫母卡 |
最該補的一步:D3 的告警。D2(Object Lock)開了也擋不住整桶刪;真正讓證據可信的是雲端那份副本。所以「回報了但取不回」要響——這比在 agent 側疊加鎖便宜,也才是這條線的正確信任模型。
install.sh、.env 的產生與權限、volume 的檔案權限——六頁 STRIDE 一條都沒有。這條線零命中是「沒掃」不是「沒事」。SEAWEEDFS_BIND_ADDR 改動的後果(D1):文件有沒有警告客戶「改成 0.0.0.0 等於把弱點報告開給全網」,沒看。MINIO_SECURE 預設 false,跨機明文——與主系統 C07 同病(總表 #124 裁定接受),但 agent 側沒人裁過。依據:agent repo deploy/docker-compose.yml、deploy/.env.example、deploy/README.md §5、config/config.py、docs/fr066-6-t62-object-storage-and-worm.md(SeaweedFS 3.99 Object Lock 實測)、core/task_executor.py(_upload_report);jedi-file-upload 全套件 grep retention|ObjectLock|legal_hold|versioning 零命中;DFD Level 0。