---
title: C23b agent 本機程序
eyebrow: Guidant AI 資安檢視總報告 · 連線清單
h1: C23b 代理程式本機程序（sonar-scanner／git clone／cinc-auditor）
lede: 嚴格說這不是一條「連線」——是代理程式在自己的容器裡起子行程，處理從外面拿進來的東西：解壓使用者上傳的源碼包、clone 公開 repo、跑 sonar-scanner 分析、跑 cinc-auditor 載入規則包。**外來內容在 agent 本機被展開、被執行**——這就是它要單獨一頁的理由。掃描六頁沒有一條落在它上面。這一頁回答：它**怎麼運作**、**哪些功能走它**、依特性**可能有什麼威脅**、我們**要怎麼防、目前做到哪**。
chips:
  - { text: "派掃描時", kind: plain }
  - { text: "外來內容在本機展開執行", kind: warn }
  - { text: "可能手法 4 種", kind: accent }
  - { text: "掃描命中 0 條", kind: plain }
---

> **這一頁怎麼來的**：[DFD Level 0](../DFD/dfd-level0.html#c23b) 把產品運作時的連線編成 C01～C25；[STRIDE 六頁](../STRIDE/S-spoofing.html)每一條問題都標了發生在哪幾條線。**這條線零命中**——agent 的 connector 層不在掃描範圍。第四段寫的是依特性可能的手法，沒有掃描實例。
>
> **DFD 寫錯的一點**：DFD §2.4／§2.6 把 **Nmap 列為 agent 本機程序**。實查 `nmap.py` 開頭：Nmap 採 NPSL 授權不可打包進我們的 image，所以 connector 改成 **SSH 登進客戶指定的「執行主機」在上面跑 nmap**——那是 [C23](C23-agent-to-target-host.html) 的形狀，不是本機程序。本頁不含 Nmap；已回寫母卡請首腦改 DFD。

## 一、連線圖

```{.mermaid cap="C23b — agent 容器內的子行程。三種外來內容進到本機：使用者上傳的源碼包（經 C20 下載）、公開 repo（agent 直接 git clone）、規則包（file 型經 C20、url 型由 cinc-auditor 自己去抓）。紅色：容器以 root 跑、沒有資源上限，子行程跑的是別人給的內容。"}
%%{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
```

## 二、這條線怎麼連

| 項目 | 現況 |
|---|---|
| **誰 → 誰** | `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](C20-agent-to-frontdoor.html) `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](C24-agent-to-profile-url.html)） |
| **何時存在** | 派 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` 邊寫邊計量）。

## 三、哪些功能會走這條線

| 群 | 功能 | 對威脅的意義 |
|---|---|---|
| **① 源碼分析** | 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 無上限無清理 | 磁碟是共用資源 |

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

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

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

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

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

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

| # | 防線 | 擋哪種威脅 | 目前做到哪 |
|---|---|---|---|
| D1 ◇ | **規則包的完整性有信任根**：file 型帶 sha256、對帳不符即 fail；url 型也要比對雲端下發的指紋 | T1 | ✅ file 型：`required=True`，缺 sha256 直接擋，對帳不符不靜默；解壓先到暫存目錄、完成才原子換上。⚠️ **url 型原樣交給 cinc-auditor 自己抓，不經 agent 下載器、不比對**（雲端已下發 sha256，agent 端沒用——詳 [C24](C24-agent-to-profile-url.html)） |
| 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），這一步決定「跑了惡意規則包」是拿到一個受限帳號還是整台容器。

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

- **整個 agent image 的加固**（D2）：root、無 cap_drop、無資源上限——六頁 STRIDE 零條，因為掃描看程式不看 Dockerfile 與 compose。
- **url 型規則包不對帳**（D1）：雲端那頭修了（下發 sha256），agent 端沒接——見 [C24](C24-agent-to-profile-url.html)。
- **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。*
