FR-134 · 需求討論稿 · 2026-10-10
上傳源碼包或現成 SBOM,既有沙箱裡跑 Syft/Trivy/Grype,產出白話報表
顧問輔導客戶時,專案弱點本該由客戶工程師盤點回報,實務上常被一句「那個不嚴重」帶過。本案讓顧問不靠工程師就能拿到一份有工具原始輸出背書的弱點報表:產出 SBOM(軟體物料清單,列出程式用了哪些第三方套件與版本)、多工具比對弱點、事實統計(不替任何人下結論)、歷次比對、可交付的 PDF/DOCX。工具一律包現成的,功夫花在沙箱隔離、離線弱點資料、把三家工具的輸出攤在同一列。第一版只收上傳(源碼壓縮檔與 SBOM 檔),掃描工具併進既有沙箱、容器數不變;Git 來源列第二版。本稿盤點現況、畫出架構,拆成八個階段,記錄 14 項決策定案。
狀態:討論稿定稿(14 項定案,見 design.md) | 日期:2026-10-10 | 白話需求草稿:draft-requirement.md
整案一句話:顧問把客戶的原始碼壓縮檔或現成的 SBOM 上傳進系統,十幾分鐘後拿到一份看得懂、工程師賴不掉的弱點報表;三個月後再掃一次,修了沒一眼看到。
給非工程讀者的背景:現代程式九成以上是拼裝現成的第三方套件。套件被發現有安全漏洞(弱點)時,用到它的程式就跟著有風險。SBOM 是一份「這支程式用了哪些套件、哪個版本」的清單;拿這份清單去比對公開的弱點資料庫,就知道哪裡有洞、多嚴重、有沒有修正版。市面上的工具都會做這件事,但產出全是給工程師看的代號與分數。本案補的是「給顧問看的那一層」。
八個階段:A 先做,B 與 C 可平行,C 之後 D/E/H 三線平行
A 把掃描工具裝進既有沙箱(不新增容器);B(弱點資料更新包)與 C(後端核心)可同時開工;D(畫面)、E(報表)、H(檢測工具清單多一個工具)都接在 C 之後、彼此平行;F(歷次比對)要 C 與 D;G 收尾文件等全部完成。獨立模組與檢測工具兩個入口同一版交付。
| 階段 | 做什麼 | 產出 | 完成怎麼判定(決策者檢查法) |
|---|---|---|---|
| A 沙箱擴充 | 掃描工具 Syft、Trivy、Grype 裝進既有的檔案沙箱(不新增容器);加一個放弱點資料的儲存區;沙箱的加固設定抽成共用樣板 | sandbox image 加工具、vulndb volume、compose 樣板、守衛測試擴充 | 進沙箱跑 syft version、trivy --version、grype version 三支都有;在沙箱裡 curl https://github.com 會失敗;守衛測試全綠 |
| B 弱點資料更新 | build 機產出一包有簽章的「弱點資料更新包」;安裝包隨帶一份;封閉網路手動匯入,連網客戶在設定頁按「檢查更新」或排程自動下載同一份包 | 更新包、簽章驗證、匯入指令 guidantai vulndb import、設定頁檢查更新、升級流程 |
在拔網路線的機器上匯入更新包 → 系統設定頁顯示「弱點資料日期:YYYY-MM-DD」→ 掃一個已知有洞的範例專案,弱點數不是零;把更新包改一個位元組再匯入 → 被拒 |
| C 掃描後端 | 資料表、背景工作、兩種輸入路徑(源碼包、SBOM 檔)、把三家工具的結果整理成同一套格式、SBOM 多格式多版本輸出、權限與授權、API | migration、job handler、正規化層、API | 用 API 送一個範例源碼包、一份 SBOM 檔,兩筆都跑完,查得到元件數、弱點數、事實儀表板六區塊的數字 |
| D 結果與操作畫面 | 掃描對象清單、新增精靈三頁、結果頁三層分頁、四段白話進度 | 前端頁面 | 不看任何文件,照畫面新增一筆掃描,等進度走完四段,點進結果頁看得到事實儀表板六區塊統計,弱點清單表頭顯示目前的排序規則;畫面與競品無可辨識的版面雷同 |
| E 報表與 AI 分析 | 三種樣式(顧問摘要/完整技術/客戶交付)× 三種格式(PDF/HTML/DOCX);報告頁「AI 分析」按鈕接 FR-122 AI 閘道 | 報表產生器、一個提示樣板 | 匯出九種組合各一份,PDF 中文不缺字;任挑一個弱點,看得到哪個工具報的、NVD 連結、修到哪版;按「AI 分析」才出現一段標示「AI 生成」的文字,AI 呼叫紀錄多一筆 |
| F 歷次比對 | 同一對象兩次掃描比:修掉/新增/仍在 | 比對頁、回訪報告 | 對同一範例專案升一個套件版本再掃,比對頁「修掉」欄出現那個弱點 |
| G 收尾 | SPEC 頁、使用手冊、安裝文件、資安規則檔補路徑 | 文件 | 手冊找得到「怎麼新增掃描、怎麼更新弱點資料、每個數字的資料來源」三件事 |
| H 檢測工具入口 | 檢測工具清單裡多一個工具「SBOM 弱點掃描」,與 OpenSCAP、SonarQube 並列;整條鏈都在:建任務時可選 → 任務頁執行 → 結果進檢測紀錄 → 轉證據。這個工具不派遠端 Agent,在我們自己的伺服器上跑,使用者不必選執行位置 | 工具目錄一列、伺服器端執行路徑、任務頁共用 | 專案規劃建檢測工具任務時,下拉能選「SBOM 弱點掃描」,而且不出現 Agent 指派欄;任務啟動或按執行後,不需任何 Agent 在線,執行列表出現一筆並跑完;結果檔出現在任務證據清單;點「看完整結果」進獨立模組結果頁,數字一致 |
依賴:A(沙箱擴充)→ B、C 平行;C 之後 D、E、H 平行;D 之後 F;B、D、E、F、H 全部完成後 G 收尾。
顧問沒有技術背景,問工程師「有沒有已知弱點」只能得到「都有在更新」。本案要讓顧問自己回答四個問題,而且答案有工具原始輸出撐腰:
| 輸入 | 誰會用 | 走哪條路 | 版次 |
|---|---|---|---|
| 上傳源碼壓縮檔(zip/tar.gz,100MB 內) | 客戶給了一包程式碼 | 沙箱內安全解壓 → Syft 產 SBOM → 比對弱點 | 第一版 |
| 上傳現成 SBOM 檔(CycloneDX/SPDX,JSON 或 XML,50MB 內) | 工程師或其他工具(Snyk、Dependency-Track、我們自己的出貨管線)交出來的清單 | 跳過 Syft,沙箱內驗格式 → Trivy、Grype 直接吃 SBOM 比對弱點 | 第一版 |
| 填 Git 位址(可含私有倉庫帳密) | 顧問拿得到 repo 讀取權限 | 抓檔容器 clone → 交給沙箱 → 同第一種 | 第二版(見「未來項」節) |
SBOM 檔輸入的價值:顧問可以驗工程師交來的 SBOM——品質檢查(是否齊備 NTIA 最低要素,即美國商務部訂的 SBOM 七個必填項)對外來 SBOM 尤其有意義,「你交的清單連版本都沒寫齊」本身就是一個發現。兩條路在「正規化層」匯合,後面的結果頁與報表完全共用;第二版加 Git 時是在最前面多一段「抓檔」,正規化層之後不動。
工具本身(Syft、Trivy、Grype)是業界標準,不自己造。差異化全在呈現層:
UI 設計約束:自行設計,不仿照競品
畫面版面、分頁結構、欄位命名、用語一律自行設計,依 Guidant 既有設計系統(guidant-design-system skill、PrimeVue 元件)與本文的顧問情境出發。不得仿照 ITRI SecureScan 或任何競品的版面與結構,競品截圖不進前端派工卡。未實作的功能不在畫面上預告或留灰色入口(例如 Git 來源在做出來之前,精靈、清單、設定頁都不出現任何 Git 字樣)。
UI 不要直接抄襲,會沒辦法對外交代。 — 決策者原話(2026-10-10)
主要入口在側選單「原始碼掃描」(以下五步都在這裡)。另一個入口是檢測工具清單裡多一個工具「SBOM 弱點掃描」,與 OpenSCAP、SonarQube 並列:專案規劃建檢測工具任務時從下拉選它(不出現 Agent 指派欄)→ 任務頁按執行 → 結果進該任務的檢測紀錄 → 轉成證據;不建掃描對象,「看完整結果」連到本模組的結果頁(D9)。整個流程沒有工程師的位置——顧問自給自足是賣點;三個月後回訪時,在同一個掃描對象下上傳新的壓縮檔即可重掃。
%%{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 TD
M["側選單:原始碼掃描"] --> L["① 掃描對象清單"]
L -->|"新增"| W1["② 精靈頁 1:這是什麼"]
W1 --> W2["頁 2:程式在哪<br/>源碼包/SBOM 檔"]
W2 --> W3["頁 3:怎麼掃<br/>標準掃描,進階可勾工具與格式"]
W3 --> P["清單上顯示四段進度<br/>接收檔案→產出物料清單→比對弱點→整理報告"]
L -->|"重新掃描"| P
P --> R["③ 結果頁<br/>顧問層/對照層/原始層"]
L -->|"看結果"| R
R -->|"匯出"| X["報表:樣式 × 格式"]
L -->|"歷次比對"| H["④ 選兩次比:修掉/新增/仍在"]
H -->|"匯出回訪報告"| X
S["⑤ 設定頁(管理員)<br/>弱點資料・資源上限"] -.->|"弱點資料日期與上限"| W3
每一列是一個「掃描對象」(一套被掃的系統),跨稽核專案重用。
| 欄位 | 說明 |
|---|---|
| 對象名稱 | 顧問自取,如「客戶 X 內部請假系統」 |
| 客戶/備註 | 自由文字 |
| 來源 | 源碼包/SBOM 檔 |
| 最近掃描日 | 最後一次完成的日期 |
| 重點數字 | KEV 目錄內幾個、High 以上幾個(Trivy/Grype 取較多者並註明來源)——只放事實,不放燈號(D6) |
| 趨勢 | 與上一次比:修掉幾個、新增幾個 |
按鈕:新增、重新掃描、看結果、歷次比對。
頁 1:這是什麼——名稱、客戶備註、聯絡工程師(選填,只是備忘,系統不會聯絡他)。
頁 2:程式在哪(兩選一)
package-lock.json、poetry.lock 這類鎖定檔,沒有的話版本會不準——請工程師提供 lock 檔,或改用下方『上傳 SBOM 檔』」。頁 3:怎麼掃——預設「標準掃描」(Syft 產清單,Trivy+Grype 比對弱點,CycloneDX 與 SPDX 都產)。「進階」摺疊區可勾選要跑的弱點工具,以及要另外產出的 SBOM 格式與版本(CycloneDX 1.4~1.7、SPDX 2.2/2.3,兩種格式同時列出)。
送出後回到清單,該列顯示四段白話進度,對應背景工作的進度回報(ctx.report_progress):
接收檔案 → 產出物料清單 → 比對弱點 → 整理報告
第一段是「檢查並解開上傳的檔案」。上傳 SBOM 檔時第二段顯示「已提供,略過」。
六個區塊,只放事實、不下結論(D6):
另有授權風險摘要(用到幾個傳染性授權的套件,如 GPL)。右上角「弱點資料日期」,超過 30 天黃色、90 天紅色。
每個弱點一列,排序可選(預設 KEV → EPSS → CVSS,規則顯示在表頭):
| 套件/版本 | Trivy | Grype | KEV/EPSS | 修正版 | 白話說明 |
|---|
兩家說法不同時,該列標「工具意見不一致」,點開看各自理由(D7)。
原始層:下載 SBOM(選 CycloneDX 或 SPDX、選版本)、下載各工具原始 JSON、工具版本與每份檔的 sha256 指紋。
匯出報表:選樣式(顧問摘要/完整技術/客戶交付)× 格式(PDF/HTML/DOCX)。
選同一對象的任意兩次掃描,三欄並排:修掉/新增/仍在。可匯出「回訪報告」。
工程師不在流程裡;進度用白話四段
顧問自給自足是本功能的賣點:上傳就能出報表,不必等工程師配合。進度不顯示「job running」這類字樣,一律四段白話,讓顧問知道現在卡在哪一步。
| 元件 | 座標 | 現況 | 可複用 | 缺口 |
|---|---|---|---|---|
| 沙箱容器 | docker/production/docker-compose.yml:580-622 |
guidant-sandbox:唯讀、去全部權限、禁提權、tmpfs /tmp 2g、mem 2g/cpus 2/pids 256;身分靠 image 的 USER classifier,compose 未寫 user: |
掃描工具直接裝進這顆(D1);加固形狀抽成樣板 | 寫死在單一服務內,沒有樣板;記憶體 2g 不夠掃描(D12) |
| 沙箱網段 | docker-compose.yml:743-749 |
guidant-sandbox/guidant-sandbox-errors 皆 internal: true |
原樣沿用,掃描不需要出網 | — |
| 沙箱呼叫設定 | docker-compose.yml:146-150、config/config.py:198-204、core/plugins/ai_gateway.py:185-214 |
SANDBOX_MODE、CLASSIFIER_SERVICE_URL 在共用環境變數;行程內模式只准 DEV |
模式判斷與 DEV 限制 | — |
| 沙箱客戶端 | jedi_ai_gateway/infra/extract/sandbox_client.py:36-72 |
只有 .extract(job_uid, files, options),POST /v1/extract |
共享密鑰、逾時處理的寫法 | 只為拆檔設計,無通用執行介面,要加掃描方法 |
| 沙箱服務 | jedi-evidence-classification/docker/classifier_service.py |
stdlib http.server,/healthz、/v1/extract |
服務骨架,加一支 /v1/scan |
掃描端點要新寫 |
| 守衛測試(容器) | test/security/code/test_security_shape_container_hardening.py:17,28-38 |
四個容器名寫死;只守 cap_drop/no-new-privileges/mem/pids | 斷言寫法 | 不守 read_only、tmpfs、cpus、網段、internal;未來新沙箱不會自動被納入 |
| 守衛測試(外呼) | test/security/code/test_security_shape_external_calls.py:159-160 |
登記 SandboxClient._remote::requests.post |
登記格式 | 新增的掃描呼叫要登記 |
| installer 服務名 | scripts/installer/install.sh:1209-1211,2144,2149,2548-2561、scripts/installer/guidantai:300,843、docker/production/.env.example:121-126 |
寫死 guidant-sandbox |
容器不增,服務名不必改 | 要加 vulndb volume 與更新包匯入 |
| 沙箱 image build | scripts/build/build_classifier_image.sh;寫死處 sbom.sh:101、vuln_gate.py:19、build_all.sh:191、build_bundle.sh:93-108、push_harbor.sh:33,46 |
不走 Nuitka 編譯 | 在這支腳本加裝三支工具 | image 變大(三支工具本身,弱點資料不進 image);出貨弱點掃描會一併掃到這三支工具 |
| 出貨 SBOM 管線 | scripts/build/sbom.sh:44-104,工具版本見 scripts/build/README.md:354 |
syft 1.54.0/trivy 0.75.0/grype 0.120.0 裝在 build 機;--source 複製 lock 檔到暫存目錄跑 syft dir: |
指令組合直接搬(lock 檔掃法、Trivy/Grype 參數) | 只服務出貨,產品零處呼叫 |
| 弱點判定 | scripts/build/vuln_gate.py:84,121-134 |
三工具任一白名單外 Critical 即擋;relatedVulnerabilities 收別名 |
別名收集(GHSA↔︎CVE)可借做跨工具配對 | 判定邏輯是「擋不擋」,不是「多危險」 |
| 離線弱點資料 | — | 每次掃描自動線上更新;Grype DB 約 3GB 在 ~/.cache/grype/db |
— | 完全沒有離線機制 |
| 安裝包 | scripts/build/build_bundle.sh:8-27,258-297,369-385 |
manifest 只對 images/*.tar 算 sha256;storage-seed 升級每次重灌 |
storage-seed 的「非 image 資料隨包」做法;ai_gateway_rules.sh:1-30 的出廠預設與客戶修改分目錄 |
無「資料可單獨更新」先例 |
| 背景工作 | infra/background_job/model/background_job.py:7-26、app/background_job/service/background_job_app_service.py:55、app/background_job/handler_registry.py:25 |
四態;enqueue(job_type, payload, max_attempts=3);payload 不可放金鑰;租約 300 秒 |
整套直接用;範本 core/plugins/_evidence_classification_job_queue.py:19-33 |
沒有對外查工作狀態的 API |
| 上傳限額與檢查 | config/config.py:212-219、jedi_detection/common/detection_source_file.py:48-101、detection_profile_archive.py:99、archive_limits.py:51-99 |
100MB/解壓後 500MB/5 萬檔;副檔名白名單;zip-slip、符號連結、檔頭、壓縮炸彈檢查 | 限額與檢查器直接用 | 後端沒有「安全解壓到磁碟」函式(解壓都在 agent 端) |
| 檔案儲存 | jedi-file-upload ports.py:54、app/upload_file/service/managed_file_upload_service.py:35 |
save_file/get_file |
原檔、原始輸出、報表都存這 | — |
| 祕密加密 | infra/cloud_integration/crypto/fernet_crypto.py:6、config/config.py:187-195、jedi_detection/common/detection_secret_params.py:62-130 |
三把 Fernet 金鑰;偵測域的 envelope 寫法 {"__enc__":"fernet","value":...} |
第二版 Git 私有倉庫憑證用 | 第一版不需要 |
| PDF 報表 | api/project_summary_report/routes/project_summary_report_route.py:163-182 |
WeasyPrint、bleach 消毒、只放行 data URI(防伺服器端請求偽造)、Noto Sans CJK TC | 整條產生方式 | 沒有共用的品牌頁首頁尾 |
| DOCX 報表 | app/oscal/service/export/ssp_docx_generator.py:46、ssp_libreoffice_converter.py:57 |
docxtpl+python-docx;LibreOffice 轉 PDF | 範本填值方式 | — |
| 差異比對 | app/oscal/service/import_diff/ssp_diff_service.py:44 |
unchanged/changed/added/gone,以 key 配對 | 四態比對骨架 | 偵測結果沒有逐筆指紋、沒有跨次比對 |
| 新模組接線 | config/app_modules.py、di_containers/containers.py:145-146 |
四層樣板 commit 6a35e2cf3;薄樣板 commit 98cc3c934 |
樣板 | — |
| 能力點 | core/host_capabilities.py、scripts/deliverables/gen_permission_matrix.py、範例 scripts/sql/2026-10-05-fr129-2-tenant-scan-zones.sql |
<模組>.<資源>.<動詞>;加值包必須整包落在單一模組 |
整套 checklist | — |
| 授權照 | common/authz/license.py:107-125,352 |
require_license;基礎設施豁免清單 |
守門 | 歸既有檢測工具包(D8) |
| RLS | FR-129.2 migration :21-60;守衛 test/security/database/test_rls_all_tenant_tables.py |
前綴比對+超管放行,FOR ALL 一條規則涵蓋增查改刪 | 照抄形狀 | — |
| 偵測結果表 | scripts/sql/2026-07-26-fr056-4-detection-executions.sql |
detection_executions 綁任務(job_execution_uid NOT NULL)、summary JSONB、report_file_id、agent_task_uid |
檢測工具入口的結果落點(D9、階段 H);伺服器端執行時 agent_task_uid 留空 |
— |
| 檢測工具目錄 | config.detection_tools;清單 GET /detection-tools(jedi_detection/api/routing.py:69)只列 status='available' 且啟用;參數 schema config.detection_tool_param_schemas;任務綁定寫 config.job_execution_detection_tools(detection_job_binding_handler.py:164),一任務只綁一工具 |
建任務只在 job_type='detection_tool' 顯示工具下拉(FE components/grc/planning/AoTaskForm.vue:472-475) |
清單、參數 schema、綁定整套沿用 | 本案動作:加欄位 execution_target(agent/server,預設 agent,既有列不動);seed 一列 sbom-scan;AoTaskForm 依此欄隱藏 Agent 指派區塊 |
| 檢測派工 | jedi_detection/app/service/detection_orchestration_service.py:184 start_execution → _dispatch_one(:464)建 Agent 工單;_pick_agent(:1417)找不到回 NO_AVAILABLE_AGENT;入口 POST /detection-tools/jobs/<job_uid>/execute(detection_tool_route.py:178,守門 require_license("plugin")+grc.job.update);任務啟動自動首掃(app/detection_tools/task_type_declaration.py:43) |
一律派給遠端 Agent | 入口、守門、首掃觸發、detection_executions 建列 |
本案動作:execution_target=='server' 時分岔到新檔(見風險節),入列 background_jobs(job_type sbom_scan),不找 Agent |
| 檢測結果轉證據 | Agent 回報 → detection_lifecycle_listener.py:24 → DetectionResultHandler.handle_result(detection_result_handler.py:71)存 JOB_EVIDENCES/<job_uid> → 寫 job_evidences(source=DETECTION_TOOL)→ 回填 detection_executions.report_file_id/evidence_id/summary |
— | 整支直接呼叫,轉證據流程零改動 | 本案動作:worker 跑完以多檔結果(報表、SBOM、原始 JSON)呼叫它 |
| 檢測檔案輸入 | 參數欄位沒有通用的檔案型別(FE DetectionConfigField.vue:186-265);FR-058.7 SonarQube 寫死 scan_mode='upload',走隱藏的檔案選擇上傳 upload_type:'DETECTION_SOURCE',執行時送 source_files(FE JobExecutionDrawer.vue:343-410,470-500;BE _resolve_row_source_files :718) |
— | 上傳機制沿用 | 不新造檔案欄位型別 |
| 源碼上傳掃描 | FR-058.7(jedi-detection scan_mode=upload,sonar) |
已上線 | 限額與檢查器(D13) | 解壓在 agent,不在後端 |
| 目標授權 | core/plugins/remote_agent.py:19,87,215(FR-129 allowed_targets) |
掃描目標必須來自登記資料 | 第二版 Git 站台白名單的形狀(未來項) | 第一版不需要 |
| 內部憑證 | FR-130:/etc/guidant/internal-ca.crt 掛載慣例 |
不碰沙箱 | 第二版抓檔容器信任客戶自簽 Git 站台時可借 | 第一版不需要 |
| 前端樣板 | FE commit 6ae57765(14 檔:router、api.js、Service、config、views、i18n) |
選單由後端 ui_routes 驅動 |
整套 | — |
| 角色 | shipped roles:系統管理員/稽核主管/稽核員/一般成員/唯讀 | 沒有「顧問」角色 | 顧問對應系統管理員或稽核主管 | — |
工研院的 SecureScan 是國內同類產品,這裡只作為參考,設計以草稿中的顧問情境為準。
工具組合(Syft、Trivy、Grype)是業界通用做法,兩邊相同並非參照它而來,出貨管線早已在用(FR-127/FR-128)。
UI 設計約束:自行設計,不仿照競品
畫面版面、分頁結構、欄位命名、用語一律自行設計,依 Guidant 既有設計系統(guidant-design-system skill、PrimeVue 元件)與本文的顧問情境出發。不得仿照 ITRI SecureScan 或任何競品的版面與結構,競品截圖不進前端派工卡。未實作的功能不在畫面上預告或留灰色入口(例如 Git 來源在做出來之前,精靈、清單、設定頁都不出現任何 Git 字樣)。
UI 不要直接抄襲,會沒辦法對外交代。 — 決策者原話(2026-10-10)
兩種輸入在「正規化層」匯合。後端行程不解壓、不解析任何客戶檔案,一律交給沙箱。
%%{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 C as 顧問瀏覽器
participant API as guidant-api
participant OBJ as SeaweedFS
participant DB as guidant-db
participant W as guidant-worker
participant S as guidant-sandbox
C->>API: 新增掃描(上傳源碼包或 SBOM 檔)
API->>OBJ: 存原檔(先擋大小與副檔名)
API->>DB: 建掃描列、background_jobs 入列(payload 只放 uid)
API-->>C: 回掃描 uid
W->>DB: 撿單(FOR UPDATE SKIP LOCKED)
Note over W: 進度①接收檔案
W->>OBJ: 取原檔寫入共用工作 volume
Note over W: 進度②產出物料清單
W->>S: 內網掃描請求(工作路徑、輸入類型)
alt 源碼包
S->>S: 安全解壓、syft 產 SBOM
else SBOM 檔
S->>S: 辨識格式版本、驗 schema
end
Note over W: 進度③比對弱點
S->>S: trivy sbom 與 grype sbom(離線弱點資料)
S->>S: syft convert 產 CycloneDX 與 SPDX 各版本
S-->>W: 原始輸出與 SBOM 檔(共用工作 volume)
Note over W: 進度④整理報告
W->>W: 正規化、跨工具配對、KEV/EPSS、六區塊統計、品質分
W->>OBJ: 存原始輸出與 SBOM(附 sha256)
W->>DB: 寫元件、弱點、摘要,掃描列標完成
C->>API: 輪詢掃描狀態
API-->>C: 四段進度與結果
先看落地版現在有哪些服務,再看本案動了哪裡。下面兩張圖依 docker/production/docker-compose.yml 實查繪製:現況共 10 個服務(其中 guidant-db-init 只在裝機/升級時跑一次)、3 個網段、4 個 named volume。
%%{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
BR["瀏覽器<br/>顧問/管理員操作介面"]
AG["遠端 Agent(FR-039)<br/>客戶主機上的檢測代理"]
HB["Harbor 映像倉庫<br/>只在升級時拉 image"]
subgraph NDEF["網段 default(一般內網,非 internal;只有 guidant-fe 對外開 80/443)"]
FE["guidant-fe<br/>nginx:唯一對外入口,SPA+同源反代"]
API["guidant-api<br/>REST API,收單、查狀態"]
SIO["guidant-socketio<br/>即時通知"]
WK["guidant-worker<br/>背景工作:撿單、證據分類、經 AI 閘道打 AI"]
DB[("guidant-db<br/>PostgreSQL 16:主庫+bugsink 庫")]
RD[("guidant-redis<br/>Redis 8:快取與佇列,只開 TLS")]
SW[("guidant-seaweedfs<br/>物件儲存:上傳檔與報表")]
BS["guidant-bugsink<br/>錯誤追蹤站"]
INIT["guidant-db-init<br/>一次性:裝機建庫、升級套 migration"]
end
subgraph NSB["網段 guidant-sandbox(internal,不能出網)"]
SB["guidant-sandbox<br/>檔案沙箱:拆不可信證據檔"]
end
subgraph NERR["網段 guidant-sandbox-errors(internal)"]
ERRL["成員只有沙箱與 Bugsink<br/>沙箱送錯誤事件專用"]
end
subgraph VOL["named volume(資料落點)"]
VPG[("guidant-pgdata<br/>DB 資料")]
VSWD[("guidant-seaweedfs-data<br/>物件儲存資料")]
VCJ[("guidant-classifier-jobs<br/>分類工作暫存<br/>api/socketio/worker/sandbox 共掛")]
VBS[("guidant-bugsink-data<br/>錯誤站資料")]
end
BR -->|"https 443"| FE
AG -->|"https 443:註冊、回傳結果"| FE
HB -.->|"只在升級時拉新版 image(各服務同)"| API
FE -->|"/api/1.0"| API
FE -->|"/socket.io"| SIO
FE -->|"/bugsink/"| BS
API --> DB
API --> RD
API --> SW
SIO --> DB
SIO --> RD
WK --> DB
WK --> RD
WK --> SW
WK -.->|"經第二網段 guidant-sandbox:內網 8080 拆檔請求"| SB
API -->|"錯誤事件(socketio/worker 同)"| BS
SB -.->|"第二網段:送錯誤事件"| ERRL
ERRL -.-> BS
BS -->|"bugsink 庫"| DB
INIT -->|"profile init,跑完退場"| DB
DB --- VPG
SW --- VSWD
WK --- VCJ
SB --- VCJ
BS --- VBS
怎麼看這張圖:
guidant-fe 一個入口(80/443),瀏覽器與遠端 Agent 都從這裡進,再由 nginx 轉給 guidant-api、guidant-socketio、guidant-bugsink。guidant-sandbox 只在 internal 網段 guidant-sandbox(成員只有它和 worker),連不到 DB、Redis、物件儲存,也出不了網;另一個 internal 網段 guidant-sandbox-errors 只讓它把錯誤事件送給 Bugsink。%%{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
BR["瀏覽器<br/>顧問/管理員操作介面"]
AG["遠端 Agent(FR-039)<br/>客戶主機上的檢測代理"]
HB["Harbor 映像倉庫<br/>只在升級時拉 image"]
subgraph NDEF["網段 default(一般內網,非 internal;只有 guidant-fe 對外開 80/443)"]
FE["guidant-fe<br/>nginx:唯一對外入口,SPA+同源反代"]
API["guidant-api<br/>REST API,收單、查狀態"]
SIO["guidant-socketio<br/>即時通知"]
WK["guidant-worker<br/>背景工作:撿單、證據分類、派掃描、產報表"]
DB[("guidant-db<br/>PostgreSQL 16:主庫+bugsink 庫")]
RD[("guidant-redis<br/>Redis 8:快取與佇列,只開 TLS")]
SW[("guidant-seaweedfs<br/>物件儲存:上傳檔與報表")]
BS["guidant-bugsink<br/>錯誤追蹤站"]
INIT["guidant-db-init<br/>一次性:裝機建庫、升級套 migration"]
end
subgraph HARD["套用共用加固樣板(唯讀、去權限、資源上限);未來新沙箱套同一份"]
subgraph NSB["網段 guidant-sandbox(internal,不能出網)"]
SB["guidant-sandbox:拆檔+SBOM 弱點掃描<br/>image 擴充:加 Syft・Trivy・Grype"]
end
end
subgraph NERR["網段 guidant-sandbox-errors(internal)"]
ERRL["成員只有沙箱與 Bugsink<br/>沙箱送錯誤事件專用"]
end
subgraph VOL["named volume(資料落點)"]
VPG[("guidant-pgdata<br/>DB 資料")]
VSWD[("guidant-seaweedfs-data<br/>物件儲存資料")]
VCJ[("guidant-classifier-jobs<br/>分類工作暫存<br/>api/socketio/worker/sandbox 共掛")]
VBS[("guidant-bugsink-data<br/>錯誤站資料")]
VDB[("guidant-scan-vulndb<br/>弱點資料庫,D4 更新包匯入")]
end
BR -->|"https 443:上傳源碼包/SBOM 檔"| FE
AG -->|"https 443:註冊、回傳結果"| FE
HB -.->|"只在升級時拉新版 image(各服務同)"| API
FE -->|"/api/1.0"| API
FE -->|"/socket.io"| SIO
FE -->|"/bugsink/"| BS
API --> DB
API --> RD
API -->|"存上傳原檔"| SW
SIO --> DB
SIO --> RD
WK --> DB
WK --> RD
SW -->|"取上傳原檔"| WK
WK -.->|"經第二網段 guidant-sandbox:內網 8080 拆檔/掃描請求"| SB
SB -->|"唯讀掛載"| VDB
API -->|"錯誤事件(socketio/worker 同)"| BS
SB -.->|"第二網段:送錯誤事件"| ERRL
ERRL -.-> BS
BS -->|"bugsink 庫"| DB
INIT -->|"profile init,跑完退場"| DB
DB --- VPG
SW --- VSWD
WK --- VCJ
SB --- VCJ
BS --- VBS
style SB fill:#fff3cd,stroke:#d39e00
style VDB fill:#fff3cd,stroke:#d39e00
style HARD fill:#ffffff,stroke:#d39e00,stroke-dasharray:6 4
| 新增元素 | 是什麼 | 為什麼獨立 |
|---|---|---|
guidant-sandbox image 擴充 |
既有檔案沙箱的 image 加裝 Syft、Trivy、Grype,同一顆容器同時負責拆證據檔與 SBOM 弱點掃描 | 不另開容器:上傳的源碼包與 SBOM 檔一樣是不可信的客戶檔,正好落在沙箱既有的隔離邊界內(internal 網段、唯讀、去權限、資源上限),直接沿用最省事也最一致 |
named volume guidant-scan-vulndb |
Trivy/Grype 弱點資料庫與 KEV、EPSS 資料,由 D4 的弱點資料更新包匯入,唯讀掛進 guidant-sandbox |
弱點資料以天為單位更新、產品版本以月為單位,放 volume 才能不出新版產品就換資料;燒進 image 會讓每次升級重拉數 GB |
容器數不變:第一版只收上傳(源碼包+SBOM 檔),不做 Git 來源,因此不需要可出網的抓檔容器。沙箱改吃共用加固樣板後,未來若真要新增沙箱(例如 Git 抓檔),套同一份樣板即可。
%%{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
BR["瀏覽器<br/>獨立模組頁/稽核任務頁"] -->|"443:上傳源碼包/SBOM 檔"| NG["guidant-fe(nginx)"]
NG --> API["guidant-api<br/>入口 1 模組 API・入口 2 檢測工具執行<br/>(execution_target=server 不派 Agent)"]
API --> PG[("guidant-db<br/>掃描表・background_jobs<br/>detection_executions")]
API -->|"存原檔"| OBJ[("SeaweedFS<br/>原檔・原始輸出・報表")]
WK["guidant-worker<br/>撿單・正規化・產報表"] --> PG
OBJ -->|"取原檔"| WK
WK -->|"存原始輸出與報表"| OBJ
subgraph INT["網段 guidant-sandbox:internal,不能出網"]
SB["guidant-sandbox<br/>拆證據檔+Syft・Trivy・Grype"]
end
WK -->|"內網 8080:掃描請求"| SB
WK -->|"寫輸入"| WV[("volume<br/>guidant-classifier-jobs<br/>既有共用工作區")]
WV -->|"讀輸入、寫輸出"| SB
VDB[("volume<br/>guidant-scan-vulndb")] -->|"唯讀"| SB
CLI["guidantai vulndb import"] -->|"匯入更新包"| VDB
| 容器 | 網路 | 有什麼 | 沒有什麼 |
|---|---|---|---|
guidant-sandbox(既有,擴充) |
既有 internal 網段(guidant-sandbox、guidant-sandbox-errors),不能出網 |
拆檔工具+Syft、Trivy、Grype、唯讀弱點資料 | 對外網路、DB 連線、金鑰 |
guidant-worker(既有) |
既有 | 派掃描、正規化、產報表 | 不解壓、不解析客戶檔案 |
工作暫存沿用既有的 guidant-classifier-jobs volume,不另開。第二版加 Git 時才會多一個可出網的抓檔容器,形狀見「未來項」節。
共同欄位(每張租戶表都有):id、uid、tenant_id NOT NULL、org_unit_id、created_user/updated_user、created_at/updated_at。RLS 照 FR-129.2 的形狀:一條 FOR ALL 規則、USING 與 WITH CHECK 都寫,增查改刪四命令都有規則;守衛 test_rls_all_tenant_tables.py 會自動掃到新表。新表 GRANT ... TO cm_app 含 sequence。
| 表 | 用途 | 關鍵欄位 | 關聯 |
|---|---|---|---|
compliance.source_scan_targets |
掃描對象(清單上一列) | name、client_note、contact_note、source_type(upload/sbom,預留 git)、source_ref(JSONB,來源細節)、latest_scan_uid |
1 對多 scans |
compliance.source_scans |
一次掃描 | target_uid(可空,檢測工具入口不建對象直接跑)、detection_execution_uid(可空,從檢測工具入口跑時對應 detection_executions)、source_type、source_ref(JSONB,當次來源快照)、input_file_id、status、background_job_uid、tools(JSONB)、tool_versions(JSONB)、vulndb_date、summary(JSONB:六區塊統計)、sbom_quality(JSONB)、raw_outputs(JSONB:檔案 id+sha256)、report_file_id、error、started_at、finished_at |
多對 1 target;1 對多 components/findings |
compliance.source_scan_components |
SBOM 裡的套件 | scan_uid、purl、name、version、ecosystem、licenses(JSONB)、license_risk |
多對 1 scan |
compliance.source_scan_findings |
正規化後的弱點(一列一個「套件×弱點」) | scan_uid、component_purl、vuln_id(以 CVE 為主)、aliases(JSONB)、severity、trivy(JSONB:有無、等級、分數)、grype(JSONB)、fixed_versions、in_kev、epss、match_key、fingerprint |
多對 1 scan |
public.scan_vulndb_imports |
弱點資料匯入紀錄(平台層,無 tenant_id) | bundle_date、sha256、sources(JSONB:各來源日期與筆數)、imported_by、imported_at |
— |
來源欄位可擴充,日後加 git 不改資料模型:source_type 用字串值,第一版只有 upload(源碼壓縮檔)與 sbom(SBOM 檔)。第一版 UI 與 API 枚舉只開放 upload/sbom,不接受 git 值(送 git 一律回參數錯誤);第二版加 git 時,位址、分支、commit、憑證 uid 都放進 source_ref(JSONB),既有表不加欄位。Git 站台白名單與租戶憑證兩張設定表屬第二版,見「未來項」節。
兩個關鍵 key:
match_key(跨工具配對):ecosystem + 套件名 + 版本 + 正規化弱點編號。正規化弱點編號用別名表收斂(GHSA、PYSEC 對到 CVE),做法借 vuln_gate.py:84 收 relatedVulnerabilities 的寫法。fingerprint(歷次比對):ecosystem + 套件名 + 正規化弱點編號,不含版本——升了版但仍在受影響範圍內,算「仍在」而不是「修掉一個、新增一個」。summary 的形狀刻意對齊 detection_executions.summary,兩個入口(D9)共用同一份摘要:檢測工具入口跑完時,source_scans 存完整結果(target_uid 空、detection_execution_uid 填上),detection_executions 經 DetectionResultHandler 存摘要、報告檔與證據 id。
| 階段 | 做什麼 | repo | 依賴 | 完成怎麼判定(決策者檢查法) |
|---|---|---|---|---|
| A 沙箱擴充 | 既有 guidant-sandbox image 加裝 Syft、Trivy、Grype(容器數不變),加一支掃描端點 /v1/scan;新增 named volume guidant-scan-vulndb(唯讀掛入沙箱);共用加固設定抽成 compose 樣板 x-guidant-sandbox-hardening,guidant-sandbox 改吃樣板;沙箱資源上限依 D12 調整;守衛測試改為自動納入名字以 -sandbox 結尾的服務,並補唯讀與 internal 網段斷言;installer、bundle 加 volume 與更新包位置 |
BE+jedi-evidence-classification | 無 | 進沙箱 syft version、trivy --version、grype version 三支都有;沙箱內 curl 外網失敗;守衛測試全綠,且把沙箱的 read_only 拿掉會紅;證據分類照常可用 |
| B 弱點資料更新 | build 機產出更新包(Trivy DB、Grype DB、CISA KEV JSON、EPSS CSV)+sha256 manifest+簽章;安裝包隨帶一份;guidantai vulndb import <檔> 驗簽章與指紋後寫入 volume;連網客戶由 api/worker 從自家站台下載同一份包、走同一條匯入(沙箱不出網);日期較舊的包拒收;系統設定頁顯示資料日期與「檢查更新」 |
BE | A | 斷網機器匯入後,設定頁顯示日期;掃範例專案弱點數非零;拿舊日期的包、或改過一個位元組的包匯入都會被拒;連網機器按「檢查更新」拿到同一份包,沙箱仍連不到外網 |
| C 掃描後端 | 資料表與 migration;job handler(max_attempts=1);掃描呼叫(登記外呼守衛);兩條輸入路徑(源碼包、SBOM 檔)與 SBOM 格式辨識;安全解壓函式(D13);正規化層(三工具 → 統一弱點模型、跨工具配對、KEV/EPSS、六區塊統計、NTIA 品質分、授權風險);syft convert 多格式多版本輸出;能力點、授權照;API(含掃描狀態查詢) |
BE+jedi-detection | A(B 未完成前可在 DEV 用線上資料) | 兩種輸入各送一筆都跑完;同一份 SBOM 檔與它的源碼包掃出的弱點數一致;無權限角色打 API 回 403;source_type 存 upload/sbom |
| D 前端 | 掃描對象清單、新增精靈三頁、四段進度、結果頁三層分頁、設定頁 | FE | C | 照畫面新增一筆、進度走完、結果頁看得到事實儀表板六區塊、排序可切換且表頭顯示規則;唯讀角色看不到「新增」;畫面與競品無可辨識的版面雷同 |
| E 報表與 AI 分析 | 三種樣式 × PDF/HTML/DOCX;每個弱點列標工具、CVE(連 NVD)、advisory 連結、修正版;附錄一頁(工具與版本、弱點資料日期、原始報告指紋與下載位置)+資料來源說明一頁;共用品牌頁首頁尾;報告頁「AI 分析」按鈕接 FR-122 AI 閘道(只加提示樣板) | BE+FE | C | 九種組合各匯出一份,中文不缺字;報表裡沒有任何結論句;按「AI 分析」才呼叫,結果標示「AI 生成,以上方統計為準」 |
| F 歷次比對 | 兩次掃描比對(借 SspDiffService 骨架);比對頁;回訪報告 |
BE+FE | C、D | 升一個套件版本再掃,「修掉」欄出現該弱點;換一個仍受影響的版本,該弱點留在「仍在」 |
| G 收尾 | SPEC 頁、使用手冊、安裝文件(更新包怎麼匯入)、.claude/rules/security/ 補新模組路徑 |
BE | A–F | 手冊三件事找得到 |
| H 檢測工具入口 | 工具目錄 config.detection_tools 加 execution_target(agent/server,預設 agent)並 seed sbom-scan 一列;參數 schema 一份(scan_mode、sbom_formats、tools);AoTaskForm 依 execution_target 隱藏 Agent 指派;start_execution 遇 server 分岔到新檔、入列 background_jobs;worker 跑完呼叫 DetectionResultHandler.handle_result;任務頁抽屜 summary 中文對照加 SBOM 的 key、加「看完整結果」連結 |
BE+jedi-detection+FE | C(與 D、E 平行) | 專案規劃建檢測工具任務時下拉能選「SBOM 弱點掃描」且不出現 Agent 指派欄;任務啟動或按執行後,不需任何 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
A["A 沙箱擴充"] --> B["B 弱點資料更新"]
A --> C["C 掃描後端"]
C --> D["D 前端"]
C --> E["E 報表與 AI 分析"]
C --> F["F 歷次比對"]
D --> F
B --> G["G 收尾"]
D --> G
E --> G
F --> G
C --> H["H 檢測工具入口"]
H --> G
D1 沙箱怎麼擴充(已定:掃描工具併進既有沙箱,不新增容器)
已定事項:掃描工具併進既有沙箱,不新增容器
guidant-sandbox 的 image,容器數不變。guidant-scan-vulndb 放弱點資料庫,唯讀掛入沙箱。x-guidant-sandbox-hardening。本案只有一顆套用,但未來新沙箱套同一份。樣板除 image、網段、volume 三個參數外,預留第四個「出網政策」(egress policy):第一版只有 none;第二版 Git 抓檔容器用 git-allowlist;若日後做建置型依賴解析再加 registry-allowlist。-sandbox 結尾的服務自動納入」,並補唯讀與 internal 網段斷言。合併原則(之後新增容器都照這條判):容器只按三個信任邊界分——能不能出網、碰不碰客戶輸入、有沒有金鑰。三個答案都一樣就合併進同一顆,不按功能開新顆。拆證據檔與掃描源碼包三個答案完全相同(不出網、碰客戶輸入、無金鑰),所以合併;第二版的 Git 抓檔容器「要出網」,答案不同,才另開。
被排除的做法
已接受的取捨:拆證據檔與弱點掃描共用一顆沙箱,大型源碼包掃描期間,證據分類要排隊。緩解靠兩件事:沙箱的 --max-concurrent 同時處理數,以及背景工作佇列本身的排隊機制(兩種工作都經 background_jobs,不會互相搶死)。若實測排隊時間不可接受,再依合併原則評估——但那時拆開的理由是「資源」,不是「信任邊界」,優先考慮調高同時處理數。
D3 第一版支援哪些語言生態系(已定:純靜態解析,Python/npm/Java 標已驗)
已定事項:第一版只做純靜態解析,不執行客戶的建置工具
只讀 lock 檔與釘死版本的宣告檔(如 pom.xml 寫死版本號),不跑 npm install、mvn、cdxgen 這類建置工具。
理由:建置型解析(cdxgen、npm install --package-lock-only 等)本質上是向套件索引(npm registry、PyPI、Maven Central 這類公開套件倉庫)查版本,純離線的客戶原理上做不到。只有自建內部套件鏡像(Nexus、Artifactory)的客戶才可行,而且需要另一種容器——「可出網到套件索引白名單、又要執行客戶程式碼」,依 D1 合併原則必須另開——再加上網路層出網代理(egress proxy),等於把隔離環境再做六成。
| 客戶環境 | 建置型解析做不做得到 |
|---|---|
| 完全封閉網路 | 不行:查不到任何套件索引 |
| 有內部套件鏡像 | 可以:設定頁要填鏡像位址,客戶 IT 要開防火牆讓我們連鏡像 |
| 客戶提供已建好的環境 | 繞過:直接掃建好的產物,但要工程師配合,與「顧問自給自足」的賣點衝突 |
背景:業界三檔做法
node_modules、.jar、容器 image,最準,但要先有建好的東西。第一版走 ①,並把「缺 lock 檔」這件事本身變成有價值的發現(見下)。
缺 lock 檔怎麼處理(第一版必做,這是終局做法,不是過渡)
package-lock.json,弱點清單只涵蓋直接宣告的 N 個套件,版本為估算值,傳遞依賴(套件自己再用到的套件)看不到,實際風險可能更高。」問題:Syft 支援的生態系很多,但我們只能對自己驗過的負責。
選項
poetry.lock/requirements.txt/Pipfile.lock)、npm(package-lock.json/yarn.lock/pnpm-lock.yaml)、Java(pom.xml 固定版本、gradle.lockfile);Go/.NET/Ruby/Rust 標「工具支援、未驗」。定案:採 A。缺 lock 檔的處理照上方。
為什麼:顧問拿到的系統什麼語言都有,拒收不如照收並誠實標示可信度。
D4 弱點資料怎麼更新(已定:獨立弱點資料更新包)
定案內容:獨立的「弱點資料更新包」guidant-vulndb-<日期>.tar,附 sha256 manifest 與簽章,內含 Trivy DB、Grype DB、CISA KEV JSON(美國網路安全暨基礎設施安全局公布的「已知正被攻擊」弱點目錄)、EPSS CSV(FIRST 組織發布的「每個弱點未來 30 天被利用機率」估計)。存進 named volume guidant-scan-vulndb,沙箱唯讀掛入;掃描時 Trivy 帶 --skip-db-update --offline-scan、Grype 關閉自動更新。報表與結果頁標「弱點資料日期」,超過 30 天黃色警示、90 天紅色。
封閉網路客戶:安裝包隨帶一份;之後單獨下載更新包,用 guidantai vulndb import <檔> 匯入。
連網客戶:不分兩套,只多一個取得管道
被排除的做法
為什麼:弱點資料的更新頻率(天)遠高於產品版本(月),兩者必須能分開更新;連網與封閉用同一份包,報表上的「弱點資料日期」在任何客戶機上意義都一樣。B 階段要實測:Trivy 掃 Java 是否另需 java-db、Trivy DB 實際大小(目前沒有文件)、簽章沿用哪一把金鑰。
D5 工具組合(已定:Syft+Trivy+Grype,SBOM 同時支援 CycloneDX 與 SPDX)
已定事項:SBOM 同時支援 CycloneDX 與 SPDX
Syft 一支工具產生,原生輸出 CycloneDX 與 SPDX 兩種格式;版本用 syft convert 轉(CycloneDX 1.4~1.7、SPDX 2.2/2.3)。精靈頁 3 的進階選項與結果頁原始層的下載選單,兩種格式一律同時列出。CycloneDX 是應用安全圈主流,SPDX 是法規與授權合規主流,美國政府採購兩者皆收;版本要可選是因為有些下游工具只認舊版(如國家資通安全研究院的 ex-sbom 只認 CycloneDX 1.6)。
問題:除了格式,產清單與查弱點各用哪些工具。
四個 SBOM 產生器差在哪
| 工具 | 看什麼列清單 | 強項 | 弱項 | 對我們的意義 |
|---|---|---|---|---|
| Syft | 純讀 lock 檔、套件中繼資料、image 分層 | 最普及;CycloneDX+SPDX 原生多版本;不執行程式、不出網 | 沒有 lock 檔只能猜版本 | 沙箱的最佳選擇,出貨管線已在用 |
| Trivy | 同類讀法 | 一個指令同時出 SBOM 與弱點 | SBOM 不是主業、格式選項少、元件數偶爾較少 | 只用它查弱點、不產 SBOM,避免兩份清單打架 |
| cdxgen | 實際呼叫各語言的建置工具,取得真正的依賴樹 | 沒有 lock 檔也準、支援語言最廣 | 必須執行客戶的建置腳本,且要出網下載依賴 | 與沙箱「不執行、不出網」的邊界衝突,第二版再評估 |
| osv-scalibr | Google 的函式庫型核心 | 新、架構乾淨 | 不是成品;配套的 osv-scanner 對 Debian/Ubuntu 判不準 | 觀察 |
結論:只選 Syft 的用意,是在「純讀檔、不執行、不出網」這一類工具裡它做得最好——這正好就是沙箱的安全邊界——而且一支就能出兩種格式、多個版本。
選項
syft convert,不另包 CycloneDX CLI。NTIA 最低要素七項(供應商、元件名、版本、唯一識別碼、依賴關係、SBOM 作者、產生時間)自己算,規則簡單。定案:採 A。
為什麼:第一版的價值在呈現層,不在工具數量;工具組合與業界一致(見業界對照節),多加工具只會增加不一致要解釋。
D6 結果怎麼呈現(已定:不做風險總評燈號,只給事實統計,判斷留給顧問)
理由:任何我們自訂的燈號或分數,客戶只要問一句「評定標準是什麼」就站不住——「為什麼 3 個 High 是橙燈、不是 4 個」沒有權威答案。系統只呈現工具與公開權威來源的事實,不替任何人下結論;顧問拿事實去跟工程師對話,判斷是顧問的專業。
顧問層改名「事實儀表板」,六個區塊
弱點清單的排序可選(取代「最急的五個」):預設依序 KEV → EPSS → CVSS,目前的排序規則顯示在表頭,顧問可以切換。排序是檢視方式,不是評等。
報表只出事實句、不出結論句
| 不寫 | 改寫 |
|---|---|
| 「本專案屬高風險」 | 「含 1 個 CISA KEV 目錄內弱點;9 個 High,其中 7 個已有修正版」 |
| 「建議立即處理」 | 「最早一個可用修正版發布於 2023-03」 |
報表附「資料來源說明」一頁:每個數字背後的來源各是誰維護——
| 來源 | 維護者 | 提供什麼 |
|---|---|---|
| NVD/CVSS | 美國國家標準暨技術研究院(NIST) | 弱點編號與嚴重度分數 |
| CISA KEV | 美國網路安全暨基礎設施安全局(CISA) | 已知正被攻擊的弱點目錄 |
| EPSS | FIRST(國際資安事件應變組織論壇) | 未來 30 天被利用機率估計 |
| 套件官方修正紀錄 | 各套件維護者(經 GitHub 安全公告等) | 哪一版修正、何時發布 |
被排除的做法
要誠實標示的一點:「有沒有公開攻擊程式」在離線環境沒有可靠資料源(exploit-db 這類資料未納入更新包),事實儀表板以 KEV 與 EPSS 呈現,不另做推估。
D7 「每個數字追到原始輸出」怎麼呈現(已定:報表帶出處,不帶原始輸出)
報表每個弱點列標四件事
附錄一頁:工具名稱與版本、弱點資料庫日期、原始報告的 sha256 指紋與系統內下載位置;另附 D6 的「資料來源說明」一頁。
不把原始輸出拉進報表。原始報告在系統的原始層可以下載,指紋讓「這份就是當時那份」可以被驗證,工程師無法說報表是編的。
被排除的做法
D8 能力點、角色與授權照(已定:歸既有「檢測工具包」,不另開加購)
這就是檢測工具的一種,未來要不要拆開老闆會跟市場部討論,產品還沒上市。 — 決策者原話(2026-10-10)
授權:歸屬既有的「檢測工具包」(FR-062 四個加購包之一,含 remote-agent-manage、detection-profile、plugin),不另開加購項。沒買檢測工具包的租戶,側選單不出現「原始碼掃描」,檢測工具清單也不列「SBOM 弱點掃描」。日後若市場決定拆賣,只改簽發站 plan、不改程式——能力點本來就落在 detection 模組內,符合「加值包整包落在單一模組」的硬規則。
能力點:detection.source-scan.{read,create,update,delete,execute,export}(execute 是「觸發掃描」,export 是「匯出報表」,都在允許的額外動詞內)。弱點資料更新與資源上限的設定只給管理員——要開 detection.source-scan-setting.* 還是併入系統設定頁,design 階段定。
出廠角色
| 角色 | 權限 |
|---|---|
| 系統管理員 | 全部 |
| 稽核主管 | read/create/execute/export |
| 稽核員 | read/create/execute/export |
| 唯讀 | read |
產品沒有「顧問」角色,顧問對應系統管理員或稽核主管。
被排除的做法
D9 獨立模組還是檢測工具(已定:一個核心、兩個入口,同版交付)
問題:要做成獨立功能,還是做成檢測工具之一?
兩個入口同一版交付(階段 D 與 H)。下面說明的是「為什麼共用一個核心」與「入口 2 怎麼接進既有檢測工具鏈」。
%%{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
E1["入口 1:獨立模組<br/>側選單・掃描對象・歷次比對"] --> CORE["核心引擎<br/>輸入→沙箱跑工具→正規化→報表<br/>不知道被誰叫"]
E2["入口 2:檢測工具「SBOM 弱點掃描」<br/>建任務可選→任務頁執行<br/>execution_target=server"] -->|"background_jobs"| CORE
CORE --> T1[("source_scans 系列表")]
CORE -->|"DetectionResultHandler"| T2[("detection_executions<br/>job_evidences(轉證據)")]
採用:一個核心、兩個入口
detection_executions(summary JSONB+report_file_id+可重跑);② 能力點放 detection 模組(D8);③ 掃描表的對象 uid 可空,檢測工具入口不建對象直接跑。核心不得出現「判斷是哪個入口叫的」分支——入口差異只在呼叫端與結果落點。入口 2 怎麼接(已定:不派 Agent,使用者不必選執行位置)
現況:FR-056 的檢測工具是「資料庫一列+Agent 上的執行器」,一律派給遠端 Agent(座標見現況盤點)。本案新增一條伺服器端執行路徑,以後任何「不需要連客戶主機」的工具都走這條。
| 環節 | 做法 |
|---|---|
| 工具目錄 | config.detection_tools 加欄位 execution_target(agent|server,預設 agent,既有列不動)。seed 一列 code='sbom-scan'、execution_target='server'、requires_target_host=false、requires_credentials=false、不設 connection_type |
| 建任務 | 使用者只是從工具下拉選「SBOM 弱點掃描」。AoTaskForm 依 execution_target 隱藏 Agent 指派區塊,不會出現 Agent 指派與目標主機欄位 |
| 參數 | schema 一份:scan_mode(upload|sbom,沿用 SonarQube 的上傳機制,不新造檔案欄位型別)、sbom_formats(多選)、tools(預設三支) |
| 派工 | start_execution 看到 execution_target=='server' 就不走 _dispatch_one/_pick_agent,改入列 background_jobs(job_type sbom_scan),由 worker 送沙箱;detection_executions 照建 running 列,agent_task_uid 留空。新開的分岔檔依工具 code 查 job handler 登記表({'sbom-scan': handler}),不把 SBOM 寫死在分岔內;日後新增伺服器端工具只需登記一筆 |
| 結果 | worker 跑完呼叫同一支 DetectionResultHandler.handle_result(報表、SBOM、原始 JSON 當多檔結果),既有轉證據流程零改動;同時 source_scans 存完整結果(target_uid 空、detection_execution_uid 對應) |
| 任務頁抽屜 | 既有執行鈕、執行列表、summary 中文對照共用;summary 對照表加 SBOM 六區塊的 key;「看完整結果」連到獨立模組的結果頁(同一筆 source_scans) |
被排除的做法
為什麼:兩種用法都要有:顧問在稽核任務外也要能掃、要跨專案比對(入口 1);稽核員在任務裡要能把結果直接變成證據(入口 2)。共用一個核心,兩個入口算出來的數字才會一致,也只有一套掃描與正規化邏輯要維護。
D12 資源上限與時限(已定:照下表,全部可設定)
| 項目 | 定案值 |
|---|---|
| 沙箱(拆檔與掃描共用) | 記憶體 4g(現行 2g)、cpus 2 |
| 單次掃描時限 | 20 分鐘 |
| 源碼包上傳 | 100MB;解壓後 500MB、5 萬檔(沿用 FR-058.7 限額) |
| SBOM 檔上傳 | 50MB |
| 失敗重跑 | 不自動重跑(max_attempts=1),顧問手動按重掃 |
以上全部可在設定頁調整,表中是出廠預設。
為什麼:Grype 比對大型 SBOM 時吃記憶體,2g 不夠;20 分鐘涵蓋草稿情境中「568 個 npm 套件」量級還有餘裕;調高的上限證據分類一併受益。不自動重跑是因為重跑成本高、而且同一份輸入結果可重現,失敗多半要顧問換檔或調限額。C 階段要用實際大型源碼包驗證,並量測掃描期間證據分類的排隊時間(D1 的取捨)。
D13 FR-058.7 上傳分支能複用到哪裡(已定:只借檢查器,不借流程)
detection_source_file 的限額與副檔名白名單、ProfileArchiveValidator(zip-slip、符號連結、檔頭)、ArchiveBombGuard(壓縮炸彈)。.claude/rules/security/uploads-parsing.md 四道(禁 extractall、每個項目做 realpath 檢查、邊解邊計量、外部工具報告視為外部輸入),在沙箱內執行。被排除:整條沿用 FR-058.7——那條的解壓在 agent 端,而且綁 SonarQube 的流程。
為什麼:檢查器是通用的;解壓位置與後續流程不同。安全解壓放在共用層,兩個入口與未來其他功能都能用。
D14 AI 分析(已定:有,但使用者主動按才跑)
被排除的做法
🔴 派工服務已嚴重超標,伺服器端分岔必須新開檔
detection_orchestration_service.py 已 2,500 多行,遠超檔案 800 行上限。本地執行分岔必須新開檔(例如 server_side_dispatch.py),再從 start_execution 一行呼叫,不准往原檔裡加邏輯(CLAUDE.md 體積規則)。reviewer 看到往原檔塞會列為阻斷級。分岔檔依工具 code 查 job handler 登記表({'sbom-scan': handler}),不把 SBOM 寫死在分岔內——這是給日後伺服器端自動化任務留的地基(見「未來項」節)。
🔴 守衛測試寫死四個容器名,新容器不會被檢查
test_security_shape_container_hardening.py:17 的清單是寫死的,而且不守唯讀與 internal 網段。本案雖然不加容器,但沙箱改吃樣板時若樣板漏了唯讀,不會有任何測試紅;第二版加抓檔容器時更是如此。A 階段必須先改守衛(按 -sandbox 結尾自動納入、補唯讀與 internal 斷言),再動 compose。
沒有 lock 檔的掃描結果會「看起來很乾淨」
Syft 讀不到確切版本、也看不到傳遞依賴時,比對弱點會少很多,報表上的弱點數可能很少,看起來很乾淨。顧問最容易被這個誤導。所以 D3 規定:缺 lock 檔的標示、品質扣分與「供應鏈管理缺失」發現都必須放在顧問層顯眼處,不能只在原始層;涵蓋不完整要在每一組統計旁同時顯示,數字不可單獨出現。
拆檔與掃描共用沙箱,掃描會讓證據分類排隊
D1 已接受的取捨。大型源碼包掃描最長 20 分鐘,這段時間證據分類要等。上線前要量測排隊時間;手冊要寫明「掃描進行中,證據分類可能變慢」。
沙箱 image 變大,出貨弱點掃描範圍跟著變
三支工具裝進沙箱 image 後,出貨管線(sbom.sh --images)掃這顆 image 時也會掃到工具本身的依賴。工具若有 Critical 弱點,會擋住出包——A 階段要先跑一次出貨弱點判定,確認白名單是否要調。
兩家工具意見不一致是常態
Trivy 與 Grype 的弱點來源、比對規則、分數來源都不同。結果頁要用白話說明「為什麼兩家說法不同」,不能丟兩個數字讓顧問自己猜。
弱點資料過期,結果就失真
封閉網路客戶可能半年不更新。資料日期必須出現在結果頁與每一份報表上,過期警示不能關。
外來 SBOM 檔是不受信任的輸入
工程師交來的 SBOM 可能格式錯誤、超大、或刻意構造來打解析器。一律在沙箱內解析,後端行程不碰內容,大小 50MB 封頂。
第一版只收上傳。下面三題是 Git 來源的設計,討論時已有建議,原文保留供第二版接續;第一版不實作、不需裁決。第二版要做時,依 D1 的合併原則,Git 抓檔容器「要出網」與既有沙箱信任邊界不同,會另開一顆 guidant-scan-fetcher,套同一份加固樣板,出網政策填 git-allowlist;資料模型只在 source_type 加 git、細節放 source_ref,不改表。另需兩張設定表:config.source_scan_git_hosts(Git 站台白名單:host、label)與 config.source_scan_credentials(租戶憑證:name、git_host、username、secret_params envelope、last_used_at),共同欄位與 RLS 同資料模型節。
未來:抓檔容器出網的範圍怎麼管(原 D2)
問題:抓 Git 一定要出網,但不能讓它變成「打客戶內網的跳板」。
選項
https、站台必須在租戶管理員維護的白名單、DNS 解析結果不得是內網位址(防伺服器端請求偽造,即借我們的伺服器去打客戶內網);② 容器層——git 設 protocol.allow=https、core.hooksPath=/dev/null(不跑 repo 內的掛勾程式)、不抓子模組、--depth 1。建議:採 A,B 列未來項。
為什麼:A 擋掉絕大部分情境且成本低。已接受風險要寫明:抓檔容器若被攻破(例如 git 本身的漏洞),它可以出網;但它手上沒有工具、沒有弱點資料、沒有 DB 連線、沒有長存金鑰,能拿到的只有當次那一份 repo。
未來:Git 來源要不要先登記才能掃(原 D10,類比 FR-129 目標授權)
問題:FR-129 規定偵測目標必須來自登記資料。Git 位址要不要比照「逐個 repo 登記」?
選項
建議:採 A,B 列第二版。
為什麼:風險的核心是「伺服器會連去哪裡」,站台層白名單已經管住這件事;repo 層的差異只是同一站台內的哪個倉庫。
未來:私有倉庫憑證怎麼存、怎麼用(原 D11)
問題:顧問填的 Git token 是客戶的祕密。
建議做法
detection_secret_params 的 envelope 寫法(Fernet 加密)。DETECTION_TOOL_ENCRYPTION_KEY(同屬偵測域,不新增環境變數)。被排除:另開新金鑰——多一把要保管、要輪替,換不到額外防護。credential_uid。GIT_ASKPASS 小腳本,不落磁碟,clone 完即丟。建議:照上述做法。
為什麼:完全照 .claude/rules/security/secrets.md 的規則(payload 只放 uid、明文不進磁碟、不進子行程參數、讀取只回有無);「只用這一次」讓不想把客戶帳密留在系統的顧問有選擇。
未來:伺服器端自動化任務的通用機制(大整理時處理)
決策者方向:顧問很多自己做的流程——產掃描報告、帳號清查後匯出清冊直接列入證據等——未來都要包進任務內,不只檢測工具。本案不做通用化,先把 SBOM 做出來,之後一次大整理。
大整理時要處理:
本案只留一個地基:伺服器端分岔檔依工具 code 查 job handler 登記表(D9「派工」列),日後新增伺服器端工具只需登記一筆,不必再改分岔。
未來:抓檔容器可出網(已接受風險)
Git 抓檔容器被攻破時(例如 git 本身的漏洞),它可以出網。緩解靠它手上什麼都沒有:沒有掃描工具、沒有弱點資料、沒有 DB 連線、沒有長存金鑰,能拿到的只有當次那一份 repo。網路層出網白名單(egress proxy)隨第二版一起評估。
未來:Git 位址可能被拿來打客戶內網
若只檢查站台名稱、不檢查 DNS 解析結果,顧問(或拿到顧問帳號的人)可以把一個看似正常的站台名稱指到內網位址,讓伺服器替他連進去。接單時必須驗 DNS 結果不得是內網位址,clone 前再驗一次(防止兩次解析結果不同)。
未來:私有倉庫憑證是客戶的祕密
明文只能出現在 worker 與抓檔容器的單次請求記憶體裡,不進網址、不進命令列參數、不落磁碟、不進 log。「只用這一次」選項必須真的不寫進任何表。
sbom.sh 維持不動;產品功能成熟後,日後可反過來讓出貨管線吃產品的服務,但不是這次| FR | 關係 |
|---|---|
| FR-122 | 沙箱的加固形狀與「拆檔與出網不同容器」原則的來源;本案把掃描工具併進該沙箱,加固設定抽成樣板 |
| FR-127、FR-128 | 出貨 SBOM 與雙工具弱點判定管線;指令組合與別名解析從這裡搬 |
| FR-058 | 源碼上傳掃描的限額與壓縮檔檢查器(D13) |
| FR-056 | 檢測工具目錄、派工、detection_executions 與轉證據流程;入口 2(階段 H)在這條鏈上多一個工具,並新增伺服器端執行路徑(D9) |
| FR-129 | 目標必須來自登記資料的原則;第二版 Git 站台白名單對應(未來項) |
| FR-130 | 內部 CA 掛載慣例,第二版抓檔容器信任客戶自簽 Git 站台時可借 |
| FR-065 | 落地版離線前提;弱點資料更新包走安裝包與 guidantai 指令 |
| FR-132 | 能力點命名與「加值包整包落在單一模組」硬規則(D8) |
| FR-122 的 AI 閘道 | D14 的 AI 分析按鈕直接借用 |