---
title: "第三方元件掃描搬到 Agent、弱點資料自動同步 — 需求討論稿 (FR-156)"
brand: "Guidant AI · **FR-156** 第三方元件掃描 Agent 端化"
eyebrow: "FR-156 · 需求討論稿 v1 · 2026-10-21（D1～D7 已於對話中拍板，本稿把定案展開並補 D8～D12 待裁）"
h1: "客戶程式碼不進主系統，弱點資料天天自己更新"
subtitle: "第三方元件掃描的執行位置從主系統沙箱搬到客戶機房的 Agent；弱點資料由我們每日發布、主系統自動抓、Agent 自動同步"
lede: "FR-134 做出來的「第三方元件風險」目前是顧問把客戶原始碼壓縮檔**上傳到主系統**，由主系統的沙箱跑掃描工具。PM 判定這不行：**掃公司程式碼的東西不能預設放在我們的主系統**。本案把掃描搬到客戶機房那台 Agent 上，原始碼**直接從瀏覽器傳到 Agent、不經主系統**；回主系統的只有元件清單與弱點結果。同時把現在要人手上傳的弱點資料改成**每日自動**：我們在 build 機排程產包蓋章、放到 Cloudflare，主系統每天自己抓，Agent 透過既有心跳通道跟著同步。FR-134 尚未進版、沒有客戶裝到伺服器端掃描版，現在改成本最低。"
chips: [
  {text: "D1～D7 對話中拍板", kind: ok},
  {text: "D8～D12 待裁", kind: warn},
  {text: "FR-134 未進版，改執行位置零客戶影響", kind: ok},
  {text: "跨 4 repo：BE／FE／jedi-python-package／evidence-agent", kind: accent},
  {text: "陷阱：工具 bundle 全 amd64，Agent 機要 x86_64", kind: crit},
  {text: "陷阱：每台 Agent 多 5GB 弱點資料", kind: crit}
]
footer: "FR-156 · 需求討論稿 · 2026-10-21 · 前作：[FR-134](../FR-134-2610-source-code-sbom-scan/) 第三方元件風險、[FR-039](../FR-039-2606-distributed-file-agent/) Agent、[FR-056](../FR-056-2607-detection-tool-integration/) 檢測工具 · 姊妹案：[FR-157](../FR-157-2610-agent-local-settings-console/) Agent 本機設定主控台 · 雛形：[prototype/index.html](prototype/index.html)"
---

> 狀態：**討論稿，等決策者審 D8～D12**｜日期：2026-10-21｜前作 FR-134（功能完成、未進版、休眠）
> 雛形：[`prototype/index.html`](./prototype/index.html)（四幕：精靈選 Agent、Agent 清單弱點資料欄、系統設定弱點資料頁、檢測工具任務頁）

---

## 1. 需求背景 {#background nav="背景"}

### 1.1 PM 的三個意見（2026-10-21）

| # | PM 說什麼 | 白話翻譯 | 本稿對應 |
|---|---|---|---|
| ① | 第三方套件掃描做在主系統不行，掃公司程式碼的東西不能預設放在我們主系統，要改到 Agent 端掃 | 客戶的原始碼不該離開客戶自己的機器 | §3 執行位置搬 Agent |
| ② | 掃描工具都不能自動更新；弱點更新太快，叫 user 自己更新庫或我們一直派人出去都不可能 | 弱點資料要自己每天更新，不靠人 | §4 弱點資料自動同步 |
| ③ | 掃描工具的設定（帳密）都在主系統，想改到 Agent 那台機器開 Web UI 設定、用主系統帳號登入、設定存 Agent 端 | 客戶機敏的連線帳密也不該放我們這 | 獨立成 **FR-157**，本稿不展開 |

### 1.2 現況哪裡痛

FR-134 的設計（2026-10-10 定案）把 Syft／Trivy／Grype／OSV 四支工具併進主系統的 `guidant-sandbox` 容器，顧問上傳的源碼包存進主系統儲存（SeaweedFS），worker 送沙箱掃。當時排除「做成 Agent 執行器」只有一個理由：**Agent 要帶 4.7GB 弱點資料且每台都要更新**。

這條理由被 PM 意見②反過來解掉：弱點資料本來就要有自動更新機制，有了機制，Agent 帶資料就不是障礙。

