第三方元件風險:受評系統清單(Source Scan Target List)
功能群:第三方元件風險|兩個入口、掃描流程、資料模型、權限先讀 功能群總覽
事實基準:2026-10-11 從 FE src/views/source-scan/SourceScanTargetList.vue、SourceScanWizard.vue、TaskScanList.vue、BE api/source_scan/routes/target_route.py、jedi-source-scan source_scan_target_app_service.py、DEV compliance.source_scan_targets 實查掃出
變更紀錄
| 日期 |
FR |
變更 |
| 2026-10-11 |
FR-134 |
新頁:受評系統清單、三步精靈(新增/重新掃描)、任務發起的掃描頁籤、批次刪除受評系統、清理失敗紀錄 |
1. 功能描述
第三方元件風險模組的入口頁。一列=一套受評系統(被掃的軟體),顯示最近一次掃描的重點數字與前次比較;從這裡新增受評系統並發起首次掃描、重新掃描、進結果頁、進歷次比較、刪除。第二個頁籤列出從專案任務發起的掃描(不掛受評系統)。
主要使用者:顧問、稽核員(持 detection.source-scan.*)。
角色速覽
| 角色 |
一句話 |
稽核主管(detection.*.*) |
全部操作,含刪除 |
稽核員(detection.*.read+create/execute/export) |
新增、掃描、看結果、匯出;不能刪 |
一般成員/唯讀(detection.*.read) |
只能看清單與結果 |
1.1 功能總覽(本頁全部功能)
| # |
功能 |
說明 |
位置 |
詳述 |
| 1 |
受評系統清單 |
分頁列出受評系統;欄位:系統名稱/委託單位說明/輸入來源/最近掃描時間/關鍵指標/前次比較/功能 |
「受評系統」頁籤 |
§5 |
| 2 |
搜尋 |
依系統名稱關鍵字,停止輸入 300ms 後查詢 |
Header 搜尋框 |
§10 |
| 3 |
新增受評系統 |
三步精靈:基本資料 → 掃描來源 → 掃描設定,送出即建系統並發起首掃 |
Header「新增受評系統」 |
UC-SSL-01 |
| 4 |
重新掃描 |
同一精靈,跳過基本資料、來源類型鎖定 |
列操作 ↻ |
UC-SSL-02 |
| 5 |
掃描進度 |
掃描中的列,「關鍵指標」欄改顯示四段進度並每 3 秒更新;失敗時顯示失敗提示 |
列「關鍵指標」欄 |
§10 |
| 6 |
檢視結果 |
開最近一次完成的掃描結果頁 |
列操作 📊 |
掃描結果頁 |
| 7 |
歷次比較 |
有兩次以上完成的掃描才出現 |
列操作 🕘 |
歷次比較 |
| 8 |
刪除單一受評系統 |
確認框列出將一併刪除的掃描次數;有掃描進行中則按鈕停用 |
列操作 🗑 |
UC-SSL-03 |
| 9 |
批次刪除 |
勾選多列後「刪除所選(N)」;所選含掃描進行中的列則按鈕停用 |
勾選欄+Header |
UC-SSL-03 |
| 10 |
清理失敗紀錄 |
該系統有失敗的掃描時出現;對話框只列失敗的那幾筆讓人勾選刪除 |
列操作(橡皮擦) |
UC-SSL-04 |
| 11 |
任務發起的掃描 |
第二頁籤:從專案任務發起的掃描,一列一次;可開結果頁 |
「任務發起的掃描」頁籤 |
§5.3 |
| 12 |
授權唯讀 |
授權到期唯讀時所有寫入按鈕 disabled,tooltip 說明原因 |
全部寫入按鈕 |
總覽 §7.2 |
頁籤切換寫在網址 ?tab=targets|task,重新整理停在原頁籤。
2. Use Case
視覺慣例:判斷框=分支條件;紅框=擋下並標 error code;綠框=完成。
UC-SSL-01 新增受評系統並首次掃描
| 項目 |
內容 |
| 角色 |
持 detection.source-scan.create+execute |
| 前置條件 |
授權含檢測工具包;未進唯讀 |
| 產出 / 後置條件 |
source_scan_targets 一列;source_scans 一列(queued)+背景工作;稽核事件 6190、6191 |

