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

C23b 代理程式本機程序(sonar-scanner/git clone/cinc-auditor)

嚴格說這不是一條「連線」——是代理程式在自己的容器裡起子行程,處理從外面拿進來的東西:解壓使用者上傳的源碼包、clone 公開 repo、跑 sonar-scanner 分析、跑 cinc-auditor 載入規則包。外來內容在 agent 本機被展開、被執行——這就是它要單獨一頁的理由。掃描六頁沒有一條落在它上面。這一頁回答:它怎麼運作、哪些功能走它、依特性可能有什麼威脅、我們要怎麼防、目前做到哪。

派掃描時 外來內容在本機展開執行 可能手法 4 種 掃描命中 0 條

這一頁怎麼來的:DFD Level 0 把產品運作時的連線編成 C01~C25;STRIDE 六頁每一條問題都標了發生在哪幾條線。這條線零命中——agent 的 connector 層不在掃描範圍。第四段寫的是依特性可能的手法,沒有掃描實例。

DFD 寫錯的一點:DFD §2.4/§2.6 把 Nmap 列為 agent 本機程序。實查 nmap.py 開頭:Nmap 採 NPSL 授權不可打包進我們的 image,所以 connector 改成 SSH 登進客戶指定的「執行主機」在上面跑 nmap——那是 C23 的形狀,不是本機程序。本頁不含 Nmap;已回寫母卡請首腦改 DFD。

§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 TB
    API["guidant-api"]
    REPO(["🌐 公開 Git repo<br/>(使用者填網址)"])
    PROF(["🌐 規則包網址<br/>(C24)"])
    subgraph AG["evidence-agent 容器(root・無資源上限)"]
        direction TB
        DL["下載器:mTLS 取檔<br/>sha256 對帳 → 安全解壓"]
        WS[("工作目錄 /tmp/sonar-scan-*<br/>規則包 cache /data/content/cache")]
        SS["sonar-scanner 子行程<br/>(分析源碼)"]
        GIT["git clone --depth 1 子行程"]
        CINC["cinc-auditor 子行程<br/>(載入規則包、連主機)"]
        DL --> WS
        GIT --> WS
        WS --> SS
        WS --> CINC
    end
    SQ(["SonarQube Server(C23a)"])
    HOSTS(["受檢主機(C23)"])
    API -- "C20 · 源碼包/file 型規則包" --> DL
    REPO -. "https · clone" .-> GIT
    PROF -. "url 型規則包<br/>cinc-auditor 自己抓" .-> CINC
    SS -- "推結果" --> SQ
    CINC -- "SSH/WinRM" --> HOSTS
    classDef ext fill:#FBFCFC,stroke:#9AA8AA,stroke-dasharray:3 2
    classDef open fill:#FDECEC,stroke:#C0392B
    class REPO,PROF,SQ,HOSTS ext
    class SS,GIT,CINC open
C23b — agent 容器內的子行程。三種外來內容進到本機:使用者上傳的源碼包(經 C20 下載)、公開 repo(agent 直接 git clone)、規則包(file 型經 C20、url 型由 cinc-auditor 自己去抓)。紅色:容器以 root 跑、沒有資源上限,子行程跑的是別人給的內容。
§2

二、這條線怎麼連

項目 現況
誰 → 誰 evidence-agent 容器內的 Python 主程式 → 同容器內的子行程(subprocess)。沒有網路連線——但子行程處理的內容來自外面
容器身分 Dockerfile.runtime 基於 debian:12-slim,沒有 USER 指令,容器以 root 執行。compose 沒設記憶體、CPU、行程數上限(mem_limit/cpus/pids_limit 零命中)
sonar-scanner agent 本機跑 /usr/bin/sonar-scanner(路徑寫死,AGENT_SONAR_SCANNER_BIN 可覆寫),參數 -Dsonar.host.url/projectKey/projectBaseDir/sources=.;token 走環境變數 SONAR_TOKEN 不進命令列;逾時 _SCANNER_TIMEOUT_SEC。工作目錄 tempfile.mkdtemp(prefix="sonar-scan-"),finally 整個 rmtree
源碼從哪來 兩條:① upload 模式——使用者上傳的壓縮包,agent 經 C20 GET /agents/files/<uid> 串流下載,四道關卡:磁碟檢查 → 串流下載邊計量 → sha256 對帳(解壓前) → 安全解壓(標準庫路徑消毒+自製解壓炸彈防護:解壓前查宣告額度、解壓後複查實際用量);限額(檔案大小/解壓後總量/檔案數)吃雲端下發值,沒下發退回預設並記警告。② scan 模式——git clone --depth 1 使用者填的網址,只收 http:///https://(不收 file:// 與本機路徑),-c credential.helper=+GIT_TERMINAL_PROMPT=0 不讓私有 repo 卡住等密碼
cinc-auditor 跑 /usr/bin/cinc-auditor exec <規則包> -t <transport>://<名稱> --config <檔>。規則包三種來源:使用者手放在 /data/content/<dir>(唯讀 bind mount);file 型經 C20 下載、sha256 對帳(required=True,缺指紋直接擋)、安全解壓到 /data/content/cache/<uid>/v<version>/(先解到暫存目錄、全部完成才 os.replace() 原子換上);url 型——agent 原樣把網址交給 cinc-auditor 自己去抓,不經 agent 下載器、不比對雲端下發的 sha256(見 C24)
何時存在 派 SonarQube(scan/upload 模式)或主機型掃描時