另外兩個現況事實讓搬家變便宜：

- **Agent 已經在跑同型工作**：FR-058.7 的 SonarQube 掃描就是「源碼包下載到 Agent 本機、安全解壓、跑 sonar-scanner、報告回雲端」。FR-134 搬過去是照先例做第七個執行器。
- **FR-039 的原始不變式**本來就寫「雲端任何路徑不得把 binary 寫進雲端磁碟」。FR-134 把源碼包存進主系統儲存，與這條精神相悖；PM 的意見與原始架構一致。

### 1.3 不做什麼（對話中已裁）

| 不做 | 原因 |
|---|---|
| Agent 程式本體自動升級、「版本過舊提示」 | 決策者裁「不做，先做弱點掃描庫自動更新」。工具版本換等於檢測行為換，應釘版隨 Agent 發版 |
| 保留主系統沙箱掃描當選項 | 兩套執行位置兩套弱點資料投遞，維護翻倍；沙箱 image 也不用再被出貨弱點閘掃三支工具 |
| Agent 直連網路抓弱點資料 | 守 FR-039「Agent 只連雲端」不變式，封閉網路客戶同一條路 |
| 磁碟用量優化（拿掉 Grype 等） | 決策者裁「先不管」；只做切換後刪舊版 |

---

## 2. 分工概述（30 秒版） {#overview nav="概述"}

整案一句話：**顧問在精靈多選一台 Agent，原始碼直接傳到那台機器上掃，結果照舊回主系統看；弱點資料我們每天自動發、主系統自動抓、Agent 自動跟上，沒有人需要按任何按鈕。**

| 子需求 | 做什麼 | 產出 | 完成怎麼判定（決策者檢查法） |
|---|---|---|---|
| **A 每日發布鏈** | 188 的 GitLab runner 每日排程：產弱點包 → License Center 蓋章 → 上傳 Cloudflare R2 → 更新 `latest.json` | CI 排程 pipeline、R2 bucket、公開下載網址 | 隔天早上開瀏覽器打 `https://<站台>/vulndb/latest.json`，日期是昨天；下載的包用產品驗章工具驗得過 |
| **B 主系統自動抓** | 既有「每日自動檢查更新」預設開、更新站預設指我們的 R2；抓到新包驗章、寫入 volume、留紀錄（沿用 FR-134 B3／B4） | 設定預設值調整、紀錄表多一種來源 | 一台連網的 DEV 什麼都不按，隔天系統設定頁「弱點資料日期」自己前進，紀錄多一列來源寫「自動下載」 |
| **C Agent 同步** | 心跳回應帶「雲端現有弱點資料日期＋sha256」；Agent 比對本機較舊就走控制面通道下載、驗章、原子切換、刪舊；Agent 清單每台顯示資料日期 | Agent 端 vulndb 同步模組、心跳契約擴充、清單欄位 | 主系統更新後五分鐘內，Agent 清單那台的「弱點資料」欄日期跟上；拔掉 Agent 機的外網線照樣跟上 |
| **D Agent 掃描執行器** | Agent 加 `sbom_scan` 執行器：取源碼包／SBOM 檔 → 安全解壓 → Syft → Trivy／Grype／OSV 離線 → 回傳 SBOM 多格式與三家原始 JSON；工具四支進 Agent 的 tools-manifest | Agent 執行器、tools-manifest 四列、能力宣告 `sbom_scan` | 派一筆掃描給 Agent，Agent 機上 `ps` 看得到 syft／trivy 在跑；主系統結果頁數字與 FR-134 伺服器端掃同一包的結果一致 |
| **E 源碼直傳 Agent** | 精靈「掃描來源」頁多一步選 Agent；瀏覽器直接 PUT 檔到那台 Agent 的資料面（8443）；主系統只收到檔案 uid 與 sha256；掃完源碼包留在 Agent 本機可設保留天數 | 精靈改版、Agent 資料面新端點、主系統契約 | 瀏覽器開發者工具看上傳請求目的地是 Agent 的 IP 不是主系統；主系統儲存裡找不到那個 zip |
| **F 收掉伺服器端路徑** | 沙箱 image 拔四支工具、`/v1/scan` 端點退役、`server_side_dispatch` 分岔收掉、檢測工具「SBOM 弱點掃描」改回一般 Agent 執行器（任務頁出現 Agent 指派欄） | 沙箱瘦身、派工分岔刪除、工具目錄列改值 | 沙箱容器裡 `which syft` 找不到；建檢測工具任務選「SBOM 弱點掃描」會出現 Agent 指派欄，與 OpenSCAP 一樣 |
| **G 收尾** | SPEC 三頁改寫、手冊「弱點資料怎麼更新」整段改寫成「不用做任何事」、安裝文件磁碟需求、FR-134 的 vulndb 設定頁保留但降為「狀態與紀錄」 | 文件 | 手冊找不到「請上傳更新包」這句當主要路徑 |

