FR-134 · 設計文件 v1 · 2026-10-10(D1–D14 全數定案)
上傳源碼包或 SBOM 檔,既有沙箱跑 Syft/Trivy/Grype,事實統計不下結論,兩個入口同版交付
顧問上傳客戶的原始碼壓縮檔或現成的 SBOM,系統在既有沙箱裡產物料清單、雙工具比對弱點、算出六區塊事實統計,匯出 PDF/HTML/DOCX;三個月後再掃一次,修掉幾個、新增幾個一眼看到。不替任何人下結論,每個數字都標工具與出處。同一個核心兩個入口:側選單獨立模組,以及檢測工具清單裡的「SBOM 弱點掃描」(建任務可選、不派 Agent、結果轉證據)。本文件把 14 項決策展開到「照做就能交」的程度,拆成八個子需求、30 張實作子任務。
狀態:設計定案(D1–D14 全數拍板)|日期:2026-10-10|前作:FR-122(沙箱與 AI 閘道)、FR-056/058.7(檢測工具與源碼上傳)、FR-127/128(出貨 SBOM 管線) 討論稿(現況盤點座標、業界對照、各選項推演):
discussion.html
🔴 這份是設計決策,不是現況。 內文的行號與座標停在 2026-10-10 定稿當時,之後實作改動的結果不回頭改寫這裡——上線後的現況看 SPEC 頁。
| 日期 | 變更 | 對應 |
|---|---|---|
| 2026-10-10 | 初版設計定案(D1–D14;D2/D10/D11 移未來項),拆 8 子需求 30 張子任務 | FR-134 母案 CM-2625,子卡 CM-2626~CM-2655 |
顧問輔導客戶時,專案的第三方套件弱點本該由客戶工程師盤點回報,實務上常被「那個不嚴重」「我們環境打不到」帶過,顧問沒有技術背景無從判斷。本案讓顧問不靠工程師就能拿到一份有工具原始輸出背書的弱點報表。
產品現況:出貨管線已有整條「Syft 產 SBOM → Trivy+Grype 雙掃 → 白名單判定」(scripts/build/sbom.sh、vuln_gate.py),但只服務我們自己的出貨,客戶看不到。缺的是:
guidant-sandbox 只會拆檔,沒有 Syft/Trivy/Grype,呼叫介面只有 /v1/extract。%%{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 guidant-api
participant S as SeaweedFS
participant W as guidant-worker
participant SB as guidant-sandbox
participant V as vulndb volume
participant DB as guidant-db
U->>API: 上傳源碼包或 SBOM 檔
API->>S: 存原檔
API->>DB: 建 source_scans(queued)+入列 background_jobs
W->>DB: 撿單(SKIP LOCKED)
W->>S: 取原檔到工作目錄
W->>SB: POST /v1/scan(檔案、選項)
SB->>SB: 安全解壓 → syft → trivy/grype(離線)
SB->>V: 唯讀讀取弱點資料
SB-->>W: SBOM 多格式+兩家原始 JSON
W->>W: 正規化:配對、KEV/EPSS、六區塊統計、NTIA
W->>S: 存 SBOM、原始 JSON(算 sha256)
W->>DB: 寫 components/findings/summary(done)
U->>API: 看結果、匯出報表
執行期四段白話進度(接收檔案 → 產出物料清單 → 比對弱點 → 整理報告)由 worker 經 ctx.report_progress 回報,清單頁輪詢顯示。檢測工具入口(§5.5)走同一條:start_execution 看到伺服器端工具就入列同一種背景工作,跑完多一步呼叫 DetectionResultHandler 把報表轉成任務證據。
整案一句話:顧問上傳客戶的原始碼或 SBOM,十幾分鐘後拿到一份看得懂、工程師賴不掉的弱點報表;三個月後再掃一次,修了沒一眼看到。
給非工程讀者的背景:現代程式九成以上是拼裝現成的第三方套件。套件被發現有安全漏洞時,用到它的程式就跟著有風險。SBOM(軟體物料清單)是一份「這支程式用了哪些套件、哪個版本」的清單;拿這份清單去比對公開的弱點資料庫,就知道哪裡有洞、多嚴重、有沒有修正版。
| 子需求 | 做什麼 | 產出 | 完成怎麼判定(決策者檢查法) |
|---|---|---|---|
| A 沙箱擴充 | 既有沙箱 image 加裝三支工具與掃描端點;弱點資料區;加固設定抽成共用樣板;守衛測試改自動納入 | 沙箱 image 新版、compose 樣板、守衛測試 | 進沙箱 syft version、trivy --version、grype version 三支都有;沙箱內 curl https://github.com 失敗;守衛測試全綠,且把沙箱 read_only 拿掉會紅;證據分類照常可用 |
| B 弱點資料更新 | build 機產更新包(Trivy DB、Grype DB、KEV、EPSS)帶簽章;安裝包隨帶一份、裝機自動放入;設定頁「上傳更新包」與連網「檢查更新」;每次更新留紀錄 | 更新包產製腳本、設定頁區塊、更新紀錄表 | 拔網路線的機器裝完,設定頁顯示「弱點資料日期」;掃一個已知有洞的範例專案弱點數非零;在設定頁上傳新包後日期前進、紀錄多一列寫誰何時;改過一個位元組的包會被拒 |
| C 掃描後端 | 資料表、背景工作、兩種輸入路徑、安全解壓、三家輸出正規化、SBOM 多格式輸出、能力點與授權、API | migration、job handler、正規化層、API | 用 API 送一個源碼包、一份 SBOM 檔,兩筆都跑完,查得到元件數、弱點數、六區塊統計;唯讀角色打寫入 API 回 403 |
| D 前端 | 掃描對象清單、新增精靈三頁、四段進度、結果頁三層分頁、設定頁 | 前端頁面 | 不看文件照畫面新增一筆,等進度走完四段,結果頁看得到六區塊統計、排序可切換且表頭顯示規則;唯讀角色看不到「新增」;畫面與競品無可辨識的版面雷同 |
| E 報表與 AI 分析 | 三種樣式 × PDF/HTML/DOCX;弱點列標工具、CVE、公告連結、修正版;附錄與資料來源頁;「AI 分析」按鈕 | 報表產生器、提示樣板 | 九種組合各匯出一份,中文不缺字;報表裡沒有任何結論句;任挑一個數字在附錄找得到它來自哪支工具;按「AI 分析」才呼叫,結果標示「AI 生成」 |
| F 歷次比對 | 同一對象兩次掃描比:修掉/新增/仍在;回訪報告 | 比對頁、回訪報告 | 升一個套件版本再掃,「修掉」欄出現該弱點;換一個仍受影響的版本,該弱點留在「仍在」 |
| H 檢測工具入口 | 工具目錄加「伺服器端執行」、seed「SBOM 弱點掃描」;建任務可選且不出現 Agent 欄;派工分岔;結果轉證據 | 工具目錄列、派工分岔檔、抽屜對照 | 專案規劃建檢測工具任務時下拉能選「SBOM 弱點掃描」且無 Agent 指派欄;不需任何 Agent 在線,執行列表出現一筆並跑完;結果檔出現在任務證據清單;「看完整結果」進獨立模組數字一致 |
| G 收尾 | SPEC 頁、使用手冊、安裝文件、資安規則檔補路徑 | 文件 | 手冊找得到「怎麼新增掃描、怎麼更新弱點資料、數字怎麼看」三件事 |
依賴:A → B、C 平行 → D、E、H 平行 → F(要 C+D)→ G。B 未完成前 C 可在 DEV 用線上資料開發。
| # | 決策 | 定案 | 理由 | 被排除的方案 |
|---|---|---|---|---|
| D1 | 沙箱怎麼擴充 | Syft/Trivy/Grype 併進既有 guidant-sandbox image,容器數不變;新增 named volume guidant-scan-vulndb 唯讀掛入;加固設定抽成 compose x-anchor x-guidant-sandbox-hardening(參數:image、網段、volume、預留「出網政策」);守衛測試改「名字以 -sandbox 結尾自動納入」並補唯讀與 internal 斷言;記憶體 2g→4g。合併原則:容器只按「能否出網、碰不碰客戶輸入、有無金鑰」三個信任邊界分,答案相同就合併 |
決策者點名容器數顧慮;拆檔與掃描三個邊界答案完全相同,多一顆換不到隔離 | 另開掃描沙箱(容器膨脹);每容器各寫加固設定(會漏且守衛抓不到)。已接受取捨:大源碼包掃描期間證據分類排隊,靠 --max-concurrent 與佇列緩解 |
| D2 | 出網範圍 | 移未來項(Git 來源第二版) | 第一版只收上傳,沙箱不出網 | — |
| D3 | 生態系與解析方式 | 只做純靜態解析(讀 lock 檔與釘死版本的宣告檔),不執行客戶建置工具。Syft 支援的都收,Python/npm/Java 標「已驗」,其餘標「工具支援、未驗」。缺 lock 檔:結果頁與報表明確標示涵蓋不完整、品質分扣分、列為「供應鏈管理缺失」發現(ISO 27001 A.8.28/A.5.21)、精靈提示改用 SBOM 上傳。這是終局做法不是過渡 | 建置型解析要向套件索引查版本,純離線客戶原理上做不到;要做還得另一種出網容器+代理 | 執行建置工具(cdxgen 等);只收有 lock 的專案 |
| D4 | 弱點資料更新 | 獨立「弱點資料更新包」(Trivy DB、Grype DB、CISA KEV、EPSS+sha256 manifest+簽章)。進入途徑三條:①裝機/升級時安裝程式自動放進 volume;②設定頁**「上傳更新包」;③連網客戶「檢查更新」/排程從自家站台抓。沙箱仍不出網**,下載與寫入由 api/worker 做。每次更新留紀錄(誰、時間、前後日期、來源、結果)。資料日期超 30 天黃、90 天紅。不做 CLI 匯入指令 | 資料更新頻率(天)遠高於產品版本(月);客戶不用 console;三條途徑同一份包、同一條驗簽寫入 | 讓工具在客戶機線上更新(版本不可控、沙箱得開網);燒進 image(3GB 每次重拉);跟 Agent 升級走(無此機制);CLI 指令(客戶不用) |
| D5 | 工具組合與格式 | Syft 產 SBOM,CycloneDX 與 SPDX 同時支援(原生輸出,版本用 syft convert 轉:CycloneDX 1.4–1.7、SPDX 2.2/2.3);Trivy+Grype 雙掃弱點;NTIA 七要素自算 |
與出貨管線一致、團隊已熟;Syft 是「純讀檔不執行不出網」裡做得最好的 | cdxgen/osv-scanner/sbomqs(第二版);Trivy 兼產 SBOM(兩份清單打架) |
| D6 | 風險總評 | 不做燈號或分數,只給事實統計,判斷留給顧問。顧問層=「事實儀表板」六區塊:嚴重度統計(兩家並列)、KEV 目錄內弱點、EPSS 達門檻弱點(門檻可調預設 10%)、可修未修、涵蓋完整度、跟上次比。排序可選(預設 KEV→EPSS→CVSS,規則顯示在表頭)。報表只出事實句,附「資料來源說明」頁 | 任何自訂燈號客戶問「標準是什麼」就站不住;系統只呈現工具與公開權威來源的事實 | 分級規則表(門檻答不出為什麼);CVSS 最高值;加權平均 |
| D7 | 數字追溯 | 報表每個弱點列標:哪個工具報的、CVE 編號(連 NVD)、工具公告連結、修到哪版;附錄一頁(工具與版本、弱點資料日期、原始報告 sha256 與系統下載位置)。不把原始輸出拉進報表 | 指紋讓「這份就是當時那份」可驗證;原始 JSON 幾十到上百頁沒人看 | 整份原始 JSON 附報表;每數字超連結回系統(離線 PDF 點不開) |
| D8 | 能力點、角色、授權 | 授權歸既有**「檢測工具包」**(FR-062 四加購包之一),不另開;能力點 detection.source-scan.{read,create,update,delete,execute,export};弱點資料設定併入系統設定頁、只給管理員;出廠角色:稽核主管/稽核員 read/create/execute/export,唯讀 read |
決策者原話「這就是檢測工具的一種,未來要不要拆開老闆會跟市場部討論,產品還沒上市」 | 獨立加購;基礎包 |
| D9 | 獨立模組還是檢測工具 | 一個核心、兩個入口,同版交付。核心:輸入→沙箱→正規化→報表,不知道被誰叫。入口 1 獨立模組(側選單、掃描對象、歷次比對);入口 2 檢測工具清單裡的「SBOM 弱點掃描」,與 OpenSCAP/SonarQube 並列,整條鏈(建任務可選→任務頁執行→結果進檢測紀錄→轉證據)都在,在伺服器端執行、不派 Agent、使用者不必選執行位置。核心契約:輸出對齊 detection_executions.summary;掃描表對象 uid 可空;核心不得判斷是哪個入口 |
顧問在任務外也要能掃、要跨專案比對;稽核員在任務裡要能直接變證據;共用核心數字才一致 | 只做檢測工具;兩入口各自實作;做成 Agent 執行器(Agent 要帶 3GB 資料且每台更新) |
| D10 | Git 來源登記制 | 移未來項 | — | — |
| D11 | 私有倉庫憑證 | 移未來項 | — | — |
| D12 | 資源與時限 | 沙箱 mem 4g/cpus 2;單次掃描 20 分鐘;源碼包 100MB、解壓後 500MB、5 萬檔(沿用 FR-058.7);SBOM 檔 50MB;失敗不自動重跑(max_attempts=1,顧問手動重掃);全部可設定 |
Grype 比對大 SBOM 吃記憶體;結果可重現,重跑只是浪費 | 不設上限;設更緊(1g/5 分) |
| D13 | FR-058.7 上傳複用邊界 | 借檢查器不借流程:沿用限額、副檔名白名單、ProfileArchiveValidator、ArchiveBombGuard;新寫「安全解壓到磁碟」放 jedi-detection 共用層;不接 SonarQube 的 orchestration |
檢查器通用;解壓位置與流程不同 | 整條沿用(解壓在 Agent 端、綁 Sonar);全部重寫 |
| D14 | AI 分析 | 有,但使用者主動按才跑:報告頁「AI 分析」按鈕,按了才呼叫才扣額度;結果標「AI 生成,以上方統計為準」;借 FR-122 AI 閘道,只加一個提示樣板 | 與 D6 不衝突(預設只給事實,分析是使用者要的);閘道現成,成本只有提示樣板 | 每次自動生成(花額度且替使用者下結論);第一版不做 |
UI 設計約束(決策者兩度明示)
畫面版面、分頁結構、欄位命名、用語一律自行設計,依 Guidant 既有設計系統(guidant-design-system skill、PrimeVue 元件)出發;不得仿照 ITRI SecureScan 或任何競品的版面與結構,競品截圖不進前端派工卡。未實作的功能不在畫面上預告或留灰色入口——Git 來源在做出來之前,精靈、清單、設定頁都不出現任何 Git 字樣。
UI 不要直接抄襲,會沒辦法對外交代。/GIT 還沒做之前都不要在畫面上顯示。 — 決策者原話(2026-10-10)
| 元件 | 座標 | 現況 | 本案動作 | 子任務 |
|---|---|---|---|---|
| 沙箱容器 | docker/production/docker-compose.yml:580-622 |
唯讀、cap_drop ALL、禁提權、tmpfs 2g、mem 2g/cpus 2/pids 256;user: 靠 image |
抽成 x-anchor 樣板、mem 4g、掛 vulndb volume | T-A1 |
| 沙箱網段 | docker-compose.yml:743-749 |
兩網段皆 internal: true |
不動 | — |
| 沙箱服務 | jedi-evidence-classification/docker/classifier_service.py |
stdlib http.server,/healthz、/v1/extract |
加 /v1/scan |
T-A3 |
| 沙箱 image build | scripts/build/build_classifier_image.sh;寫死處 sbom.sh:101、vuln_gate.py:19、build_bundle.sh:93-108 |
不走 Nuitka | Dockerfile 加裝三支工具(釘版);出貨弱點閘先跑白名單確認 | T-A2 |
| 沙箱客戶端 | jedi_ai_gateway/infra/extract/sandbox_client.py:36-72 |
只有 .extract(),POST /v1/extract |
加 .scan();test_security_shape_external_calls.py:159 登記 |
T-A3、T-C3 |
| 守衛測試(容器) | test/security/code/test_security_shape_container_hardening.py:17,28-38 |
四容器名寫死;不守 read_only/tmpfs/cpus/網段 | 改命名規則自動納入;補 read_only 與 internal 斷言 | T-A4 |
| installer/bundle | scripts/installer/install.sh:1949-2016(storage-seed 重灌)、scripts/build/build_bundle.sh:258-297,369-385 |
非 image 資料隨包先例;manifest 只算 image | 收更新包進包、裝機/升級自動放入 volume、更新包進 manifest | T-B2、T-B3 |
| 出貨 SBOM 管線 | scripts/build/sbom.sh:44-104、vuln_gate.py:84 |
工具裝 build 機;別名收集寫法 | 指令組合搬進沙箱;別名收斂借用 | T-A3、T-C4 |
| 背景工作 | app/background_job/handler_registry.py:25、background_job_app_service.py:55 |
@register_job_handler、enqueue()、lease 300s |
新 job type sbom_scan,max_attempts=1 |
T-C2 |
| 上傳限額與檢查 | config/config.py:212-219、jedi_detection/common/detection_source_file.py:48-101、detection_profile_archive.py:99、archive_limits.py:51-99 |
限額、副檔名、zip-slip、炸彈檢查 | 直接用;新寫安全解壓 | T-C1 |
| 檔案儲存 | jedi-file-upload ports.py:54、app/upload_file/service/managed_file_upload_service.py:35 |
save_file/get_file |
原檔、SBOM、原始 JSON、報表都存這 | T-C2 |
| 全站請求上限 | config/config.py:119 MAX_REQUEST_BODY_MB=110,core/app_factory.py:113 |
110MB;要連前門 nginx 一起調 | 不動;更新包上傳走分塊上傳 | T-B4 |
| PDF 報表 | api/project_summary_report/routes/project_summary_report_route.py:31-62,163-182 |
WeasyPrint、bleach、_data_uri_only_url_fetcher、Noto Sans CJK TC |
整條產生方式照抄 | T-E1 |
| DOCX 報表 | app/oscal/service/export/ssp_docx_generator.py:46 |
docxtpl+python-docx | 範本填值方式照抄 | T-E2 |
| 差異比對 | app/oscal/service/import_diff/ssp_diff_service.py:44 |
四態、key 配對 | 骨架借用,key 換 fingerprint |
T-F1 |
| 能力點 | core/host_capabilities.py、scripts/deliverables/gen_permission_matrix.py、範例 scripts/sql/2026-10-05-fr129-2-tenant-scan-zones.sql |
Python 宣告→產 SQL;三段式命名 | 宣告 detection.source-scan.*;migration 加 ui_routes+route_capabilities+bump CAPABILITY_CATALOG.VERSION |
T-C5 |
| 授權照 | common/authz/license.py:352 |
require_license |
用 plugin(檢測工具包) |
T-C5 |
| RLS | FR-129.2 migration :21-60;守衛 test/security/database/test_rls_all_tenant_tables.py |
FOR ALL 一條規則 | 四張租戶表照抄 | T-C1 |
| 檢測工具目錄 | config.detection_tools;GET /detection-tools(jedi_detection/api/routing.py:69);seed jedi_detection/migrations/003-detection-tools-seed.sql |
列 status='available' 且啟用;無執行位置概念 |
加 execution_target 欄;seed sbom-scan 一列+參數 schema |
T-H1 |
| 建任務選工具 | FE components/grc/planning/AoTaskForm.vue:472-475 |
jobType==='detection_tool' 時顯示工具下拉與 Agent 指派 |
依 execution_target 隱藏 Agent 指派區塊 |
T-H4 |
| 檢測派工 | jedi_detection/app/service/detection_orchestration_service.py:184 start_execution→_dispatch_one(:464)→_pick_agent(:1417);2,571 行 |
一律建 Agent 工單 | 新開檔 server_side_dispatch.py 依 code 查 handler 登記表;start_execution 一行分岔 |
T-H2 |
| 檢測結果轉證據 | detection_lifecycle_listener.py:24→DetectionResultHandler.handle_result(detection_result_handler.py:71) |
存 JOB_EVIDENCES/<job_uid>→job_evidences→回填 report_file_id/evidence_id/summary |
worker 跑完直接呼叫同一支(多檔結果) | T-H3 |
| 檢測檔案輸入 | FE JobExecutionDrawer.vue:343-410,470-500;BE _resolve_row_source_files(:718) |
SonarQube 寫死 scan_mode='upload' 走隱藏檔案選擇 |
沿用,scan_mode 多 sbom 值 |
T-H4 |
| 任務頁抽屜 | JobExecutionDrawer.vue:904-980(summary 中文對照)、:2156(執行列表) |
對照表寫死在 Vue | 加 SBOM 六區塊 key;加「看完整結果」連結 | T-H5 |
| 新模組接線 | config/app_modules.py、di_containers/containers.py:145-146;四層樣板 commit 6a35e2cf3 |
— | 主專案 api/source_scan/+DI |
T-C6 |
| 前端樣板 | FE commit 6ae57765(14 檔) |
選單由 ui_routes 驅動 |
整套照抄 | T-D1 |
| AI 閘道 | FR-122 core/plugins/ai_gateway.py、jedi_ai_gateway |
provider/額度/呼叫紀錄/注入防護 | 加一個提示樣板,新端點呼叫 | T-E3 |
| 稽核事件埋點 | docs/claude/memory/reference_audit_event_logging_pattern.md |
既有 pattern | 更新包上傳、掃描建立/匯出各埋一筆 | T-B4、T-C6 |
jedi-source-scan核心引擎做成新套件 jedi-source-scan(~/Projects/Jedicogy/module/jedi-python-package/jedi-source-scan/),DDD 四層照 jedi-detection 的目錄形狀;主專案與檢測工具入口都只是它的呼叫端。理由:D9「核心不知道被誰叫」在套件邊界上最自然;出貨管線日後若要反過來吃產品服務,也只依賴套件。
| 東西 | 落點 |
|---|---|
| 資料模型、repository、正規化、統計、比對、報表產生 | jedi_source_scan/{domain,infra,app}/ |
job handler(sbom_scan) |
jedi_source_scan/app/jobs/sbom_scan_job.py |
| 安全解壓 | jedi_detection/common/safe_extract.py(D13:放檢測共用層,兩套件都用) |
沙箱 /v1/scan 服務端 |
jedi-evidence-classification/docker/scan_service.py(沙箱 image 本來就由該套件 build) |
沙箱客戶端 .scan() |
jedi_ai_gateway/infra/extract/sandbox_client.py(既有 client 加方法) |
| 能力點宣告 | jedi_source_scan/plugin/contract.py CAPABILITIES(照 jedi_detection 契約形狀) |
| 主專案接線 | api/source_scan/、di_containers/source_scan/、config/app_modules.py、core/plugins/source_scan.py |
| 檢測工具入口分岔 | jedi_detection/app/service/server_side_dispatch.py(新檔) |
| 前端 | FE src/views/source-scan/、src/service/SourceScanService.js、i18n |
開發期走 poetry path dependency(主專案 pyproject.toml 取消註解),path 改動不 commit;發版等決策者明示。
compose 樣板(docker/production/docker-compose.yml):
x-guidant-sandbox-hardening: &guidant-sandbox-hardening
read_only: true
cap_drop: [ALL]
security_opt: ["no-new-privileges:true"]
tmpfs:
- /tmp:size=4g # D12:掃描工作區(原 2g)
- /home/classifier:mode=1777
mem_limit: 4g # D12
cpus: 2
pids_limit: 256
# 預留:egress policy 由網段決定;第一版所有實例皆 internal(none)guidant-sandbox 服務段改 <<: *guidant-sandbox-hardening,自己只留 image、environment、networks、volumes;volumes 加 guidant-scan-vulndb:/opt/vulndb:ro。頂層 volumes: 加 guidant-scan-vulndb。installer 的服務名清單不動(容器沒增)。
image(jedi-evidence-classification/docker/Dockerfile):多階段加一層下載三支工具的釘版二進位(版本與出貨管線同:syft 1.54.0/trivy 0.75.0/grype 0.120.0,從 GitHub release 取並驗 sha256),放 /usr/local/bin;不裝任何弱點資料。Trivy/Grype 的快取目錄用環境變數指到 /opt/vulndb/{trivy,grype}。
守衛測試(test/security/code/test_security_shape_container_hardening.py):
SANDBOX_SERVICES 改為「compose 內所有名字以 -sandbox 結尾的服務」動態取得,舊的四容器 tuple 拿掉名單改規則,其餘三容器照原規則。read_only is True、所有 networks 都是 internal: true、tmpfs 有 /tmp。read_only 拿掉要紅、把網段 internal 拿掉要紅。🔴 出貨弱點閘:三支工具本身進了 image,scripts/build/sbom.sh --images 會掃到它們的 Go 二進位依賴。A 階段 build 完先在 188 跑一次 sbom.sh --images 與 vuln_gate.py,有 Critical 就依 vuln-allowlist.txt 四欄格式登記(理由寫「掃描工具自身依賴,不對外暴露」,到期日給三個月),不可為了過閘降版工具。
更新包 guidant-vulndb-<YYYYMMDD>.tar:
| 內容 | 來源 | 壓縮後估算 |
|---|---|---|
| Trivy DB(含 java-db,B 階段實測是否必要) | trivy image --download-db-only/官方 OCI |
50–80 MB |
| Grype DB | grype db update 後的 vulnerability.db+metadata |
150–250 MB |
| CISA KEV | known_exploited_vulnerabilities.json |
<1 MB |
| EPSS | 當日 epss_scores-current.csv.gz |
10–20 MB |
manifest.json |
各來源日期與筆數、每檔 sha256、bundle_date |
— |
manifest.sig |
以產品簽章私鑰簽 manifest(沿用 FR-064 的鑰與驗章碼路;B 階段確認鑰名) | — |
合計約 250–350 MB(估算值,A/B 階段 build 機實測回填);解開進 volume 約 3.5 GB;更新時新舊並存約 7 GB——安裝文件磁碟需求要加這一條。產製腳本 scripts/build/build_vulndb_bundle.sh(新檔,sbom.sh 不動);安裝包 build_bundle.sh 收一份進 vulndb/,進 sha256 manifest。
進入途徑三條,寫入同一條:
install.sh 在起沙箱前把包內那份解進 guidant-scan-vulndb(比照 storage-seed 的 docker cp 寫法,install.sh:1949-2016);升級時只在包內日期較新才覆蓋。客戶無感。MAX_REQUEST_BODY_MB 與 nginx 都不動)。合併後驗 manifest 簽章、逐檔 sha256、bundle_date 不得早於現行 → 解進 volume 的新目錄 → 原子切換 symlink current → 清舊目錄。GET /vulndb/latest(URL 在 config,預設我們的站台)取 manifest 比日期,較新就由 worker 下載走同一條驗簽寫入。沙箱不出網,下載一律由 api/worker 做。更新紀錄(決策者要求「誰什麼時間點上傳」):表 public.vulndb_updates(平台層、無 tenant_id):uid、bundle_date、previous_bundle_date、source(installer|ui_upload|auto_download)、status(succeeded|failed)、error、sha256、sources(JSONB:各來源日期筆數)、created_user(installer 寫 system)、created_at。設定頁顯示最近 20 筆,同時走既有稽核事件埋點(事件 vulndb.update)。
使用端:掃描時 Trivy 帶 --skip-db-update --offline-scan --cache-dir /opt/vulndb/current/trivy,Grype 設 GRYPE_DB_AUTO_UPDATE=false、GRYPE_DB_CACHE_DIR=/opt/vulndb/current/grype;KEV/EPSS 由 worker 正規化階段讀(不在沙箱內)。每筆 source_scans.vulndb_date 記當次資料日期;結果頁與報表顯示,超 30 天黃、90 天紅。
資料模型(schema compliance,共同欄位 id、uid、tenant_id NOT NULL、org_unit_id、created_user/updated_user、created_at/updated_at;RLS 照 FR-129.2 一條 FOR ALL;GRANT 表與 sequence 給 cm_app;收尾 INSERT schema_migrations):
| 表 | 關鍵欄位 |
|---|---|
source_scan_targets |
name、client_note、contact_note、source_type(upload|sbom;字串欄、API 枚舉只開放這兩值,送其他值回 400)、source_ref JSONB、latest_scan_uid |
source_scans |
target_uid(可空)、detection_execution_uid(可空)、source_type、source_ref JSONB、input_file_id、status(queued/running/done/failed)、background_job_uid、tools JSONB、tool_versions JSONB、vulndb_date、summary JSONB(六區塊)、sbom_quality JSONB(NTIA 七要素+缺 lock 清單)、raw_outputs JSONB(檔案 id+sha256)、sbom_files JSONB(格式@版本→檔案 id)、report_file_id、error、started_at、finished_at |
source_scan_components |
scan_uid、purl、name、version、ecosystem、licenses JSONB、license_risk(copyleft/permissive/unknown) |
source_scan_findings |
scan_uid、component_purl、vuln_id(CVE 優先)、aliases JSONB、severity、cvss_score、trivy JSONB(reported/severity/score/advisory_url)、grype JSONB、fixed_versions JSONB、oldest_fix_date、in_kev、kev_date_added、epss_score、match_key、fingerprint、description、nvd_url |
兩個 key:match_key=ecosystem+name+version+正規化弱點編號(跨工具配對,別名收斂 GHSA/PYSEC→CVE,借 vuln_gate.py:84 收 relatedVulnerabilities 的寫法);fingerprint=ecosystem+name+正規化弱點編號(不含版本,升版仍受影響算「仍在」)。
job handler sbom_scan(@register_job_handler('sbom_scan'),max_attempts=1):payload 只放 scan_uid(無金鑰)。流程:
report_progress({'stage':'receive'}):從儲存取原檔到工作目錄(沿用 stage_to_dir pattern);source_type=upload 走 ProfileArchiveValidator+ArchiveBombGuard 檢查 → safe_extract(新寫,§D13);source_type=sbom 驗 JSON/XML 格式與 CycloneDX/SPDX 版本辨識(schema 驗證在沙箱內做)。report_progress({'stage':'sbom'}):SandboxClient.scan(job_uid, files, options) → 沙箱 /v1/scan:syft dir:<工作區> -o cyclonedx-json@1.6=... -o spdx-json@2.3=...(依選項多格式多版本);source_type=sbom 時略過 syft、直接把上傳檔當輸入。report_progress({'stage':'vuln'}):沙箱內 trivy sbom <cdx> --format json --skip-db-update --offline-scan、grype sbom:<cdx> -o json;回傳 SBOM 檔案群+兩份原始 JSON。report_progress({'stage':'report'}):worker 正規化——解析兩份 JSON 成統一 finding、match_key 配對、KEV/EPSS enrich(讀 volume 內檔)、六區塊統計、NTIA 七要素、缺 lock 偵測(Syft 輸出的 cataloger 來源判斷:只有 python-package-cataloger 類而無 lock cataloger)、授權風險;存 SBOM/原始 JSON(算 sha256)進儲存;寫三張表;status=done。沙箱逾時 20 分鐘(D12);任何階段失敗 status=failed 寫 error,不重跑。worker 啟動時沿用 sweep_stale_job_dirs 清殘留。
安全解壓 jedi_detection/common/safe_extract.py:safe_extract(archive_path, dest_dir, limits) -> ExtractReport,照 .claude/rules/security/uploads-parsing.md 四道:禁 extractall、每個 entry realpath 必在 dest_dir 內、拒 symlink/hardlink/裝置檔、邊解邊累計 bytes 與檔數超限即中止、工作目錄不得是符號連結。呼叫端在沙箱內(沙箱 /v1/scan 收到的是壓縮檔,解壓在沙箱裡做)。
沙箱端點 /v1/scan(scan_service.py):Bearer token 同 /v1/extract;輸入多部檔案+JSON 選項;單一併發(--max-concurrent 1 不變);對外回傳一律視為外部輸入(worker 解析時 bleach 不適用、改走嚴格 JSON schema)。SandboxClient.scan() 登記進 test_security_shape_external_calls.py。
六區塊 summary 形狀(對齊 detection_executions.summary,兩入口共用):
{"severity":{"trivy":{"critical":1,"high":9,"medium":15,"low":3},"grype":{...}},
"kev":{"count":1,"items":["CVE-..."]},
"epss":{"threshold":0.1,"count":2,"items":[...]},
"fixable":{"with_fix":7,"without_fix":3,"oldest_fix_date":"2023-04-01"},
"coverage":{"missing_lock":["npm"],"ntia":{"score":5,"missing":["supplier","author"]}},
"delta":{"vs_scan_uid":"...","fixed":4,"new":2,"remaining":8},
"vulndb_date":"2026-10-08","components":568,"findings":28}能力點:detection.source-scan.{read,create,update,delete,execute,export} 宣告在 jedi_source_scan/plugin/contract.py;跑 scripts/deliverables/gen_permission_matrix.py 重產 FE capabilities.js;migration 加 ui_routes(路徑 /source-scan,license_module='plugin')+route_capabilities(ALL:detection.source-scan.read)+bump CAPABILITY_CATALOG.VERSION。shipped-roles.json:稽核主管/稽核員 pattern 加 detection.source-scan.* 扣 delete、update;唯讀 detection.source-scan.read。弱點資料設定併入系統設定頁(system.system-config.update,管理員)。
API(api/source_scan/routes/,全部 @jwt_required → @require_license("plugin") → @require_capability(...),資源域守門在 app service 查 target 屬租戶):
| 方法 | 路徑 | 能力點 | 說明 |
|---|---|---|---|
| POST | /source-scan/targets |
create | 建對象(分頁列表 POST /source-scan/targets/list) |
| GET/PUT/DELETE | /source-scan/targets/<uid> |
read/update/delete | |
| POST | /source-scan/targets/<uid>/scans |
execute | multipart 上傳+選項 → 建 scan、入列;回 scan_uid |
| GET | /source-scan/scans/<uid> |
read | 狀態、進度(四段)、summary |
| GET | /source-scan/scans/<uid>/findings |
read | 分頁、排序參數(kev,epss,cvss)、篩選 |
| GET | /source-scan/scans/<uid>/components |
read | |
| GET | /source-scan/scans/<uid>/files/<kind> |
read | 下載 SBOM(?format=cyclonedx&version=1.6)/原始 JSON,由伺服器讀檔頭定型別+nosniff |
| POST | /source-scan/scans/<uid>/export |
export | style×format → 檔案 |
| GET | /source-scan/targets/<uid>/diff?a=&b= |
read | 歷次比對 |
| POST | /source-scan/scans/<uid>/ai-analysis |
read | D14,呼叫 AI 閘道 |
| GET/POST | /system/vulndb/status、/system/vulndb/upload(分塊)、/system/vulndb/check-update |
system-config | 設定頁 |
審計欄位回傳 created_user_name/updated_user_name(app service 批次轉 nickname)。
FE(照 FR-129.2 commit 6ae57765 14 檔形狀):router、api.js、SourceScanService.js、config/sourceScan.js、views/source-scan/{SourceScanTargetList,SourceScanWizard,SourceScanResult,SourceScanDiff}.vue、i18n source-scan.json(zh-tw/en)+menu.json+error-code.json;元件級 v-can。每支 Vue 不超過 800 行,結果頁三層各拆子元件。
畫面骨架(自行設計,見 §3 UI 約束):①對象清單(名稱、客戶備註、來源、最近掃描日、重點數字、趨勢;按鈕新增/重掃/看結果/歷次比對);②精靈三頁(這是什麼/程式在哪:只有上傳源碼包與上傳 SBOM 檔兩選一/怎麼掃:標準掃描+進階勾工具與格式版本);送出後清單列顯示四段進度;③結果頁三層分頁(事實儀表板六區塊+授權風險+資料日期警示/對照層每弱點一列排序可選、兩家不一致標示/原始層下載);④歷次比對三欄;⑤設定頁(弱點資料日期、上傳更新包、檢查更新、更新歷史、資源上限)。
| 環節 | 做法 | 座標 |
|---|---|---|
| 工具目錄 | migration:config.detection_tools 加 execution_target VARCHAR(16) NOT NULL DEFAULT 'agent'(agent|server);seed 一列 code='sbom-scan'、name='SBOM 弱點掃描'、execution_target='server'、requires_target_host=false、requires_credentials=false、status='available'、enabled=true;參數 schema 一版:scan_mode(select:upload|sbom)、sbom_formats(multiselect)、tools(multiselect 預設三支) |
jedi_detection/migrations/(新檔,照 003-detection-tools-seed.sql 形狀) |
GET /detection-tools |
回應加 execution_target |
jedi_detection/api/routing.py:69 對應 serializer |
| 建任務 | AoTaskForm.vue 工具選定後若 execution_target==='server':隱藏 Agent 指派區塊與目標主機欄,送出的 agent_assignments 為空;BE _replace_detection_tool_binding 對 server 型不要求分派列 |
FE AoTaskForm.vue:472-475;detection_job_binding_handler.py:164 |
| 派工 | 新檔 jedi_detection/app/service/server_side_dispatch.py:SERVER_SIDE_HANDLERS = {'sbom-scan': enqueue_sbom_scan} 登記表;dispatch(job, tool, params) -> execution:建 detection_executions running 列(agent_task_uid NULL、group_uid 照建)、呼叫對應 handler 入列 background_jobs(payload:scan_uid,先建 source_scans 列 target_uid=NULL、detection_execution_uid=<新列>)。start_execution 在守門之後、_plan_from_assignments 之前一行:if tool.execution_target == 'server': return server_side_dispatch.dispatch(...)。不碰 _dispatch_one/_pick_agent |
detection_orchestration_service.py:184(只加一行) |
| 檔案輸入 | 沿用 sonar 上傳機制:抽屜判 scan_mode in ('upload','sbom') 顯示檔案選擇,upload_type:'DETECTION_SOURCE',執行時送 source_files;_resolve_row_source_files 對 server 型回傳檔案 uid 給 dispatch |
FE JobExecutionDrawer.vue:343-410,470-500;BE :718 |
| 自動首掃 | on_tasks_activated 對 server 型同樣觸發 start_execution,但 server 型若尚未有 source_files 則不自動跑、抽屜提示「請上傳檔案後執行」 |
task_type_declaration.py:43 |
| 結果 | job handler 跑完若 detection_execution_uid 非空:組 ScanResult 清單(報表 PDF、CycloneDX、SPDX、兩份原始 JSON,各帶 filename/content_type/summary)呼叫 DetectionResultHandler.handle_result;既有流程寫 job_evidences、回填 report_file_id/evidence_id/summary、群組收口與 completion_mode=auto 判斷零改動 |
detection_result_handler.py:71 |
| 抽屜 | summary 中文對照表加六區塊 key(嚴重度/KEV/EPSS/可修未修/涵蓋/元件數);執行列加「看完整結果」連 /source-scan/scans/<uid>(由 source_scans.detection_execution_uid 反查) |
JobExecutionDrawer.vue:904-980,2156 |
| 授權 | 同一張檢測工具包;沒買的租戶 GET /detection-tools 本來就被 license 過濾 |
— |
核心不判斷入口:差異只在「誰建 source_scans 列」與「跑完要不要多呼叫 DetectionResultHandler」(看 detection_execution_uid 是否非空)。
| 樣式 | 內容 | 讀者 |
|---|---|---|
| 顧問摘要 | 封面、六區塊統計、KEV/EPSS 清單、可修未修前 20、涵蓋完整度、附錄、資料來源說明 | 顧問開會用 |
| 完整技術 | 以上+全部弱點列表(工具、CVE、公告連結、修正版)+元件清單+授權清單 | 交工程師 |
| 客戶交付 | 顧問摘要去掉內部備註、加客戶名與日期欄 | 交客戶 |
格式:HTML(Jinja 模板,單檔內嵌 CSS,可展開原始片段)→ PDF(同一份 HTML 經 WeasyPrint,照 project_summary_report_route.py 的 bleach 消毒與 _data_uri_only_url_fetcher,字型 Noto Sans CJK TC);DOCX(docxtpl 範本 jedi_source_scan/templates/report_*.docx,照 ssp_docx_generator.py 填值)。共用品牌頁首頁尾放模板內(產品名、租戶名、產生時間、頁碼)。
硬規則:不出現結論句(「屬高風險」「建議立即」),只出事實句;每個弱點列四件事(工具、CVE 連 https://nvd.nist.gov/vuln/detail/<id>、advisory 連結、fixed version);附錄一頁(工具與版本、弱點資料日期、各原始檔 sha256 與系統內路徑);資料來源說明一頁(NVD/CVSS—NIST、CISA KEV、EPSS—FIRST、套件官方修正紀錄)。缺 lock 時「涵蓋完整度」段必在統計同頁、不得只在附錄。
AI 分析(D14):結果頁按鈕 → POST /source-scan/scans/<uid>/ai-analysis → 經 FR-122 AI 閘道(既有 provider 設定、額度、呼叫紀錄、Prompt Guard)送提示樣板(輸入只放 summary 與前 20 筆 finding 的事實欄位,不放原始 JSON);回應存 source_scans.ai_analysis JSONB(文字、模型、時間、誰按的);畫面標「AI 生成,以上方統計為準」。報表不含 AI 分析。
SourceScanDiffService.diff(scan_a, scan_b),借 SspDiffService 骨架:以 fingerprint 配對 findings → fixed(a 有 b 無)、new(a 無 b 有)、remaining(兩邊都有,版本可能不同,附兩邊版本);components 以 purl 去版本配對列升降版。結果不落表、即時算;source_scans.summary.delta 在每次掃描完成時對 target.latest_scan_uid 自動算一次存入。回訪報告=顧問摘要樣式加「與上次比」一節。
| 規則檔 | 本案落實 |
|---|---|
uploads-parsing.md |
四道:限額先量、串流收檔、解壓前 sha256、safe_extract;下載端點伺服器讀檔頭定型別+nosniff;沙箱與工具回傳一律視為外部輸入(嚴格 JSON schema 解析、長度上限) |
secrets.md |
job payload 只放 scan_uid;更新包簽章驗證公鑰隨產品、私鑰在 build 機(沿用 FR-064 鑰管理);log 不記檔案內容 |
external-calls 守衛 |
SandboxClient.scan、連網檢查更新的 requests.get 兩筆登記 |
| 容器加固 | §5.1 守衛改規則;read_only/internal 新斷言 |
logging.md |
掃描建立/完成/失敗、報表匯出、更新包上傳各埋稽核事件(誰、何時、對象 uid、結果) |
| 新模組路徑 | .claude/rules/security/*.md 的 paths 加 api/source_scan/**、jedi-source-scan/**(G 階段) |
粒度:一張子任務=一個 session 做得完;每張卡開卡時把本節對應列+§5 對應段展開進卡。
| 子任務 | 做什麼(白話) | 主要檔案 | 驗收 | 依賴 |
|---|---|---|---|---|
| T-A1 compose 樣板與 volume | 加固設定抽成 x-anchor,沙箱改吃;加 vulndb volume 唯讀掛入;mem 4g | docker/production/docker-compose.yml、.env.example |
docker compose config 展開後沙箱帶 read_only/cap_drop ALL/4g;掛載 /opt/vulndb:ro |
— |
| T-A2 沙箱 image 加工具 | Dockerfile 加三支釘版二進位並驗 sha256;build 腳本不變 | jedi-evidence-classification/docker/Dockerfile、scripts/build/build_classifier_image.sh |
進容器三支 version 都有;image 大小增量記錄;sbom.sh --images 跑過、白名單處理完 |
T-A1 |
T-A3 沙箱 /v1/scan+客戶端 .scan() |
沙箱新端點收檔與選項、跑 syft/trivy/grype(離線參數)、回多檔;client 加方法並登記外呼守衛 | jedi-evidence-classification/docker/scan_service.py、jedi_ai_gateway/.../sandbox_client.py、test_security_shape_external_calls.py |
DEV 用 curl 送範例 zip 回 SBOM 兩格式+兩份 JSON;逾時 20 分生效;守衛綠 | T-A2 |
| T-A4 守衛測試改規則 | 名字 -sandbox 結尾自動納入;補 read_only/internal/tmpfs 斷言;突變驗證 |
test/security/code/test_security_shape_container_hardening.py |
全綠;拿掉 read_only 紅、拿掉 internal 紅 | T-A1 |
| T-B1 更新包產製腳本 | build 機下載四來源、組 manifest、簽章、打包;量實際大小回填設計 | scripts/build/build_vulndb_bundle.sh(新) |
188 跑一次出 .tar;manifest 列四來源日期;驗章通過;實測大小寫進 §5.2 |
— |
| T-B2 安裝包收更新包+裝機自動放入 | bundle 收 vulndb/、進 sha256 manifest;install 首裝解進 volume、升級較新才覆蓋 |
scripts/build/build_bundle.sh、scripts/installer/install.sh |
乾淨機裝完 volume 有 current/;升級拿舊包不覆蓋、新包覆蓋;manifest 校驗含 vulndb |
T-B1、T-A1 |
| T-B3 更新包寫入服務+紀錄表 | 驗簽→逐檔 sha256→日期不倒退→解進新目錄→原子切換→清舊;vulndb_updates 表與 migration;稽核埋點 |
jedi_source_scan/app/service/vulndb_update_service.py、scripts/sql/2026-10-xx-fr134-vulndb-updates.sql |
改一個位元組被拒;舊日期被拒;成功後 current 指新目錄、紀錄一列含 created_user |
T-B1 |
| T-B4 設定頁:上傳(分塊)、檢查更新、排程、歷史 | 分塊上傳端點與前端;連網檢查自家站台;每日排程;設定頁區塊與歷史列表(最近 20 筆) | api/source_scan/routes/vulndb_route.py、FE 設定頁元件、config/config.py(站台 URL 變數,先查既有同義變數) |
瀏覽器上傳 300MB 包成功、日期前進、歷史多一列寫誰何時;斷網時檢查更新回白話錯誤 | T-B3 |
| T-C1 資料表 migration+安全解壓 | 四張租戶表+RLS+GRANT;safe_extract 含單元測試(zip-slip、symlink、炸彈) |
scripts/sql/2026-10-xx-fr134-source-scan-tables.sql、jedi_detection/common/safe_extract.py、test/ |
RLS 守衛綠;safe_extract 測試綠且突變紅;回報「出貨基線待重產」 |
— |
| T-C2 套件骨架+job handler | jedi-source-scan 四層、entity/repo/mapper;sbom_scan handler 四段進度、max_attempts=1;儲存讀寫 |
jedi-source-scan/、主專案 core/plugins/source_scan.py DI |
DEV 入列一筆跑完 status=done,進度四段依序出現 |
T-C1、T-A3 |
| T-C3 兩種輸入路徑 | upload 走檢查→解壓→syft;sbom 走格式辨識(CycloneDX/SPDX、JSON/XML、版本)→略過 syft;source_type 枚舉守門(送 git 回 400) |
jedi_source_scan/app/service/scan_input_service.py |
同一專案的源碼包與其 SBOM 掃出弱點數一致;送 git 回 400 |
T-C2 |
| T-C4 正規化層 | 兩份 JSON→統一 finding;別名收斂+match_key/fingerprint;KEV/EPSS enrich;六區塊 summary;NTIA 七要素;缺 lock 偵測;授權風險 |
jedi_source_scan/app/service/normalize/ |
固定範例輸入的 summary 與手算一致;兩家同報一筆只出一列;GHSA↔︎CVE 合併 | T-C2 |
| T-C5 能力點、授權、角色 | contract 宣告、產矩陣、ui_routes+route_capabilities migration+catalog bump、shipped-roles |
jedi_source_scan/plugin/contract.py、scripts/sql/...、resources/roles/shipped-roles.json |
守衛 test_capability_migration_bumps_catalog_version 綠;唯讀角色打 POST 回 403;沒買檢測工具包的租戶選單不出現 |
T-C1 |
| T-C6 API 路由+app service | §5.4 表全部端點;分頁、排序、篩選;下載端點型別與 nosniff;審計欄位暱稱;稽核埋點 | api/source_scan/、jedi_source_scan/app/service/source_scan_app_service.py、di_containers/source_scan/ |
Swagger 全列可打;route guard inventory 守衛綠;ddd-compliance-reviewer 無阻斷 | T-C3、T-C4、T-C5 |
| T-D1 對象清單+精靈 | 清單頁、精靈三頁(頁 2 只兩選項)、四段進度輪詢 | FE views/source-scan/、Service、i18n |
新增一筆跑完;唯讀看不到新增;畫面無 Git 字樣 | T-C6 |
| T-D2 結果頁三層 | 事實儀表板六區塊+授權風險+資料日期警示;對照層排序可選與不一致標示;原始層下載 | FE 子元件三支 | 六區塊數字與 API 一致;表頭顯示排序規則;每支 Vue ≤800 行 | T-D1 |
| T-D3 設定頁區塊 | 與 T-B4 前端合一張:弱點資料狀態、上傳、檢查更新、歷史、資源上限 | FE 系統設定頁 | 見 T-B4 | T-B4 |
| T-E1 HTML/PDF 報表 | 三樣式 Jinja 模板、品牌頁首頁尾、附錄、資料來源頁;WeasyPrint 照既有防護 | jedi_source_scan/templates/、export service |
六份(3 樣式×2 格式)中文不缺字;grep 報表無結論句字詞;附錄 sha256 與儲存檔一致 | T-C6 |
| T-E2 DOCX 報表 | docxtpl 三範本 | jedi_source_scan/templates/*.docx、scripts/generate_source_scan_docx_template.py |
三份開啟正常、表格完整 | T-E1 |
| T-E3 AI 分析按鈕 | 端點+提示樣板經 AI 閘道;存結果;畫面標示 | api/source_scan/routes/ai_route.py、FE 按鈕 |
按才呼叫(AI 呼叫紀錄多一筆);不按零呼叫;標示文字存在 | T-C6 |
| T-F1 比對服務+頁面+回訪報告 | fingerprint 三欄比對;自動算 delta;比對頁;回訪報告樣式 |
jedi_source_scan/app/service/source_scan_diff_service.py、FE SourceScanDiff.vue |
升版再掃「修掉」出現;仍受影響版本留「仍在」 | T-C6、T-D2 |
| T-H1 工具目錄加欄位+seed | execution_target 欄、sbom-scan 列、參數 schema;GET /detection-tools 回欄位 |
jedi_detection/migrations/(新)、serializer |
工具清單 API 多一列且帶 server |
T-C1 |
| T-H2 伺服器端派工分岔 | 新檔 server_side_dispatch.py 登記表;start_execution 一行;建 execution 列與 source_scans 列並入列 |
jedi_detection/app/service/server_side_dispatch.py、detection_orchestration_service.py(一行) |
無 Agent 在線也能跑完;agent_task_uid NULL;orchestration 檔 diff 只有一行 |
T-H1、T-C2 |
| T-H3 結果轉證據 | handler 完成時組多檔 ScanResult 呼叫 DetectionResultHandler.handle_result |
jedi_source_scan/app/jobs/sbom_scan_job.py |
任務證據清單出現 PDF、兩份 SBOM、兩份 JSON;detection_executions.summary 等於 source_scans.summary |
T-H2、T-E1 |
| T-H4 建任務與抽屜檔案輸入 | AoTaskForm 依 execution_target 隱藏 Agent 區塊;binding handler 對 server 不要求分派;抽屜 scan_mode 多 sbom |
FE AoTaskForm.vue、JobExecutionDrawer.vue;detection_job_binding_handler.py |
建任務無 Agent 欄;送出成功;抽屜能上傳 SBOM 檔 | T-H1 |
| T-H5 抽屜 summary 對照與連結 | 六區塊 key 中文對照;「看完整結果」連獨立模組 | JobExecutionDrawer.vue:904-980,2156 |
抽屜顯示六區塊;連結進結果頁數字一致 | T-H3、T-D2 |
| T-G1 SPEC 頁 | docs/spec-site/current/ 新頁(獨立模組、檢測工具入口、設定頁) |
spec-site | writing-feature-specs skill 走完 |
A–H |
| T-G2 使用手冊+安裝文件 | 手冊三件事;安裝文件磁碟需求(+7GB)、更新包取得方式 | docs/user-manual/、installer docs |
手冊找得到三件事 | A–H |
| T-G3 資安規則路徑+域能力索引 | .claude/rules/security/*.md paths 加新模組;domain-capabilities.md 登記 safe_extract、SandboxClient.scan |
規則檔、索引 | Edit 新模組檔時規則自動載入 | A–H |
| T-G4 套件發版+pin 還原 | jedi-source-scan 首版、jedi-detection/jedi-evidence-classification/jedi-ai-gateway 進版;主專案 pin |
pyproject.toml、Nexus |
等決策者明示;pin 還原後全守衛重跑 | A–H |
%%{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
A1[T-A1] --> A2[T-A2] --> A3[T-A3]
A1 --> A4[T-A4]
B1[T-B1] --> B2[T-B2]
A1 --> B2
B1 --> B3[T-B3] --> B4[T-B4] --> D3[T-D3]
C1[T-C1] --> C2[T-C2]
A3 --> C2
C2 --> C3[T-C3]
C2 --> C4[T-C4]
C1 --> C5[T-C5]
C3 --> C6[T-C6]
C4 --> C6
C5 --> C6
C6 --> D1[T-D1] --> D2[T-D2]
C6 --> E1[T-E1] --> E2[T-E2]
C6 --> E3[T-E3]
C6 --> F1[T-F1]
D2 --> F1
C1 --> H1[T-H1] --> H2[T-H2]
C2 --> H2
H2 --> H3[T-H3]
E1 --> H3
H1 --> H4[T-H4]
H3 --> H5[T-H5]
D2 --> H5
D2 --> G[T-G1~G4]
E2 --> G
F1 --> G
H5 --> G
B4 --> G
決策者在 DEV 親手做(全部跑完再收集失敗,不逐項修):
docker exec guidant-sandbox syft version、trivy --version、grype version 三支有;docker exec guidant-sandbox curl -m 5 https://github.com 失敗;docker compose config | grep -A20 guidant-sandbox 看到 read_only: true 與 4g;既有證據分類跑一筆照常。pytest test/security/code/test_security_shape_container_hardening.py 綠;手動把 compose 沙箱 read_only 註解掉再跑 → 紅。package-lock.json 的範例專案 zip → 清單四段進度走完 → 結果頁六區塊有數字、KEV 區塊有列、對照層切換排序表頭跟著變、原始層下載 CycloneDX 1.6 與 SPDX 2.3 兩檔都能開。package.json 的 zip → 結果頁「涵蓋完整度」明確標示 npm 缺 lock、品質分較低。detection_tool 任務 → 下拉有「SBOM 弱點掃描」、選後無 Agent 指派欄 → 任務頁上傳 zip 按執行(所有 Agent 離線)→ 執行列表一筆跑完 → 任務證據清單出現 PDF 與 SBOM → 「看完整結果」數字與獨立模組一致。工具進沙箱 image 會被出貨弱點閘掃到:Syft/Trivy/Grype 是 Go 二進位,各自帶上百個 Go 模組,sbom.sh --images 會列出它們的 CVE。T-A2 build 完先跑閘,Critical 依白名單四欄格式登記(理由、到期日),不可降版工具硬過。
派工服務檔 2,571 行,超標三倍:detection_orchestration_service.py 任何新增邏輯一律新開檔(server_side_dispatch.py),start_execution 只准多一行呼叫;ddd-compliance-reviewer 看到該檔 diff 超過一行即阻斷。JobExecutionDrawer.vue 2,721 行同理——T-H4/H5 的改動能抽子元件就抽。
MAX_REQUEST_BODY_MB(會連帶放寬所有端點)。--max-concurrent,不拆容器。trivy-java-db,B 階段實測、必要時進更新包。source_type 枚舉:資料層預留 git 但 API 與 UI 不開放;守衛可加一條「序列化器枚舉只含 upload/sbom」。| 項目 | 說明 | 時機 |
|---|---|---|
| Git 來源(含私有倉庫憑證、出網白名單、逐 repo 登記) | 討論稿「未來項」D2/D10/D11 原文保留;需另開抓檔容器(套同一份樣板、出網政策 git-allowlist)、資料模型只加 source_type='git' 與 source_ref 內容 |
第二版 |
| 伺服器端自動化任務通用機制 | 工具目錄加分類、授權按工具列、summary 對照由工具列自帶、任務型態顯示名;本案只留 server_side_dispatch 登記表地基 |
大整理 |
| cdxgen/osv-scanner/sbomqs | 建置型解析需客戶套件鏡像;sbomqs 品質分 | 第二版評估 |
| 定期重掃與告警 | 新弱點資料進來後對既有掃描重比對、通知 | 第二版 |
| 容器 image 掃描 | 本案只收源碼與 SBOM | 不在範圍 |
| 授權合規法律意見 | 只列授權類型與傳染性,不給法律結論 | 不在範圍 |