依據:agent repo deploy/Dockerfile.runtime(無 USER)、deploy/docker-compose.yml(無資源限制)、core/task_executor_connectors/sonarqube.py(四道關卡、_ALLOWED_GIT_SCHEMES、SONAR_TOKEN、workspace 清理)、core/archive_safety.py、core/profile_cache.py(file 型 required=True、url 型原樣交出)、core/task_executor_connectors/inspec.py(--config 檔)、core/task_executor.py(_get_stream 邊寫邊計量)。

§3

三、哪些功能會走這條線

群 功能 對威脅的意義
① 源碼分析 SonarQube scan/upload:把客戶源碼放到 agent 本機、跑 sonar-scanner 源碼包是使用者上傳的壓縮檔——解壓炸彈、路徑穿越、超大檔都在這裡發生;sonar-scanner 本身會讀源碼裡的設定檔(sonar-project.properties)——使用者可以在源碼包裡放一份
② 規則包載入 cinc-auditor 載入 InSpec/GCB 規則包 規則包就是程式(Ruby DSL),cinc-auditor 載入即執行。file 型有 sha256 把關;url 型沒有
③ 清理 workspace rmtree、規則包 cache 無上限無清理 磁碟是共用資源
§4

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

這條線零命中。依特性——外來內容在 root 容器裡被解壓、被子行程執行,且無資源上限——可能的手法有四種。風險欄寫「—(無掃描實例)」。

# 風險 威脅 駭客怎麼打 得手什麼 STRIDE 實例
T1 —(無掃描實例) 規則包裡藏程式,在 agent 容器裡以 root 執行 ① 能上傳規則包、或控制得了 url 型規則包網址的人;② 規則包是 InSpec 的 Ruby DSL,control 區塊裡可以寫任何 Ruby——讀 /data/certs 的通行證與私鑰、讀 .env 的 S3 金鑰、開反向 shell;③ cinc-auditor 載入規則包就執行;④ 容器是 root,拿到的是整個容器(含所有憑證),再拿憑證去冒充這台 agent(通往 C20 T1) 代理程式的全部身分與鑰匙;客戶內網的立足點 E 權限提升 —
T2 —(無掃描實例) 源碼包裡放一份 sonar 設定檔,改掉掃描行為 ① 上傳源碼包的人在根目錄放 sonar-project.properties;② sonar-scanner 會讀它——sonar.exclusions 排掉有問題的檔、sonar.host.url 改指別處(命令列 -D 優先,但沒明寫的鍵會被檔案補上);③ 分析結果看起來乾淨,或結果連同 token 推到攻擊者的伺服器 偽造源碼檢測結果;SonarQube token 外流 T 竄改資料、I 資料外洩 —
T3 —(無掃描實例) 特製壓縮包或巨型 repo 把 agent 主機撐爆 ① 上傳幾 KB 的解壓炸彈、或填一個數 GB 的公開 repo 網址;② 若限額沒下發、或 clone 沒限大小,解壓/clone 把磁碟吃光;③ 容器無記憶體上限,sonar-scanner 分析超大源碼吃爆記憶體,連帶其他掃描與心跳一起死 讓該客戶的代理程式整台停擺 D 讓服務停擺 —
T4 —(無掃描實例) 用 clone 網址讓 agent 替人探內網 ① 有權設 SonarQube 任務的人填 http://10.0.0.5:8080/ 當 repo 網址;② agent 從客戶內網對它 git clone;③ 連得上/連不上/回什麼錯,從任務失敗訊息看得出來——agent 變成內網探測器(同 C21 T3 的形狀) 客戶內網主機存活圖 I 資料外洩 —