依賴：A → B → C 串行；D 可與 A 平行開工（開發期用手動放的弱點資料）；E 要 D；F 要 D＋E 驗過；G 最後。**先讓決策者看到畫面的順序**：E 的精靈改版與 C 的清單欄位是最先看得見的，雛形已含。

---

## 3. 執行位置搬 Agent {#relocate nav="搬 Agent"}

### 3.1 搬完之後的端到端

```{.mermaid cap="圖 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','actorBkg':'#E2F0F1','actorTextColor':'#14201F','actorBorder':'#0E7C86','actorLineColor':'#C7D1D2','signalColor':'#4A5A5C','signalTextColor':'#14201F','labelBoxBkgColor':'#EEF2F3','labelBoxBorderColor':'#C7D1D2','labelTextColor':'#14201F','loopTextColor':'#14201F','noteBkgColor':'#F6EBD5','noteTextColor':'#14201F','noteBorderColor':'#9C6B12','activationBkgColor':'#EEF2F3','activationBorderColor':'#C7D1D2','sequenceNumberColor':'#FFFFFF'}}}%%
sequenceDiagram
    participant U as 顧問（瀏覽器）
    participant API as 主系統 api
    participant AG as Agent（客戶機房）
    participant DB as 主系統 DB
    U->>API: 精靈：建掃描對象、選 Agent
    API->>DB: 建 source_scans（queued，agent_uid）
    API-->>U: 回「上傳票」（短效、綁這台 Agent）
    U->>AG: PUT 源碼包 到 Agent 8443（帶上傳票）
    AG->>AG: 落本機 /data/scan-input，算 sha256
    AG-->>U: 回 file_uid、sha256
    U->>API: 送 file_uid、sha256 → 開始掃描
    API->>DB: 建 agent_tasks（sbom_scan）
    AG->>API: 心跳 → 領走 pending_tasks
    AG->>AG: 安全解壓 → syft → trivy／grype／osv（離線，讀本機 vulndb）
    AG->>API: 回傳 SBOM 多格式＋三家原始 JSON（不含源碼）
    API->>API: 正規化：配對、KEV／EPSS、六區塊統計、NTIA
    API->>DB: 寫 components／findings／summary（done）
    U->>API: 看結果、匯出報表（與現在完全相同）
```

**守住的邊界**：主系統從頭到尾**沒有碰過原始碼**。回來的是 SBOM（套件名與版本清單）與弱點比對結果。D1 已裁這兩樣留雲端。

### 3.2 現況接入點盤點（可複用 vs 要新寫）