精靈三步:
| 步驟 |
欄位 |
規則 |
| 基本資料 |
系統名稱(必填,≤255)、委託單位/系統說明(選填,≤2000)、技術聯絡窗口(選填,≤2000,提示「僅供紀錄,系統不會主動聯繫」) |
名稱空白不能下一步 |
| 掃描來源 |
二選一:「上傳原始碼封存檔」(.zip/.tar/.tar.gz/.tgz,≤100MB)或「上傳 SBOM 文件」(.json/.xml/.spdx,≤50MB);拖曳或點選檔案 |
選原始碼時提示要含鎖定檔;副檔名與大小前端先擋,後端再擋 |
| 掃描設定 |
「標準掃描」卡片=三套工具全勾+CycloneDX 1.6、SPDX 2.3;「進階設定」可改弱點比對工具(Trivy/Grype/OSV-Scanner)與 SBOM 輸出格式(CycloneDX 1.4/1.5/1.6、SPDX 2.2/2.3) |
工具、格式各至少一個;有改過時卡片標「已調整」 |
送出順序:先 POST /source-scan/targets 建系統,再 POST /source-scan/targets/{uid}/scans 上傳並入列。上傳中顯示百分比進度條,對話框不可關。
陷阱:系統建好但上傳失敗時,系統已存在。精靈記住剛建好的 uid,使用者換檔重送只補上傳,來源類型同時鎖定——否則每按一次就多一個重複的受評系統。
UC-SSL-02 重新掃描
| 項目 |
內容 |
| 角色 |
持 detection.source-scan.execute |
| 前置條件 |
該系統沒有掃描進行中 |
| 產出 / 後置條件 |
source_scans 新一列;完成後 summary.delta 自動對前一次完成的掃描算出修補/新增/仍存在 |
精靈只有「掃描來源」「掃描設定」兩步,標題「重新掃描:<系統名稱>」。來源類型鎖定為該系統建立時選的那一種(提示「同一受評系統須固定使用同一種來源,歷次結果方可比較」)——原始碼與 SBOM 兩種來源的元件辨識範圍不同,混用會讓歷次比較失真。
UC-SSL-03 刪除受評系統(單一/批次)
| 項目 |
內容 |
| 角色 |
持 detection.source-scan.delete |
| 前置條件 |
所選系統都沒有掃描進行中 |
| 產出 / 後置條件 |
受評系統與其全部掃描、元件、弱點刪除(DB 外鍵 cascade);每套系統一筆稽核事件 6193 |