四種手法的共同點:全部是「使用者給的東西在 agent 本機被當成可信的來處理」。T1 最嚴重——規則包本來就是程式,這是 InSpec 的設計不是 bug;產品能做的是確認規則包是誰上傳的、沒被換過(sha256),以及把執行環境關小(非 root、資源上限),讓就算跑了惡意規則包也拿不到整台。

§5

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

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

# 防線 擋哪種威脅 目前做到哪
D1 ◇ 規則包的完整性有信任根:file 型帶 sha256、對帳不符即 fail;url 型也要比對雲端下發的指紋 T1 ✅ file 型:required=True,缺 sha256 直接擋,對帳不符不靜默;解壓先到暫存目錄、完成才原子換上。⚠️ url 型原樣交給 cinc-auditor 自己抓,不經 agent 下載器、不比對(雲端已下發 sha256,agent 端沒用——詳 C24)
D2 ◇ 容器非 root、資源有上限:USER 非特權帳號、cap_drop ALL、no-new-privileges、記憶體與 pids 上限 T1 的後果縮小、T3 ⚠️ 容器以 root 跑(Dockerfile 無 USER);⚠️ compose 無任何資源上限。這兩項是標準 docker 加固,一次設定全部子行程受益。注意 /dev/shm 暫存檔與 /host/product_uuid 讀取可能需要調整權限
D3 ◇ 源碼包四道關卡:磁碟檢查 → 串流下載計量 → sha256 對帳(解壓前)→ 安全解壓(路徑消毒+炸彈防護,宣告值不可信要複查實際用量) T3 ✅ 四道都在(sonarqube.py 的 upload 流程+archive_safety.py);✅ 限額吃雲端下發、沒下發退回預設不等於無上限。⚠️ git clone 這條沒有大小上限(--depth 1 減少歷史,但單版本數 GB 的 repo 擋不住);clone 逾時 10 分鐘是唯一的限制
D4 ◇ sonar-scanner 的關鍵參數由命令列鎖死,不讓源碼包裡的設定檔蓋掉:sonar.host.url、sonar.projectKey、sonar.exclusions 等都明寫 -D;或掃描前刪掉源碼包裡的 sonar-project.properties T2 ✅ host.url/projectKey/projectBaseDir/sources 四個命令列明寫(-D 優先於檔案)。⚠️ sonar.exclusions、sonar.inclusions、sonar.scm.* 等沒明寫——源碼包裡的設定檔可補上這些鍵。沒有「刪掉包內設定檔」的步驟
D5 ◇ clone 網址只收 http(s)、不收本機路徑、不等密碼 T4、T1 的一種變體 ✅ _ALLOWED_GIT_SCHEMES 只收 http:///https://(註解明寫「收了 file:// 等於開一條讀取 agent 本機檔案的路」);✅ credential.helper= 空+GIT_TERMINAL_PROMPT=0。⚠️ 不擋內網位址——與 C21/C23a 同一裁定(哪裡算合法由客戶管),但 clone 的錯誤訊息回不回畫面沒查
D6 ◇ 工作目錄與 cache 有清理與上限 T3 ✅ sonar workspace finally rmtree。⚠️ 規則包 cache 無容量上限、無清理(設計文件明寫第一期不做,LRU 列 follow-up)

最該補的一步:D2。非 root+資源上限是一次設定、全部子行程受益的加固,也是 docker 部署的基本功——規則包本來就是程式(T1),這一步決定「跑了惡意規則包」是拿到一個受限帳號還是整台容器。

§6

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

  • 整個 agent image 的加固(D2):root、無 cap_drop、無資源上限——六頁 STRIDE 零條,因為掃描看程式不看 Dockerfile 與 compose。
  • url 型規則包不對帳(D1):雲端那頭修了(下發 sha256),agent 端沒接——見 C24。
  • sonar-scanner 的設定檔覆寫面(D4):哪些鍵命令列沒鎖、源碼包裡能改到什麼,沒逐鍵查。
  • git clone 大小上限(D3):源碼包有,clone 沒有。
  • 規則包 Ruby DSL 的執行邊界(T1):InSpec 本來就允許任意 Ruby,有沒有 cinc-auditor 層的 sandbox 選項,沒查。

依據:agent repo deploy/Dockerfile.runtime、deploy/docker-compose.yml、core/task_executor_connectors/sonarqube.py/inspec.py/nmap.py(開頭 docstring:Nmap 為 SSH 型)、core/archive_safety.py、core/profile_cache.py、core/task_executor.py;DFD Level 0 §2.4/§2.6。