| 元件 | 現況 | 本案動作 |
|---|---|---|
| Agent 檢測執行器框架 | `evidence-agent/core/task_executor.py` ＋ `task_executor_connectors/` 六支（openvas／openscap／zap／inspec／nmap／sonarqube）；`sonarqube.py` 已有「下載源碼包 → 安全解壓 → 跑 CLI → 回報」整條 | **新增第七支 `sbom_scan.py`**，解壓與限額直接借 sonarqube 那段（D13 借檢查器不借流程的精神） |
| Agent 工具 bundle | `scripts/build/tools-manifest.txt` 釘版＋sha256，`fetch_bundle_tools.sh` 拉、啟動驗簽章；全 amd64 | **加 syft／trivy／grype／osv-scanner 四列**（版本與 FR-134 沙箱同：syft 1.54.0／trivy 0.75.0／grype 0.120.0），單檔靜態二進位、零系統依賴 |
| Agent 能力宣告 | `common/util/agent_capabilities.py` 兩個：`file_storage`、`detection_scan`，隨心跳自報 | **加 `sbom_scan`**；雲端派工資格條件多看這個 |
| Agent 資料面 | 8443 mTLS，端點 `/blob`（證據上傳）、`/detection/probe`、`/detection/cancel` | **加 `/scan-input`（PUT 源碼包、回 uid＋sha256）**；瀏覽器沒有客戶端憑證，見 §3.3 |
| Agent 心跳契約 | 回應帶 `pending_tasks`、`error_tracking`、`allowed_targets`（`core/enroll.py:322-335` 兩個 observer） | **加 `vulndb` observer**（§4.3） |
| 雲端正規化層 | `jedi_source_scan/app/service/normalize/`（trivy／grype／osv parser、merge、summary、ntia、risk_rating）吃三家原始 JSON | **原樣沿用**，輸入從「沙箱回傳」改成「Agent 回傳的檔」 |
| 雲端掃描 job | `jedi_source_scan/app/jobs/sbom_scan_job.py`：取檔 → 送沙箱 → 存產物 → 正規化 → 寫表 | **拆成兩半**：派工半（建 agent_task）＋結果半（收 Agent 回傳後正規化）。沙箱那步刪掉 |
| 雲端派工分岔 | `jedi_detection/app/service/server_side_dispatch.py`（`is_server_side` → 不派 Agent） | **退役**；「SBOM 弱點掃描」工具目錄列 `execution_mode` 改回一般 |
| 精靈 | FE `SourceScanWizard.vue` 三頁：基本資料／掃描來源／掃描設定 | **掃描來源頁加「選 Agent」**（有 `sbom_scan` 能力且在線的才列；只有一台自動選）；上傳目的地改 Agent |
| Agent 清單 | FE `RemoteAgentManage.vue` 欄位：名稱、位址、狀態、能力、版本 | **加「弱點資料」欄**（日期＋距今天數，30 天黃 90 天紅） |
| 沙箱 | `guidant-sandbox` image 含四支工具、`/v1/scan` | **拔掉**，回到只拆檔；守衛測試不變 |

### 3.3 瀏覽器怎麼直傳到 Agent（E 的關鍵問題）

Agent 的 8443 強制驗客戶端憑證（mTLS），瀏覽器沒有憑證，直接 PUT 會被擋。三個選項：

| 選項 | 做法 | 優 | 劣 |
|---|---|---|---|
| **a. 上傳票（建議）** | 主系統簽一張短效票（JWT，5 分鐘、綁 agent_uid＋scan_uid＋使用者），瀏覽器帶票 PUT 到 Agent **另一個單向 TLS 埠**（例 8444）；Agent 用雲端公鑰驗票（既有 `verify_data_plane_jwt` 路徑） | 源碼真的不經主系統；Agent 零新信任關係（公鑰註冊時就有）；FR-157 的登入也走同一套票 | 要開第二個埠；自簽憑證瀏覽器會警告（§3.4） |
| b. 經主系統串流 relay | 瀏覽器照舊傳主系統，主系統邊收邊轉給 Agent，不落磁碟 | 瀏覽器零改動、無新埠 | 原始碼仍「經過」我們的機器，PM 問起說不清；大檔 relay 吃 api 連線 |
| c. 8443 加白名單路徑免驗客戶端憑證 | 同一個埠，`/scan-input` 不要求 client cert | 不開新埠 | TLS 層的 client cert 驗證是整個 listener 的設定，Python ssl 做不到按路徑放行；要改成 `CERT_OPTIONAL` 等於全埠降級 |

::: {.callout .pending}
**D8 瀏覽器直傳 Agent 走哪條**

[**建議**：a 上傳票＋第二埠。守住「不經主系統」這句話，也是 FR-157 登入要用的同一把機制，做一次用兩次。]{.rec}
:::

### 3.4 自簽憑證與瀏覽器警告

Agent 的 TLS 憑證是我們自己的 CA 簽的（註冊時由雲端發），瀏覽器第一次連會跳「不安全」。處理方式：

