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

C22 代理程式 → 證據暫存(agent-seaweedfs,S3 HTTP :8333)

代理程式把掃描報告先放在自己旁邊的那條線——同一台機器上的兩個容器。它不出客戶機房,掃描六頁也沒有任何一條落在它上面;但它存的是稽核證據的原始本體,而產品曾對它有「防篡改(WORM)」的想像。這一頁回答:這條線怎麼連、哪些功能走它、依特性可能有什麼威脅、我們要怎麼防、目前做到哪。

裝了 agent 才有 同機・明文・S3 金鑰 可能手法 3 種 掃描命中 0 條

這一頁怎麼來的:DFD Level 0 把產品運作時的連線編成 C01~C25;STRIDE 六頁每一條問題都標了發生在哪幾條線。這條線零命中——不是因為安全,是因為掃描範圍是主系統程式碼,agent 安裝包的 compose 與儲存設定不在裡面。第四段寫的是依這條線的特性可能的手法,沒有掃描實例。

§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 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
C22 — agent 到同機的 SeaweedFS。兩個容器在同一個 docker 網路,S3 埠預設只綁 127.0.0.1。agent 持有讀寫金鑰;雲端經 C21 從 agent 取檔,不直連 SeaweedFS。紅色是「持有金鑰者可以做」而目前沒擋的事。
§2

二、這條線怎麼連

項目 現況
誰 → 誰 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」)。

§3

三、哪些功能會走這條線

群 功能 對威脅的意義
① 寫入 掃描完成,connector 產出的報告經 _upload_report() 寫進 bucket;雲端派工單的結果回報只帶檔案編號 寫進去的那一刻起,這份檔就是稽核證據的原始本體,雲端那份是從這裡複製過去的
② 讀出 雲端經 C21 /blob/<uid> 取檔、/blob/<uid>/sha256 對帳 雲端拿到後會算 sha256 當防竄改錨點(upload_files.sha256)——錨點是取回那一刻算的,取回前被換掉就錨到假的
③ 清理 目前沒有自動清理;第一期無容量上限 越掃越多,磁碟滿是可用性問題;但也意味著所有歷史證據都在這裡,一次拿走全部
§4

四、依這條線的特性可能的攻擊手法(無掃描實例)

這條線零命中。但它的特性很清楚:同一台機器、明文 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」)。所以這條線的防線目標不是「讓機器擁有者改不了」,而是「改了會被發現」——那要靠雲端那頭盡早取回、算指紋、留副本。

§5

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

零命中的線沒有「直接對應掃描手法」的防線,五條全部是依連線特性補的標準防線,全標 ◇。「目前」欄寫程式與部署裡實際有的機制;⚠️ 表示沒有機制保證。

# 防線 擋哪種威脅 目前做到哪
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 側疊加鎖便宜,也才是這條線的正確信任模型。

§6

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

  • 整個 agent 安裝包不在掃描範圍:compose、install.sh、.env 的產生與權限、volume 的檔案權限——六頁 STRIDE 一條都沒有。這條線零命中是「沒掃」不是「沒事」。
  • 取回失敗的處置(D3):雲端取檔失敗時任務狀態怎麼走、有沒有重試、有沒有告警,沒看。
  • SEAWEEDFS_BIND_ADDR 改動的後果(D1):文件有沒有警告客戶「改成 0.0.0.0 等於把弱點報告開給全網」,沒看。
  • MinIO 模式的 TLS:客戶自備 S3 端點時 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。