- 確認框文字:「將刪除 N 套受評系統及其全部掃描紀錄(共 M 次),無法復原。」沒有任何掃描時省略次數。
- 整批全有或全無:後端先檢查所有選取的系統,任一套有掃描排隊中或執行中就整批 409
SOURCE_SCAN_409001,訊息點名是哪幾套;不會刪掉一半。前端同時把「勾到掃描中的列」時的刪除鈕停用,避免送出後才被擋回。
- 上傳檔與產物檔留在檔案庫,不在本交易刪。
- 換頁或重新查詢時勾選清空,避免刪到畫面上看不到的列。
UC-SSL-04 清理失敗紀錄
| 項目 |
內容 |
| 角色 |
持 detection.source-scan.delete |
| 前置條件 |
該系統至少有一次失敗的掃描 |
| 產出 / 後置條件 |
所選失敗掃描刪除;每筆一個稽核事件 6195 |
對話框說明「僅失敗的掃描可刪除;成功的掃描保留作為歷次比較依據,不可刪除」,列出該系統所有失敗掃描(時間、檔名、錯誤摘要),可全選。後端另擋一道,下列任一種混在選取裡就整批 409 SOURCE_SCAN_409002 並點名:
| 拒絕理由 |
為什麼 |
| 不是失敗的(完成或進行中) |
完成的是歷次比較的基準;進行中的要等它結束 |
| 任務發起的掃描 |
跟著任務走,不從模組刪 |
| 該系統最近一次完成的掃描 |
防禦性檢查:系統指向的那筆被刪會變孤兒指標 |
3. 權限矩陣
| 操作 |
FE 判定 |
BE 強制 |
BE 檢查位置 |
| 選單可見、進頁 |
ui_routes source-scan 綁 detection.source-scan.read(ALL) |
— |
web-menu |
| 清單、任務掃描清單 |
— |
require_license("plugin")+detection.source-scan.read |
SourceScanTargetListRoute.post、SourceScanTaskScanListRoute.post |
| 新增受評系統 |
v-can="{ all: [create, execute] }" |
create |
SourceScanTargetCreateRoute.post |
| 發起掃描(含重新掃描) |
v-can=execute(精靈「開始掃描」、列 ↻) |
execute |
SourceScanTargetScansRoute.post |
| 刪除/批次刪除/清理失敗 |
v-can=delete(勾選欄、🗑、橡皮擦、Header 刪除鈕) |
delete |
SourceScanTargetDetailRoute.delete、SourceScanTargetBatchDeleteRoute.post、SourceScanBatchDeleteRoute.post |
| 租戶歸屬 |
— |
RLS;查不到即 404 SOURCE_SCAN_404001/404002 |
source_scan_target_app_service.py::_get |
| 授權唯讀 |
useLicenseReadonly().writeDisabled() |
require_license("plugin") |
common/authz/license.py |
4. 狀態機與前置條件
掃描狀態機見總覽 §4。本頁的 gate:
| 條件 |
效果 |
出處 |
該列有任一次掃描 queued/running |
↻ 與 🗑 停用;勾選後 Header 刪除鈕停用並提示「所選系統有掃描進行中…」 |
SourceScanTargetList.vue::isRowBusy、selectedBusy |
| 有掃描進行中仍送刪除 |
409 SOURCE_SCAN_409001,訊息點名系統 |
source_scan_target_app_service.py::delete_targets |
| 完成次數 ≥ 2 |
出現「歷次比較」鈕 |
SourceScanTargetList.vue doneCount >= 2 |
| 有失敗掃描 |
出現「清理失敗紀錄(N)」 |
failedCount |
| 有完成的掃描 |
出現「檢視結果」,開最近一次完成的 |
lastDoneScan |
| 精靈來源類型 ≠ 系統建立時的類型 |
重新掃描時來源選擇鎖死 |
SourceScanWizard.vue lockedType |
5. UI 設計