- 安裝精靈結尾印出 CA 憑證檔路徑與一句「匯入瀏覽器信任可消除警告」；手冊同步。
- 精靈畫面在跳轉上傳前顯示一行提示：「檔案將直接傳到 Agent 機器 `192.168.x.x`，瀏覽器可能提示憑證警告，屬正常」。
- 顧問要在客戶內網才連得到 Agent。這本來就是「程式碼不離開客戶機」的必然結果，手冊寫明。

::: {.callout .pending}
**D9 自簽憑證警告第一版接受嗎**

[**建議**：接受，手冊與畫面提示處理。替代方案是客戶自備該機器的正式憑證掛進 Agent，列第二期選配。]{.rec}
:::

### 3.5 Agent 機的新前提

| 項目 | 數字 | 來源 |
|---|---|---|
| 弱點資料磁碟 | **約 5GB**（4.7GB 常駐＋切換時短暫並存） | DEV 實測：grype 3.0G／trivy 1.5G／osv 287M／kev＋epss 4M |
| 四支工具二進位 | 約 250MB | 單檔靜態 binary |
| 掃描工作區 | 源碼包 100MB × 12 係數＝約 1.2GB 暫用 | 沿用 FR-058.7 磁碟預留公式 |
| 記憶體 | Grype 比對大 SBOM 峰值約 2～3GB | FR-134 D12 沙箱 4g 的依據 |
| CPU 架構 | **x86_64 only** | 工具 bundle 全 amd64（既有限制，不新增） |

安裝文件的磁碟需求從現行數字加 7GB。

---

## 4. 弱點資料自動同步 {#vulndb nav="弱點資料"}

### 4.1 三段式供應鏈

```{.mermaid cap="圖 2 — 弱點資料供應鏈：我們每日發布 → 主系統自動抓 → Agent 跟著同步"}
%%{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','actorBkg':'#E2F0F1','actorTextColor':'#14201F','actorBorder':'#0E7C86','actorLineColor':'#C7D1D2','signalColor':'#4A5A5C','signalTextColor':'#14201F','labelBoxBkgColor':'#EEF2F3','labelBoxBorderColor':'#C7D1D2','labelTextColor':'#14201F','loopTextColor':'#14201F','noteBkgColor':'#F6EBD5','noteTextColor':'#14201F','noteBorderColor':'#9C6B12','activationBkgColor':'#EEF2F3','activationBorderColor':'#C7D1D2','sequenceNumberColor':'#FFFFFF'}}}%%
flowchart LR
  subgraph JT["我們（每日 03:00，GitLab 排程 → 188 runner）"]
    UP["上游公開資料<br/>Trivy DB／Grype DB／OSV／KEV／EPSS"] --> BUILD["build_vulndb_bundle.sh<br/>（既有）"]
    BUILD --> LC["License Center 蓋章<br/>（既有 vulndb_manifest 型）"]
    LC --> R2["Cloudflare R2<br/>guidant-vulndb-YYYYMMDD.tar.zst<br/>latest.json"]
  end
  subgraph CUST["客戶（連網）"]
    R2 -->|"每日 GET latest.json<br/>較新才下載"| API["主系統 api／worker<br/>驗章 → volume → 紀錄"]
    API -->|"心跳回應：date＋sha256"| AG1["Agent A"]
    API -->|"心跳回應"| AG2["Agent B"]
    AG1 -->|"GET /agents/vulndb/bundle<br/>（控制面通道）"| API
  end
  subgraph CLOSED["客戶（封閉網路）"]
    ADMIN["管理員上傳更新包<br/>（既有設定頁）"] --> API2["主系統"]
    API2 -->|同一條心跳機制| AG3["Agent C"]
  end
```

### 4.2 A 段：每日發布鏈（我們這邊的營運承諾）

| 項目 | 定案／建議 |
|---|---|
| 排程在哪跑 | **GitLab CI 排程 pipeline，tag `guidantai-build`（188 runner）**。既有 `.gitlab-ci.yml` 已是「GitLab → 188 執行」模型，加一條 `schedule` 觸發的 job；Cloudflare 自己做不到（要抓數 GB 上游並跑 docker） |
| 產包 | 既有 `scripts/build/build_vulndb_bundle.sh`，零改 |
| 簽章 | **License Center 既有 `vulndb_manifest` 型**（決策者裁「有的話就從 188 跑」）；LC 在 188 本機，產包腳本已接 |
| 存放與流量 | **Cloudflare R2**，走 Cloudflare 網路對外，出站流量不計費；與官網同帳號 |
| 契約 | `latest.json`：`{bundle_date, file, sha256, size_bytes, sources{...}, tool_versions{...}}`；包本體 `guidant-vulndb-<date>.tar.zst`；保留最近 30 天 |
| 失敗怎麼知道 | pipeline 紅 → 既有通知；連三天沒新包 → 主系統設定頭「更新站無新資料」黃字 |
| 時間 | 每日 03:00（台北）；上游 Trivy／Grype 約 UTC 每 6 小時更新，03:00 抓到的是前一天完整資料 |

::: {.callout .pending}
**D10 R2 bucket 與公開網址**

[**建議**：bucket `guidant-vulndb`，自訂網域 `vulndb.jedicotech.com`（Cloudflare 同帳號一鍵綁）；主系統 `VULNDB_UPDATE_URL` 預設值改成這個網址（現況預設空字串＝不連網）。正式網域名由決策者定。]{.rec}
:::

### 4.3 B 段：主系統自動抓

FR-134 B3／B4 已經做好：`VulndbCheckUpdateRoute`（向更新站檢查）、`VulndbAutoUpdateRoute`（每日自動開關）、驗章、日期不倒退、原子切換、`vulndb_updates` 紀錄表。本案只改三件事：

1. `VULNDB_UPDATE_URL` 預設值改成我們的站（D10）。
2. 「每日自動檢查更新」**預設開**（現況預設關）；封閉網路客戶連不到站台時紀錄一列失敗、設定頁黃字提示改用上傳，**不彈錯誤**。
3. 切換到新版後**刪舊版目錄**（現況保留；D7 裁「先不管」磁碟，但這一步零成本）。

### 4.4 C 段：Agent 同步

| 項目 | 設計 |
|---|---|
| 怎麼知道有新的 | 心跳回應 `data` 多一個 `vulndb: {bundle_date, sha256, size_bytes}`（雲端現行那份）。Agent `core/enroll.py` 加第三個 observer `_observe_vulndb`，比對本機 `current/manifest.json` 的 `bundle_date` |
| 怎麼拿 | 新控制面端點 `GET /api/1.0/agents/vulndb/bundle`（通行證驗身、串流回包）；Agent 走既有 `_get_stream`（FR-059.3 profile 投遞同款，含 sha256 對帳） |
| 怎麼放 | Agent 本機 `/data/vulndb/<date>/`，**驗章**（Agent 已帶出貨公鑰表 `common/integrity/public_keys.py`，加認 `vulndb_manifest` 型）→ 逐檔 sha256 → 原子切 `current` symlink → 刪舊 |
| 下載中掃描怎麼辦 | 掃描讀 `current` symlink 解析後的實路徑，切換是原子 rename，進行中的掃描繼續用舊版到結束 |
| 多台 Agent 同時抓 | 每台各自抓一次（4.7GB 解開、約 480MB 壓縮）；同租戶多台就是 N × 480MB 內網流量，一天一次可接受 |
| Agent 回報 | 心跳 payload 多 `vulndb_date`；雲端 `remote_agents` 加一欄 `vulndb_date`；清單顯示 |
| 掃描時的日期 | `source_scans.vulndb_date` 改記**Agent 回報的那次資料日期**（Agent 結果 payload 帶），結果頁與報表照舊顯示 |
| 沒有弱點資料的 Agent | 能力仍宣告 `sbom_scan`，但派工資格多一條「`vulndb_date` 非空」；精靈不列它，並在 Agent 清單標「弱點資料尚未同步」 |

::: {.callout .pending}
**D11 Agent 弱點資料從主系統拉還是主系統推**

[**建議**：拉（心跳得知 → Agent 自己 GET）。Agent 永遠是發起方，與 FR-039／059.3 一致；推要雲端主動連 Agent 資料面、要處理 Agent 離線重試，多一套狀態機。]{.rec}
:::

### 4.5 FR-134 的「弱點資料」設定頁怎麼變