5.1 受評系統頁籤欄位
| 欄 |
內容 |
| 勾選(只對持 delete 顯示) |
批次刪除用 |
| 系統名稱 |
粗體 |
| 委託單位/說明 |
最多兩行截斷,空值「—」 |
| 輸入來源 |
Tag:原始碼封存檔/SBOM 文件 |
| 最近掃描時間 |
最近一次掃描的完成時間(沒完成取建立時間),YYYY-MM-DD HH:mm;沒掃過顯示「尚未掃描」 |
| 關鍵指標 |
掃描中或失敗:四段進度(ScanStageProgress);否則最近一次完成的重點數字:「KEV 收錄 N」「嚴重度 High 以上 N」 |
| 前次比較 |
「已修補 N/新增 N」(綠/橘);只完成一次顯示「首次掃描」 |
| 功能 |
純圖示鈕,滑過有 tooltip:📊 檢視結果、🕘 歷次比較、↻ 重新掃描、橡皮擦 清理失敗紀錄(N)、🗑 刪除 |
關鍵指標的兩個數字:
- KEV 收錄:本次弱點中被列在美國 CISA「已知遭利用弱點」目錄的筆數。弱點資料沒帶 KEV 目錄時顯示「—」並提示「無法判定,不代表無收錄」——不能顯示 0。
- 嚴重度 High 以上:Critical+High 的筆數。各工具各算各的、取最多的那家,tooltip 列這次跑了的各家原數(例:「Trivy 58/Grype 57/OSV-Scanner 55」)。
5.2 四段進度
檔案接收 → SBOM 產製(上傳 SBOM 時標「(已提供,略過)」)→ 弱點比對 → 報告彙整。排隊時顯示「排隊中,等候開始」;失敗時顯示「掃描未完成,請確認檔案內容後按『重新掃描』」。
5.3 任務發起的掃描頁籤
| 欄 |
內容 |
| 所屬專案 |
反查任務所屬專案名 |
| 任務 |
任務名 |
| 掃描時間 |
發起時間(清單依發起時間排,這欄也顯示發起時間,否則排序看起來是亂的) |
| 關鍵指標 |
失敗:紅色「掃描失敗」徽章,滑過看錯誤首行;進行中:四段進度;完成:重點數字(沒有數字的指標整顆不顯示) |
| 功能 |
📊 檢視結果(失敗的也能開,看原因) |
- 所屬任務已刪除的掃描不列。反查專案走 canonical
JobProjectOwnershipQuery;反查本身壞掉時不過濾(記 log),清單是找結果用的,不能因此整頁 500。
- 失敗的提示:「掃描未完成,可開啟結果頁查看原因;如需重新掃描,請至所屬任務重新執行掃描工具。」——本頁籤沒有重新掃描與刪除。
- 空狀態:「於專案任務中選用『第三方元件弱點掃描』工具執行後,結果將列於此處。」
6. API 規格
Envelope 見 CLAUDE.md「Response Format」。全部端點守門順序 @jwt_required → @require_license("plugin") → @require_capability(...)。
6.1 總清單(本頁呼叫)
| Method + Path |
說明 |
能力點 |
POST /source-scan/targets/list |
分頁清單(body 帶 filters.keyword) |
read |
POST /source-scan/targets |
建受評系統 |
create |
GET /source-scan/targets/{uid} |
單一受評系統 |
read |
PUT /source-scan/targets/{uid} |
改名稱/說明(畫面目前未使用) |
update |
DELETE /source-scan/targets/{uid} |
刪單一 |
delete |
POST /source-scan/targets/batch-delete |
批次刪受評系統,body {"uids": [...]} |
delete |
GET /source-scan/targets/{uid}/scans |
該系統歷次掃描(新到舊) |
read |
POST /source-scan/targets/{uid}/scans |
上傳並發起掃描(multipart) |
execute |
POST /source-scan/scans/batch-delete |
刪失敗掃描,body {"uids": [...]} |
delete |
POST /source-scan/task-scans/list |
任務發起的掃描分頁清單 |
read |
GET /source-scan/scans/{uid} |
輪詢掃描進度 |
read |
6.2 建受評系統(SourceScanTargetCreateSchema)
| 欄位 |
型別 |
說明 |
| name |
string,必填,1~255 |
系統名稱 |
| client_note |
string|null,≤2000 |
委託單位/系統說明 |
| contact_note |
string|null,≤2000 |
技術聯絡窗口 |
| source_type |
string,必填 |
upload|sbom;其他值 400 |
回應 SourceScanTargetResponseSchema:
| 欄位 |
型別 |
說明 |
| uid |
string |
|
| name / client_note / contact_note / source_type |
|
同上 |
| latest_scan_uid |
string|null |
最近一次完成的掃描;進行中與失敗的不會寫這欄 |
| created_user / created_user_name / updated_user / updated_user_name |
string|null |
審計欄+暱稱 |
| created_at / updated_at |
datetime |
|
出處:api/source_scan/serializers/source_scan.py:28-60。
6.3 發起掃描(multipart)
| 欄位 |
說明 |
| file |
原始碼封存檔或 SBOM 文件 |
| source_type |
upload|sbom,須與受評系統一致 |
| tools |
可重複:trivy/grype/osv;不帶=三家全跑 |
| sbom_formats |
可重複:cyclonedx-json@1.4|1.5|1.6、spdx-json@2.2|2.3,≤8 個 |
回應 {"scan_uid": "...", "status": "queued"}。驗不過的檔不進儲存、也不留掃描列(create_scan_from_upload「先驗再存」);掃描列與背景工作同一交易建立,排不進去就不留一筆永遠 queued 的列。
6.4 批次刪除(SourceScanBatchDeleteSchema)
{ "uids": ["<uid>", "<uid>"] }
uids 1~100 筆,每個 ≤64 字。回應 {"deleted": N}。409 的 msg 帶「<原因>: <點名清單>」,前端以 error_code 翻譯當抬頭、msg 當明細。
6.5 任務掃描清單回應(TaskScanResponseSchema)
| 欄位 |
型別 |
說明 |
| uid / source_type / status |
string |
|
| progress |
dict|null |
四段進度 |
| summary |
dict|null |
見總覽 §6 |
| error |
string|null |
失敗原因 |
| started_at / finished_at / created_at |
datetime |
|
| task_context |
object|null |
project_uid、project_name、job_uid、job_name |
錯誤碼見總覽 §9。
7. 前端檔案地圖(compliance-manager-fe/)
| 檔案 |
角色 |
src/views/source-scan/SourceScanTargetList.vue |
主頁:兩頁籤、清單、搜尋、刪除、按鈕守門 |
src/views/source-scan/SourceScanWizard.vue |
新增/重新掃描精靈(三步或兩步) |
src/views/source-scan/WizardStepBasic.vue/WizardStepSource.vue/WizardStepOptions.vue |
精靈三步 |
src/views/source-scan/TaskScanList.vue |
任務發起的掃描頁籤 |
src/views/source-scan/ScanStageProgress.vue |
四段進度 |
src/views/source-scan/ScanKeyFigures.vue |
清單重點數字(兩頁籤共用) |
src/components/source-scan/FailedScanCleanupDialog.vue |
清理失敗紀錄 |
src/composables/useSourceScanRows.js |
清單列:補最近一次掃描、完成/失敗次數、輪詢 |
src/composables/useScanPoller.js |
掃描中的列每 3 秒輪詢 |
src/composables/useScanToolsRan.js |
這次跑了哪幾家工具 |
src/service/SourceScanService.js |
API 包裝 |
src/config/sourceScan.js |
上傳上限、工具清單、狀態、輪詢間隔 |
src/config/locales/i18n/{zh-tw,en}/source-scan.json |
頁面字串 |
8. 後端檔案地圖
| 鏈路 |
Route |
App Service |
底層 |
| 受評系統 CRUD/批次刪除 |
api/source_scan/routes/target_route.py |
jedi-source-scan app/service/source_scan_target_app_service.py |
domain/source_scan、infra/source_scan |
| 發起掃描 |
target_route.py::SourceScanTargetScansRoute.post |
source_scan_app_service.py::create_scan_from_upload |
scan_input_service.py(收檔驗證)、app/jobs/sbom_scan_job.py(背景工作) |
| 任務掃描清單 |
api/source_scan/routes/task_scan_route.py |
task_scan_app_service.py |
宿主 port:JobProjectOwnershipQuery(任務 → 專案反查) |
| 宿主組裝 |
api/source_scan/__init__.py(路由註冊)、core/plugins/source_scan.py(DI、沙箱 client、檔案庫 adapter) |
|
|
9. DB
9.1 資料表總清單
| 表 |
讀/寫 |
說明 |
| compliance.source_scan_targets |
寫 |
受評系統 |
| compliance.source_scans |
寫 |
發起掃描建一列;清理失敗刪列 |
| compliance.source_scan_components/source_scan_findings |
刪(cascade) |
刪系統或掃描時一併刪 |
| public.upload_files |
寫 |
上傳檔 |
| public.background_jobs |
寫 |
sbom_scan 背景工作 |
9.2 compliance.source_scan_targets
| 欄位 |
型別 |
說明 |
| id |
bigint NOT NULL |
PK |
| uid |
uuid NOT NULL |
對外識別碼 |
| tenant_id |
bigint NOT NULL |
租戶 |
| org_unit_id |
bigint |
組織 |
| name |
varchar(255) NOT NULL |
系統名稱 |
| client_note |
text |
委託單位/說明 |
| contact_note |
text |
技術聯絡窗口 |
| source_type |
varchar(16) NOT NULL |
upload|sbom;字串欄,允許值由 API 守門,不用 enum 以免加來源要改表 |
| source_ref |
jsonb |
來源補充資訊 |
| latest_scan_uid |
uuid |
soft-ref → source_scans.uid,最近一次完成的掃描 |
| created_user / updated_user |
varchar(255) |
審計欄 |
| created_at / updated_at |
timestamptz NOT NULL |
|
source_scans 欄位卡見掃描結果頁 §9。
10. 頁面邏輯與資料對應
- 載入:
POST /targets/list 取一頁 → 對每列平行 GET /targets/{uid}/scans,算出 latestScan(第一筆)、lastDoneScan(第一個 done)、doneCount、failedCount、hasActiveScan、scanCount。單列補資料失敗不擋整頁,該列顯示「尚未掃描」。
- 為什麼要逐列再查歷次掃描:受評系統的
latest_scan_uid 只在掃描完成時寫,進行中與失敗的掃描只能從歷次清單找。
- 輪詢:最近一次是 queued/running 的列,每 3 秒
GET /scans/{uid} 更新進度,直到 done/failed。任務頁籤同理。
- 錯誤:一律由全域 interceptor 依 error_code 顯示 i18n 訊息;精靈出錯時停在當頁讓使用者換檔重送。
11. 背景行為與外部依賴
| 類型 |
內容 |
| 背景工作 |
sbom_scan(max_attempts=1),worker 撿單執行;沒起 worker 時掃描永遠停在「排隊中」且不報錯 |
| 沙箱 |
guidant-sandbox /v1/scan,單一併發,逾時 20 分鐘 |
| 稽核事件 |
6190 建系統、6191 發起掃描、6193 刪系統、6195 刪掃描 |
| socket |
無;進度靠輪詢 |
| jedi-* 套件 |
jedi-source-scan(本模組全部 domain/service)、jedi-detection(安全解壓、沙箱 client) |
12. 邊界情況與已知坑
- 刪除檢查與刪除之間不加鎖:檢查完沒有進行中、刪除前剛好有人對同一系統發起掃描,新掃描會隨系統一起被刪。可接受,不處理。
- 刪除不清檔案庫:上傳檔與產物檔留在檔案庫;要回收空間需另行處理。
- 重新掃描不能換來源類型:要改用 SBOM 文件,請另建一套受評系統。
- 畫面沒有編輯受評系統的入口:
PUT /targets/{uid} 存在,畫面未接。
- 任務掃描的「所屬任務已刪除」判定靠反查:反查失敗時不過濾,可能短暫列出孤兒掃描。
- 兩頁籤共用
ScanKeyFigures:改重點數字的顯示會同時影響兩邊。
13. 開發與驗證
- 導航:合規與稽核 → 第三方元件風險(
/source-scan)
- 跑起來:BE api(
python main.py)+worker(RUN_MODE=worker python main.py)+DEV 沙箱(docker/dev/README-source-scan.md);弱點資料放 /opt/vulndb
- 測試帳號:稽核主管(全部)、稽核員(無刪除)。密碼見
.env/部署文件
- 驗收:新增系統上傳一包含鎖定檔的原始碼 → 清單看到四段進度 → 完成後出現重點數字 → 再掃一次 → 出現「已修補/新增」與歷次比較鈕 → 勾兩列批次刪
- 相關:FR-134 設計
docs/features/FR-134-2610-source-code-sbom-scan/design.md