保留，但角色從「主要操作入口」變成「狀態與紀錄」：日期、來源五項、更新紀錄照舊；「上傳更新包」按鈕保留給封閉網路客戶；「檢查更新」保留當手動觸發；新增一塊 **「Agent 同步狀態」**（每台 Agent 的資料日期、最後同步時間），雛形第三幕。

---

## 5. 檢測工具入口怎麼變 {#tool-entry nav="工具入口"}

FR-134 H 群把「SBOM 弱點掃描」做成工具目錄裡唯一的「伺服器端執行」工具，任務頁刻意不出現 Agent 指派欄。搬家後它變回一般 Agent 執行器：

| 現況（FR-134 H） | 本案 |
|---|---|
| `config.detection_tools` 該列 `execution_mode=server` | 改回一般 |
| 建任務選它不出現 Agent 指派欄 | **出現**，與 OpenSCAP／SonarQube 一樣（FR-067 多 Agent 派工照用） |
| `start_execution` 分岔到 `server_side_dispatch` | 分岔刪除，走一般派工 |
| 源碼包隨任務執行時上傳到主系統儲存（FR-058.7 `_source_file`） | **上傳改直傳 Agent**（§3.3 同一條票），`_source_file` 改帶 Agent 本機 uid；FR-058.7 的 SonarQube 掃描**順便一起改**，不然同一個任務頁兩種工具兩種上傳目的地 |
| 結果經 `DetectionResultForwarder` 轉任務證據 | 原樣 |

::: {.callout .pending}
**D12 FR-058.7 SonarQube 的源碼上傳要不要一起改成直傳 Agent**

[**建議**：一起改。同一條「客戶程式碼不進主系統」的理由對 SonarQube 掃描完全成立，PM 的意見①字面就是「第三方套件掃描」但精神涵蓋所有掃源碼的工具。不改的話主系統儲存裡仍會躺著客戶源碼包，PM 問起要解釋兩套規則。成本是精靈外多一處上傳元件改目的地。]{.rec}
:::

---

## 6. 決策彙整 {#decisions nav="決策"}

### 6.1 對話中已拍板（2026-10-21）

| # | 問題 | 定案 | 理由 |
|---|---|---|---|
| **D1** | SBOM 與弱點結果留雲端？ | **留** | 不留就沒有報表、歷次比對、任務證據 |
| **D2** | 主系統沙箱掃描保留當選項？ | **拔掉** | 兩套執行位置維護翻倍；沙箱不再被出貨弱點閘掃工具 |
| **D3** | 弱點資料雲端中繼還是 Agent 直連？ | **雲端中繼** | 守 FR-039 不變式，封閉網路同一條路 |
| **D4** | Agent 程式自動升級／過舊提示？ | **都不做** | 先做弱點掃描庫自動更新 |
| **D5** | 憑證搬 Agent、多台各設一次？ | **可做；一個網段通常一台，或資料台與掃描台分開，各設正常** | 併入 FR-157 |
| **D6** | 產包／簽章／發布在哪 | **188 runner 排程產包、LC 蓋章、Cloudflare R2 發布** | CI 跑的地方就是 188，LC 已接；R2 零出站費 |
| **D7** | 每台 Agent 多 5GB？ | **先不管** | 只做切換後刪舊 |

### 6.2 本稿待裁

| # | 問題 | 建議 |
|---|---|---|
| **D8** | 瀏覽器直傳 Agent 走哪條 | a 上傳票＋第二埠 |
| **D9** | 自簽憑證警告第一版接受？ | 接受，手冊與畫面提示 |
| **D10** | R2 bucket 與公開網域 | `vulndb.jedicotech.com`，預設寫進 config |
| **D11** | Agent 弱點資料拉還是推 | 拉 |
| **D12** | FR-058.7 SonarQube 上傳一起改直傳？ | 一起改 |

---

## 7. 拆棒草案（design.md 定稿後開卡） {#breakdown nav="拆棒"}

| 批 | 卡 | 改哪個 repo | 依賴 |
|---|---|---|---|
| 1 | A1 GitLab 每日排程 pipeline：產包→LC 簽→上傳 R2→更新 latest.json | BE `.gitlab-ci.yml`、`scripts/build/` | — |
| 1 | D1 Agent 工具 bundle 加四支工具、能力宣告 `sbom_scan` | evidence-agent | — |
| 1 | D2 Agent `sbom_scan` 執行器（借 sonarqube 的下載解壓段） | evidence-agent | D1 |
| 2 | B1 主系統：更新站預設值、自動更新預設開、切換後刪舊、連不到站的黃字 | BE、jedi-source-scan、FE | A1 |
| 2 | C1 心跳契約加 `vulndb`、控制面 bundle 端點、`remote_agents.vulndb_date`、清單欄 | jedi-remote-agent、jedi-source-scan、FE | B1 |
| 2 | C2 Agent 端 vulndb 同步模組（observer、下載、驗章、原子切換、刪舊、心跳回報） | evidence-agent | C1 |
| 3 | E1 上傳票簽發與 Agent 第二埠＋`/scan-input` | BE、jedi-remote-agent、evidence-agent | D2 |
| 3 | E2 精靈改版：選 Agent、直傳、憑證提示；掃描 job 拆派工半／結果半 | FE、jedi-source-scan | E1 |
| 4 | F1 收掉伺服器端：沙箱拔工具、`/v1/scan` 退役、`server_side_dispatch` 刪、工具目錄列改值、任務頁 Agent 欄 | jedi-evidence-classification、jedi-detection、jedi-source-scan、FE、migration | E2 |
| 4 | F2 FR-058.7 SonarQube 源碼上傳改直傳 Agent（D12 裁了才開） | FE、jedi-detection、evidence-agent | E1 |
| 5 | G1 SPEC／手冊／安裝文件 | BE docs | 全部 |

---

## 8. 風險與陷阱 {#risks nav="風險"}

| 風險 | 怎麼擋 |
|---|---|
| Agent 機磁碟不夠，同步到一半爆 | 下載前 `shutil.disk_usage` 檢查（FR-058.7 既有公式），不夠就心跳回報「磁碟不足」、清單紅字，不嘗試下載 |
| 弱點資料驗章失敗（公鑰表沒有簽章用的那把鑰） | FR-134 已踩：出貨公鑰表只認 189 正式鑰，DEV 開發鑰簽的包會被拒。Agent 端公鑰表要與產品端同一份；CI 一律用正式鑰簽 |
| 排程 pipeline 與人手跑的 build 在 188 撞 | 既有 `pgrep` 守門沿用；弱點包產製不碰 `.venv`／Nuitka，撞的機率低；排 03:00 避開人 |
| Trivy Java 專案要 `trivy-java-db` | FR-134 已註記：產品走 syft 產 SBOM 再 trivy 掃 SBOM，java-db 用不到；維持不包 |
| 上傳票被截走拿去傳別的檔 | 票綁 `scan_uid`＋`agent_uid`＋5 分鐘，一票一用（Agent 記 jti）；檔 sha256 由主系統另外收一份，與 Agent 回報比對 |
| Agent 回傳的 SBOM 不含源碼，但 Syft 的 SBOM 會帶檔案路徑 | 正規化層已處理（FR-134 元件清單攤原檔）；路徑是相對路徑、不含內容，PM 可接受範圍要在 design 寫明 |
| FR-134 的 `source_scans.vulndb_date` 現在記主系統那份日期 | 改記 Agent 回報的；舊資料為空的列照舊顯示 |

---

## 9. 這份文件的定位 {#positioning nav="定位"}

| 你想知道 | 看 |
|---|---|
| PM 說了什麼、對話裡裁了什麼 | 本稿 §1、§6.1 |
| 搬家後畫面長什麼樣 | [雛形](prototype/index.html) |
| FR-134 原本怎麼設計、為什麼當時不派 Agent | [FR-134 design.md](../FR-134-2610-source-code-sbom-scan/design.md) D4／D9 |
| Agent 怎麼跑檢測、工具怎麼打包 | [FR-056](../FR-056-2607-detection-tool-integration/)、[FR-066](../FR-066-2608-agent-native-installer/) |
| 檢測 profile 怎麼走控制面投遞（本案弱點資料同款） | evidence-agent `core/profile_cache.py` 檔頭 |
| 帳密搬 Agent 端的 Web UI | [FR-157](../FR-157-2610-agent-local-settings-console/) |
