手冊首頁 / 合規稽核 / 掃描設定檔管理

掃描設定檔管理(Scan Profile Management)

功能群:系統管理|共用權限模型先讀 功能群總覽

事實基準:2026-08-01 從 FE src/views/detection-profile/DetectionProfileManageView.vue(1,239 行)+ BE api/detection_tools/routes/detection_tool_profile_route.py / app/detection_tools/service/detection_tool_profile_service.py 全鏈 + DEV config.detection_tool_profiles 實查掃出(FR-059 檢測工具 Profile / Content 庫 T-1.1~T-4.2)

變更紀錄

日期 FR 內容
2026-08-01 FR-059 新頁建立:檢測工具掃描設定檔(profile / content)後台管理——公用版(SYSTEM,平台維護全租戶可見)+租戶自有(TENANT)雙軌同頁;上傳(zip / tar / tar.gz / tgz 四格式、50MB 上限、結構與安全驗證)或登記外部連結雙軌來源;版更(INSERT 新版不覆蓋)、fork 公版為自有、複製到另一工具池、停用(軟刪)。任務抽屜的 profile 下拉由本庫動態餵(options_source 宣告,見 任務配置

1. 功能描述

掃描設定檔管理是檢測工具(CINC Auditor / GCB)掃描設定檔(profile / content)的後台管理頁:把「一支掃描要套用哪套檢測基準」的內容檔收進平台庫,取代原本「進版控 → 人工投放到 Agent 主機 → 改資料庫選項」的維運鏈。上傳或登記後,任務配置頁的 profile 下拉立即列出庫內項目(不經任何部署動作),Agent 執行時透過平台通道取檔——封閉網路(Agent 無外網)環境也能使用檔案型設定檔。

資料模型是公用版 / 租戶自有雙軌(與流程範本管理同一模式):scope='SYSTEM' 公用版由平台(root 租戶)管理員維護、全租戶可見可用但唯讀;scope='TENANT' 自有版各租戶自行上傳維護、他租戶不可見。非 root 使用者對公用版可「複製為自有」(fork)後自行編輯。設定檔按 detection_tool_id 分池:上傳時必選所屬工具,GCB 任務抽屜只見 gcb 池、CINC Auditor 只見 inspec 池;同一份內容兩工具都要用,走「複製到其他工具」各登記一筆。

來源型態雙軌:檔案型source_type='file')上傳打包檔存平台儲存,Agent 走 mTLS 通道拉檔+本機 cache;連結型source_type='url')只登記 URL 字串,Agent 原樣交給檢測引擎自行下載,平台不驗證可達性、不代管憑證(打不打得通是使用端網路與授權問題,設計界線非缺陷)。

主要使用者:持有 detection-profile.* capability 的角色(角色權限矩陣「掃描設定檔管理」列;比照流程範本管理授予各租戶系統管理員角色);tenant 管理員與 root 用同一頁,能力差異由 BE 回的 can_edit 與三層守門決定。

角色速覽

角色 一句話
租戶系統管理員(持 detection-profile.create / delete 上傳 / 版更 / 停用自有設定檔;對公用版唯讀但可 fork 成自有副本
平台管理員(root 租戶) 同上,另可維護公用版(版更 / 複製 / 停用 SYSTEM 列)
一般使用者 不進本頁;在任務抽屜下拉選用庫內設定檔(menu API 人人可讀)

1.1 功能總覽(本頁全部功能)

# 功能 說明 位置 詳述
1 設定檔列表(分頁) 公用版+本租戶自有合併列出、公用版排前面;欄位:名稱(可點開詳細)/ 來源(公用版・自有 Tag)/ 所屬工具 / 類型(上傳檔案・外部連結)/ 適用平台 / 版本 / 狀態(三態)/ 更新時間・更新者 / 操作 主畫面 DataTable(lazy 分頁) §6.2
2 篩選:來源範圍 全部 / 公用版 / 自有 segmented pill 篩選列 §6.2
3 篩選:所屬工具 下拉(來源=工具目錄 GET /detection-tools 篩選列 §6.2
4 搜尋名稱 關鍵字(300ms debounce,後端 name like 過濾) 篩選列 §6.2
5 顯示已停用與舊版 InputSwitch——打開後同時放掉 is_activecurrent_only 兩個條件(見 §12 坑 4) 篩選列 §4
6 新增 / 登記 Dialog:選所屬工具(必選)→ 選來源類型(切換下方欄位)→ 檔案型上傳打包檔 / 連結型填 URL;名稱 / 適用平台 / 描述 「新增設定檔」按鈕 → Dialog UC-DPM-01
7 版更 同名 INSERT 新版本+舊版退出現行,不原地覆蓋;檔案型換新檔、連結型換新連結;只改描述也算一次版更 列操作「版更」→ Dialog UC-DPM-02
8 複製為自有(fork) 公用版複製成本租戶可編輯副本;僅公用版現行啟用列顯示此鈕 列操作「複製為自有」→ Dialog UC-DPM-03
9 複製到其他工具 同內容在另一工具池各登記一筆(分池語意);目標工具下拉排除來源工具本身 列操作「複製到其他工具」→ Dialog §6.4
10 停用(軟刪) confirm 後 is_active=FALSE(同時降 is_current,見 §12 坑 4);任務下拉即不出現,歷史執行紀錄參照可回溯 列操作「停用」→ confirm UC-DPM-04
11 詳細資料 唯讀 Dialog:名稱 / 來源 / 工具 / 類型 / 檔名+SHA-256(檔案型)或 URL(連結型)/ 適用平台 / 版本 / 描述 / 建立者・時間 點名稱或「檢視」icon §6.2
12 公版唯讀提示 公用版列對非 root 不顯示任何寫入鈕,改顯示鎖 icon+tooltip「公用版由平台管理員維護,您可以『複製為自有』後編輯」 列操作區 §3 / §4

UC(§2)展開具後端寫入的核心路徑:新增 / 登記(6,UC-DPM-01)、版更(7,UC-DPM-02)、fork(8,UC-DPM-03)、停用(10,UC-DPM-04)。複製到其他工具(9)與 fork 同構(INSERT 新列),於 §6.4 說明;純顯示 / 篩選類(1~5、11、12)於 §5 / §6.2 說明。

2. Use Case

角色速覽:見 §1。

流程圖視覺慣例:菱形 = 判斷、橘底 = 例外/擋下、紅框 = 錯誤(標 error code)、綠框 = 成功終點、虛線 = 可選路徑。

UC-DPM-01 上傳 / 登記掃描設定檔

項目 內容
角色 detection-profile.create 的角色
前置條件 所屬工具存在於工具目錄(上傳時必選,P7 分池)
產出 / 後置條件 config.detection_tool_profiles 新增一列(version=1, is_current=TRUE);檔案型另落 public.upload_files 一筆實體+回填 sha256;任務抽屜 menu 立即可選

流程:按「新增設定檔」開 Dialog → 選所屬工具(必選)+來源類型+名稱 → 檔案型選擇打包檔(FE accept=".zip,.tar,.tar.gz,.tgz"maxFileSize=52428800)走 multipart 送出;連結型填 URL 走 JSON 送出(同一 endpoint 依 content-type 分流,_payload())→ BE capability 檢查 → 同工具同租戶同名檢查(DETECTION_TOOLS_409006)→ 檔案型跑驗證管線(四格式偵測 / 50MB / 安全 / 結構,四種失敗各自獨立 error code,見 §6.3)→ 計算 sha256 → 存平台儲存 → INSERT。連結型只驗 URL 形狀(須 http(s):// 開頭,否則 DETECTION_TOOLS_400012),不驗可達性

dpm_uc_01 start 按「新增設定檔」開 Dialog 選所屬工具(必選)+來源類型+名稱 branch 來源類型是檔案型還是連結型? start->branch submit_file 檔案型:選擇打包檔 (.zip / .tar / .tar.gz / .tgz,≤50MB) 以 multipart 送出 branch->submit_file 檔案型 submit_url 連結型:填入 URL 以 JSON 送出 branch->submit_url 連結型 cap BE 檢查:使用者持有 detection-profile.create capability? submit_file->cap submit_url->cap cap_no 403 GRC_CAPABILITY_REQUIRED cap->cap_no dup BE 檢查:同工具同租戶已有 同名現行設定檔? cap->dup dup_yes 409 DETECTION_TOOLS_409006 同名已存在 dup->dup_yes validate 檔案型驗證管線(streaming 不落地解壓): ① 副檔名+magic number(400008) ② 大小 ≤50MB(400009) ③ 防 slip / bomb / symlink(400010) ④ 結構須含 inspec.yml(400011) dup->validate 否(檔案型) url_shape 連結型:驗 URL 為 http(s):// 開頭形狀 (不符 → 400 DETECTION_TOOLS_400012) 不驗可達性、不代管憑證 dup->url_shape 否(連結型) sha 計算 sha256(對原始壓縮檔 bytes) 存平台儲存(SYSTEM→upload_system_file / TENANT→一般上傳)並回填 upload_files.sha256 validate->sha 全數通過 insert INSERT config.detection_tool_profiles version=1, is_current=TRUE, is_active=TRUE url_shape->insert 形狀合法 sha->insert done 成功:關 Dialog、跳回第 1 頁重查 任務抽屜 menu 立即可選(不經部署) insert->done
UC-DPM-01 流程:開 Dialog 選工具與來源類型 → 檔案型 multipart / 連結型 JSON → capability 與同名檢查 → 檔案型驗證管線(格式/大小/安全/結構四碼分立)→ sha256 → 存儲 → INSERT version=1

建立公用版(scope='SYSTEM')目前無 FE 入口:新增 Dialog 的 scope 固定送 TENANTDetectionProfileManageView.vue::openCreate:188-202),API 雖收 scope 欄位且 service 有對應守門,公用版新增現況走 seed script(見 §11)。詳見 §12 坑 1。

UC-DPM-02 版更

項目 內容
角色 detection-profile.create 的角色;SYSTEM 列限平台管理員
前置條件 該列 can_edit=true 且為現行啟用版(canWriteRow,見 §4)
產出 / 後置條件 同名 INSERT version+1 新列+舊版 is_current=FALSE(不原地 UPDATE);menu / 任務下拉即指新版;舊版留表供歷史執行紀錄回溯

流程:按「版更」開 Dialog(顯示「為『{name}』建立新版本(目前 v{n})」)→ 檔案型換新檔 / 連結型換新連結(名稱與所屬工具沿用舊版不可改——版更的定義是「同一支 profile 的新內容」)→ BE guard(SYSTEM 列非平台管理員 → DETECTION_TOOLS_403001)→ 帶了新來源就重跑驗證管線;未帶新來源(只改描述 / 適用平台)也算一次版更,沿用舊版的檔案與 sha256,讓每一列自身資訊完整 → 先 demote_current(舊版降 FALSE)再 INSERT——順序不可顛倒,uq_dtp_current_name 唯一索引擋兩筆 current。

dpm_uc_02 start 現行啟用列按「版更」開 Dialog (名稱與所屬工具沿用舊版不可改) guard BE 檢查:目標是公用版(SYSTEM) 且使用者不是平台管理員? start->guard guard_no 403 DETECTION_TOOLS_403001 公用版僅平台管理員可維護 guard->guard_no has_source 這次有帶新來源嗎? (新檔 / 新連結 / 換來源型態) guard->has_source 否(可寫) revalidate 重跑來源解析: 檔案型走完整驗證管線+sha256 連結型驗 URL 形狀 has_source->revalidate reuse 只改 metadata(描述 / 適用平台): 沿用舊版 file_id / sha256 / url (每一列自身資訊完整,agent 不必回溯前版) has_source->reuse 沒有 demote 先降舊版:同名列 is_current=FALSE (uq_dtp_current_name 擋兩筆 current, 順序不可顛倒) revalidate->demote 通過(失敗碼同 UC-DPM-01) reuse->demote insert INSERT 新列 version+1 is_current=TRUE, is_active=TRUE demote->insert done 成功:任務下拉即指新版 舊版留表供歷史執行紀錄回溯 (已綁舊版的任務照常可派工) insert->done
UC-DPM-02 流程:按版更 → BE 檢查公用版寫入權 → 判斷是否帶新來源(帶了重跑驗證管線、未帶沿用舊版來源與 sha256)→ 先降舊版 is_current 再 INSERT version+1 → 下拉即指新版

UC-DPM-03 複製為自有(fork 公用版)

項目 內容
角色 detection-profile.create 的角色(不需平台管理員——fork 寫的是新租戶列,不是改公用版)
前置條件 來源列為公用版現行啟用版(FE scope==='SYSTEM' && is_active && is_current 才顯示按鈕)
產出 / 後置條件 本租戶新增一列 scope='TENANT', tenant_id=本租戶, version=1 的可編輯副本;共用來源列的檔案實體file_id / sha256 沿用,不複製 binary)

流程:按「複製為自有」→ Dialog 預填來源名稱(可改)→ 送出 → BE 檢查本租戶同工具同名(DETECTION_TOOLS_409006)→ INSERT——scope / tenant_id 由 BE 硬寫成 TENANT / 本租戶,不沿用來源列(沿用會讓副本仍是公用版,等於任何人 fork 一次就多一份全站可見資料;detection_tool_profile_service.py::fork_profile:256-294)。

UC-DPM-04 停用

項目 內容
角色 detection-profile.delete 的角色;SYSTEM 列限平台管理員
前置條件 該列 canWriteRow=true
產出 / 後置條件 is_active=FALSE is_current=FALSE(軟刪;同時降 current 讓同名後繼者建得起來,見 §12 坑 4);任務下拉即不出現;歷史執行紀錄參照仍可回溯,已綁定舊參照的任務再派工會被擋(見 §11 派工展開)

流程:按「停用」→ confirm(「停用後任務下拉不再出現此設定檔,既有的執行紀錄不受影響」)→ PUT /<uid>/deactivate → guard → 更新兩旗標。

3. 權限矩陣

操作 FE 判定 BE 強制 BE 檢查位置
進入本頁 ui_routes web-menu 可見性(detection-profile.read=ALL;寫入三能力=ANY) N/A(僅 JWT) migration scripts/sql/2026-08-01-fr059-3-detection-profile-menu-route.sql
列表 / 下拉 menu / 詳細 @jwt_required()無 capability 守門,menu 供任務抽屜全員取用;可見範圍由查詢收斂+RLS 決定,見 §12 坑 3) api/detection_tools/routes/detection_tool_profile_route.py::DetectionToolProfileListRoute / MenuRoute / DetailRoute
新增 / 版更 / fork / 複製到其他工具 hasCap('detection-profile.create')——無權時按鈕 disabled+tooltip「無權限」 route 層 @require_capability("detection-profile.create")(POST 家族全對 create——版更 / fork / 複製都是 INSERT 新列) detection_tool_profile_route.py:39+各 route decorator
停用 hasCap('detection-profile.delete') route 層 @require_capability("detection-profile.delete")(軟刪非真刪,但「讓它從下拉消失」的權限語意等同刪除) detection_tool_profile_route.py:40 / DeactivateRoute
對公用版(SYSTEM)寫入 依 BE 回的 can_edit——false 時不顯示寫入鈕、顯示鎖 icon(FE 不自己重算「我是不是 root」,避免兩邊判定漂移) service 層 guard _guard_system_writable()scope=='SYSTEM' and not viewer_is_platform_admin() → 403 DETECTION_TOOLS_403001(版更 / 複製到其他工具 / 停用適用;fork 不走 guard——寫的是新租戶列) app/detection_tools/service/detection_tool_profile_service.py:377-386;root 判定 canonical=common/authz/platform.py::viewer_is_platform_admin()
跨租戶隔離 無(列表本來就查不到他租戶) repo 查詢顯式收斂 or_(scope=='SYSTEM', tenant_id==ctx.tenant_id)DB RLS 四段 policy 兜底(SELECT 公用版人人可讀;INSERT / UPDATE WITH CHECK 堵租戶偽造 / 竊升 SYSTEM;DELETE 非 super 不能刪 SYSTEM) infra/detection_tools/repository/detection_tool_profile_repo_impl.py:45-56;RLS scripts/sql/2026-08-01-fr059-1-detection-tool-profiles.sql

三層防禦分工(capability 管「能不能碰這個功能」、service guard 管「能不能碰公用版」、RLS 兜底):四個 capability 全部 is_platform=false——租戶管理員必須能建自己的 TENANT 設定檔與 fork 公用版,這正是雙軌功能的核心;公用版保護不在 capability 層鎖。與檢測工具管理(純平台目錄頁)的守門模型刻意不同。

⚠️ detection-profile.update capability 已 seed 且掛在路由節點,但目前沒有任何端點使用(見 §12 坑 2)。

4. 狀態機與前置條件

本頁無輪次 / 階段狀態機,僅有每列自身的 is_active × is_current 二旗標,FE 呈現為三態:

狀態 條件 Tag 語意
啟用中 is_active && is_current success 現行版——任務下拉可選、可版更 / 停用
舊版 is_active && !is_current warning 版更後的歷史版本——下拉不出現,但已綁定它的任務照常可派工(派工不檢查 is_current,見 §11)
已停用 !is_active secondary 軟刪——下拉不出現、再派工被擋、不可再異動

頁面 gate 表:

條件 效果 出處
row.can_edit === false(公用版 × 非平台管理員) 版更 / 複製到其他工具 / 停用按鈕整組不顯示(v-if="canWriteRow(data)"),改顯示鎖 icon+tooltip DetectionProfileManageView.vue::canWriteRow:32、鎖 icon :727-733
row.is_active === falserow.is_current === false 同上——停用列與舊版列不可再異動(要改請對現行版版更) canWriteRow:32
scope==='SYSTEM' && is_active && is_current 才顯示「複製為自有」按鈕(自有列 fork 自己沒有意義;停用 / 舊版不該再被複製出去) DetectionProfileManageView.vue:686
!hasCap('detection-profile.create') / !hasCap('detection-profile.delete') 對應按鈕 disabled+tooltip「無權限」(按鈕仍渲染,與 canWriteRow 的「不渲染」是兩層不同 gate) DetectionProfileManageView.vue:27-28
createDialog.source_type 切換 Dialog 下半欄位:file → FileUpload(accept=".zip,.tar,.tar.gz,.tgz"maxFileSize=52428800);url → URL 輸入框 DetectionProfileManageView.vue:810-853
showHistory switch 打開 列表 filters 同時放掉 is_activecurrent_only必須一起放,見 §12 坑 4) DetectionProfileManageView.vue::fetchProfiles:109-125
版更 Dialog 沿用來源列的 source_type 分流欄位(檔案型換檔 / 連結型換連結),名稱與工具不可改 DetectionProfileManageView.vue::openNewVersion:284-295

5. UI 設計

版面骨架(篩選列 + DataTable + 四組 Dialog):

標題「掃描設定檔管理」+右上「新增設定檔」鈕(無 detection-profile.create 時 disabled) 篩選列:來源 pill(全部/公用版/自有)|所屬工具下拉|名稱搜尋(debounce 300ms)|「顯示已停用與舊版」開關 開啟:§6.2 列表 / 下拉 menu 設定檔列表 DataTable(lazy 分頁;公用版排前面) POST /detection-tool-profiles/list | GET /detection-tools(工具名對照) 欄:名稱(點開詳細)/ 來源 Tag / 工具 / 類型 file・url / 適用平台 / 版本 / 狀態三態 / 操作 公用版對非 root:寫入鈕不顯示、改鎖 icon → §6.2(點我開啟) 開啟:UC-DPM-01 上傳 / 登記 新增 / 登記 Dialog POST /detection-tool-profiles(file=multipart / url=JSON) 選工具(必)→ 來源類型切換欄位: file:上傳 .zip/.tar/.tar.gz/.tgz ≤50MB(驗證管線四碼) url:只登記連結字串(不驗可達性) → UC-DPM-01(點我開啟) 開啟:UC-DPM-02 版更 版更 Dialog POST /detection-tool-profiles/<uid>/new-version 同名 INSERT version+1+舊版 is_current=FALSE 名稱與工具沿用不可改;file 換新檔 / url 換新連結 只改描述也算一次版更(沿用舊來源與 sha256) → UC-DPM-02(點我開啟) 開啟:UC-DPM-03 複製為自有 複製為自有(fork)Dialog POST /detection-tool-profiles/<uid>/fork 公用版 → 本租戶可編輯副本(scope/tenant 硬寫) 共用檔案實體不複製 binary;僅公用版現行啟用列有此鈕 → UC-DPM-03(點我開啟) 開啟:UC-DPM-04 停用 停用(confirm)/複製到其他工具 Dialog PUT /<uid>/deactivate | POST /<uid>/copy-to-tool 停用=軟刪(is_active+is_current 同降)下拉即不出現 複製=同內容在另一工具池登記一筆(分池) → UC-DPM-04(點我開啟) 彩色=主要互動區塊(連對應 UC / §6 API,API 僅代表性,完整清單見 §6.1)|灰=本頁純結構|虛線框=dialog 詳細資料 Dialog(唯讀,含 sha256 / 檔名 / 稽核欄位)由列表名稱與「檢視」icon 開啟,不另列區塊
掃描設定檔管理版面:標題+新增鈕、來源範圍 pill+工具下拉+搜尋+歷史開關篩選列、公用版排前的分頁列表(名稱/來源/工具/類型/版本/狀態/操作)、新增與版更與 fork 與複製 Dialog

📸 實機截圖待補:列表(公用版 10 筆排前 + 自有列、三態狀態 Tag、公用版鎖 icon)、新增 Dialog(file / url 兩型態各一張)、版更 Dialog、複製為自有 Dialog、詳細資料 Dialog(sha256 等寬字)、任務抽屜 profile 下拉動態選項(GCB 池)。

狀態呈現:來源欄 Tag——公用版(info)/ 自有(success);類型欄 icon+文字——上傳檔案(pi-file)/ 外部連結(pi-link);狀態欄三態 Tag(見 §4)。名稱 cell 可點開詳細資料 Dialog(hover 底線);描述截兩行顯示於名稱下方。互動模式沿用流程範本管理頁(segmented pill 篩選、DataTable lazy 分頁、confirm 停用),不另發明。無 socket;各寫入動作成功後重打 fetchProfiles()(新增另重置回第 1 頁)。首次載入以 LoadingState 蓋住整個表格區。錯誤呈現:具體驗證錯誤(格式 / 大小 / 安全 / 結構)由 axios interceptor 依 error code 出 toast(error-code.json 已補 DETECTION_TOOLS_4000xx 對應文案),Dialog 端 catch 留空不重複出錯。

6. API 規格

Envelope:成功 {"code": 1, "data": …}、失敗 {"code": 0, "msg": "…"}return_response)。route 註冊:api/detection_tools/__init__.py:48-68(掛 /detection-tool-profiles,不掛工具目錄底下——設定檔是獨立資源家族)。

6.1 總清單(本頁呼叫的全部 endpoint)

分類 Method + Path 說明 完整規格
POST /detection-tool-profiles/list 分頁列表(公用版+本租戶自有,公用版排前面) §6.2
GET /detection-tool-profiles/menu?detection_tool_uid= 下拉 menu(只回現行+啟用,依工具分池)——本頁不直接用,任務抽屜與執行紀錄消費(見 §11) §6.2
GET /detection-tool-profiles/{uid} 單筆詳細(本頁詳細 Dialog 用列表資料即足,此端點供他處 / 除錯) GAI-SD-02
POST /detection-tool-profiles 新增 / 登記(file 型 multipart、url 型 JSON) §6.3
POST /detection-tool-profiles/{uid}/new-version 版更(INSERT 新版+舊版退現行) §6.4
POST /detection-tool-profiles/{uid}/fork fork 公用版成本租戶自有副本 §6.4
POST /detection-tool-profiles/{uid}/copy-to-tool 複製到另一工具池 §6.4
PUT /detection-tool-profiles/{uid}/deactivate 停用(軟刪) §6.4
讀(輔) GET /detection-tools 工具目錄——所屬工具下拉與工具名對照的來源 檢測工具管理 §6.2

6.2 列表 / 下拉 menu

[POST] /detection-tool-profiles/list

Request(RequestMetaSchema 分頁慣例):

{
  "pager": {"page": 1, "page_size": 10, "with_total": true},
  "sort": [],
  "filters": {
    "detection_tool_uid": "<uid>",
    "source_type": "file",
    "scope": "SYSTEM",
    "name": "TWGCB",
    "is_active": true,
    "current_only": true
  }
}
filters 欄位 型別 說明
detection_tool_uid string|null 依所屬工具過濾
source_type string|null file / url
scope string|null SYSTEM / TENANT(FE 來源範圍 pill:全部=不帶)
name string|null 名稱 like 過濾
is_active bool|null load_default=true(預設只看未停用);FE 歷史開關打開時送 null
current_only bool load_default=true(預設只看現行版);歷史開關打開時送 false

Response data[]DetectionToolProfileResponseSchema):

欄位 型別 說明
uid string 對外識別
scope string SYSTEM / TENANT——FE 來源 Tag 與 fork 鈕條件的依據
detection_tool_id / detection_tool_code int / string|null 所屬工具;code 由 service 補算(FE 以工具目錄對照顯示名)
name / description / platform_hint string / string|null / string|null 顯示名稱 / 描述 / 適用平台標記(純顯示用,不參與過濾邏輯
source_type string file / url
file_id / file_name int|null / string|null 檔案型的 upload_files soft-ref 與原始檔名(service 批次補算)
url string|null 連結型的登記 URL
sha256 string|null 檔案型對原始壓縮檔 bytes 計算——Agent 取檔對帳的信任根;詳細 Dialog 以等寬字顯示
version / is_current / is_active int / bool / bool 版本模型三件套(§4 三態)
can_edit bool BE 算好的結論scope != 'SYSTEM' or 平台管理員,與 service guard 同一條規則)——FE 直接用,不自己重算
created_user / created_user_name / updated_user / updated_user_name string|null 審計欄位規範:login_name 配 nickname 成對回傳(app service 層批次查,_build_nickname_map:519-531system 映「系統」)
created_at / updated_at datetime %Y-%m-%d %H:%M:%S

排序固定:scope asc, name asc——'SYSTEM' < 'TENANT' 字典序,公用版天然排前(detection_tool_profile_repo_impl.py:89-92),FE 不另排序。

可見範圍是業務語意不是只靠 RLS:root 租戶的 allowed_tenant_paths 前綴涵蓋所有子租戶,只靠 RLS 的話平台管理員會看到全站每個租戶的私有設定檔;repo 顯式收斂 or_(scope=='SYSTEM', tenant_id==ctx.tenant_id):45-56),RLS 只是兜底。

出處:route detection_tool_profile_route.py:54-77、schema api/detection_tools/serializers/detection_tool_profile.py:13-61、service detection_tool_profile_service.py::list_profiles:84-112

[GET] /detection-tool-profiles/menu?detection_tool_uid=

detection_tool_uid 必帶(P7 分池:GCB 抽屜只該見 gcb 池,未帶回 400 DETECTION_TOOLS_400007)。只回 is_current=TRUE AND is_active=TRUE 的列(公用版+本租戶自有)。Response data[]DetectionToolProfileMenuResponseSchema):

欄位 型別 說明
uid / name string 識別與顯示名
value string 已組好的 profile:<uid> 下發格式——前綴規則只有 BE 一處知道(common/util/detection_profile_ref.py::PROFILE_REF_PREFIX),FE 直接放進任務 params.profile,不自己拼
scope / source_type / platform_hint string / string / string|null 供下拉呈現輔助

出處:route :80-97、service list_menu:114-125、repo list_menu:114-123

6.3 新增 / 登記

[POST] /detection-tool-profiles

同一 endpoint 收兩種 content-type(route _payload():43-51multipart/form-datarequest.form,否則取 JSON):檔案型走 multipart(檔案在 file 欄位、其餘欄位在 form;FE 送 FormData 時不手動設 Content-Type,讓 axios 自帶 boundary);連結型走 JSON。

欄位 型別 必填 說明
detection_tool_uid string 所屬工具(P7 必選;查無 → 404 DETECTION_TOOLS_404001
name string 同工具同租戶同名(現行)已存在 → 409 DETECTION_TOOLS_409006
source_type string file / url
file multipart file file 型是 打包檔——走下方驗證管線
url string url 型是 只驗 http(s):// 開頭形狀;不驗可達性、不代管憑證
description / platform_hint string 描述(≤2000 字 FE 限)/ 適用平台(≤50 字)
scope string 預設 TENANTSYSTEM 僅平台管理員送得動(service guard)——FE 現況固定送 TENANT(§12 坑 1)

檔案型驗證管線common/util/detection_profile_archive.py::ProfileArchiveValidator,streaming 全程不落地解壓——只讀 entry metadata、不呼叫任何 extract):

順序 檢查 失敗碼
1 副檔名白名單 .zip / .tar / .tar.gz / .tgz(最長後綴優先比對)+ magic number 雙重確認(zip=PK\x03\x04 含空包 / spanned 變體、gzip=\x1f\x8b、tar=offset 257 ustar)——防改副檔名的假檔 400 DETECTION_TOOLS_400008
2 大小 ≤ 50MB(Content-Length 預檢+實讀累計雙保險) 400 DETECTION_TOOLS_400009
3 逐 entry 安全檢查:路徑不得為絕對 / 含 .. 逸出(防 zip-slip);tar 的 symlink / hardlink 一律拒收;entry 數 ≤10,000、宣告解壓總量 ≤500MB、壓縮比 ≤200(1MB 以下小檔不看比值,防誤殺)。四類問題共用一個碼——細分會把防護細節洩漏給攻擊者 400 DETECTION_TOOLS_400010
4 結構驗證:頂層或一層目錄內須有 inspec.yml只驗結構不驗語意正確性——壞 profile 掃了會 fail、錯誤可回溯,平台不做「上傳即保證能掃」的承諾(設計界線,非缺陷) 400 DETECTION_TOOLS_400011
5 sha256 計算(對原始壓縮檔 bytes)→ 落 detection_tool_profiles.sha256回填 upload_files.sha256(上傳 adapter 只算 md5,_backfill_sha256:460-470
6 存儲:SYSTEM 走 upload_system_file()(root 租戶儲存+storage_scope=system)、TENANT 走一般上傳;save_dir="detection-profiles"

tar 系與 zip 的安全檢查共用同一套 entry 檢查器(僅迭代介面不同)——「按格式分支處理」正是 zip 路徑漏防的典型成因。source_type 與來源欄位互斥(file 無檔 / url 無字串 / 未知型態)→ 400 DETECTION_TOOLS_400012(DB chk_dtp_source_exclusive CHECK 是兜底,業務層先擋讓錯誤可讀)。

出處:route :100-125、service create_profile:133-194 / _resolve_source:404-432 / _store_file:434-458

6.4 版更 / fork / 複製到其他工具 / 停用

[POST] /detection-tool-profiles/{uid}/new-version

Request(全部選填;multipart / JSON 同 §6.3 分流):source_type / file / url / description / platform_hint。名稱與所屬工具不收(沿用舊版)。來源型態允許換(file → url 或反向);帶了新來源重跑驗證管線;全部未帶=只改 metadata 也算一次版更,沿用舊版 file_id / sha256 / url——讓「這一版對應哪個檔」在每一列上都是完整的(Agent 拿 payload 不必回溯前版)。寫入順序固定:先 demote_current 再 INSERT version+1uq_dtp_current_name 擋兩筆 current,順序是必要條件不是風格)。出處:service create_new_version:196-254

[POST] /detection-tool-profiles/{uid}/fork

Request:{"name": "<新名稱>"}(未帶沿用來源名)。scope='TENANT' / tenant_id=本租戶 硬寫;file_id / url / sha256 沿用來源列(共用同一 upload_files 實體——profile 檔不可變、版更=新列新檔,複製 binary 只會同內容存兩次)。同名檢查對本租戶池。出處:service fork_profile:256-294

[POST] /detection-tool-profiles/{uid}/copy-to-tool

Request:{"detection_tool_uid": "<目標工具>", "name": "<名稱>"}。目標工具=來源工具 → 400(FE 下拉已排除來源工具,copyToolOptions:387-389)。scope 沿用來源——把公用版複製到另一工具,結果仍是公用版(受眾沒變,只是換池),故此路徑_guard_system_writable(與 fork 的語意差異正在於此)。同名檢查對來源列的 tenant_id+目標工具池。出處:service copy_to_tool:296-343

[PUT] /detection-tool-profiles/{uid}/deactivate

無 body。is_active=FALSEis_current=FALSE(後者讓同名後繼者建得起來——留著 current 會撞 uq_dtp_current_namedeactivate_profile:345-360)。

Error code 一覽(本頁新增,common/code/detection_tools_error_code.py:56-90

情境 HTTP code
設定檔不存在 404 DETECTION_TOOLS_404005
同工具同名已存在 409 DETECTION_TOOLS_409006
壓縮格式不支援(副檔名 / magic number) 400 DETECTION_TOOLS_400008
超過 50MB 400 DETECTION_TOOLS_400009
壓縮檔不安全(slip / bomb / entry 數 / symlink,合併一碼) 400 DETECTION_TOOLS_400010
結構驗證失敗(找不到 inspec.yml 400 DETECTION_TOOLS_400011
source_type 與內容互斥違反 / URL 非 http(s) 形狀 400 DETECTION_TOOLS_400012
非平台管理員寫公用版 403 DETECTION_TOOLS_403001

FE error-code.json(zh-tw / en)已補齊上列八碼文案,上傳失敗由 axios interceptor 統一出 toast。

7. 前端檔案地圖(compliance-manager-fe/)

檔案 角色
src/views/detection-profile/DetectionProfileManageView.vue 主檢視(1,239 行):篩選列(scope pill / 工具下拉 / 搜尋 debounce / 歷史開關)、lazy 分頁 DataTable、新增(source_type 切換 file / url 欄位、multipart vs JSON 分流)、版更、fork、複製到其他工具、停用 confirm、詳細資料 Dialog、canWriteRow / 鎖 icon 呈現
src/service/DetectionToolProfileService.js API 包裝:listProfiles / listMenu / createProfile / createNewVersion / forkProfile / copyToTool / deactivateProfile——FormData 不手動設 Content-Type(缺 boundary 會 400)
src/composables/useDetectionProfileOptions.js 任務抽屜下拉用(非本頁):options_source: "profile_library" 欄位的 menu 選項載入——模組層級快取(同工具只打一次 API、in-flight promise 去重;規劃頁 N 個任務列共用)+invalidate()isProfileLibraryField() 宣告判定
src/components/detection-tools/DetectionConfigField.vue 動態欄位渲染元件(與檢測工具管理 / 任務配置共用):FR-059 起 options_source 宣告欄位的 options 改接 profile menu API(帶 detectionToolUid prop 分池);已停用參照補唯讀 fallback 選項、庫拉不到顯示警示但手填照常(見 §11)
src/config/router/index.js:630-634 route detection-profile-manage,path /plugin/detection-profile-manage
src/config/api/api.js:353-362 DETECTION_TOOL_PROFILE_LIST / _MENU / DETECTION_TOOL_PROFILES / _DETAIL / _NEW_VERSION / _FORK / _COPY_TO_TOOL / _DEACTIVATE 常數
src/config/locales/i18n/{zh-tw,en}/detection-profile-manage.json 頁面 i18n(detection_profile namespace,獨立檔)
src/config/locales/i18n/{zh-tw,en}/menu.json 選單標題「掃描設定檔管理」+描述
src/config/locales/i18n/{zh-tw,en}/error-code.json:186-193 上傳驗證八碼的使用者文案

8. 後端檔案地圖(本頁核心鏈路)

鏈路 Route App Service 底層
列表 / menu / 詳細 api/detection_tools/routes/detection_tool_profile_route.py::DetectionToolProfileListRoute / MenuRoute / DetailRoute app/detection_tools/service/detection_tool_profile_service.py::list_profiles / list_menu / get_profile(皆 @transaction domain/detection_tools/service/detection_tool_profile_domain_service.pyinfra/detection_tools/repository/detection_tool_profile_repo_impl.py(繼承 BaseRepositoryImpl,session lazy)
新增 / 登記 DetectionToolProfilesRoute.post create_profile(驗證管線 → 存儲 → INSERT) 同上+common/util/detection_profile_archive.py::ProfileArchiveValidator(DI 注入,di_containers/detection_tools/detection_tools_containers.py:53,81)+app/upload_file/service/managed_file_upload_service.py(canonical 儲存,upload_system_file / upload_files
版更 / fork / 複製 NewVersionRoute / ForkRoute / CopyToToolRoute create_new_version / fork_profile / copy_to_tool 同 repo(demote_current / find_by_exact_name
停用 DeactivateRoute.put deactivate_profile 同 repo
下發參照契約 —(非 HTTP) common/util/detection_profile_ref.py——PROFILE_REF_PREFIX / PROFILE_PARAMS_KEY / build_profile_ref() / extract_profile_uid() / build_payload()前綴判斷只有這一份,派工端與 menu DTO 都 import 這裡
ORM Model infra/detection_tools/model/detection_tool_profile.pyBaseModel, TenantScopedMixinModel——before_flush 自動填 tenant_id)

DDD 提醒:route 不碰 session、app service 全數 @transaction、repo 繼承 BaseRepositoryImpl;寫入守門 route 層 capability decorator(主體域)+service 層 scope guard(資源域)雙軌,符合 FR-048 規範。

9. DB

9.1 資料表總清單(本頁讀寫的全部表)

讀/寫 說明 欄位詳述
config.detection_tool_profiles 掃描設定檔庫本體(雙軌 scope+版本模型+來源雙軌) §9.3
public.upload_files (檔案型上傳時) 檔案實體 metadata(file_id soft-ref 目標;sha256 回填) 證據管理群
config.detection_tools 工具目錄(所屬工具綁定與 code 對照) 檢測工具管理 §9.3
config.detection_tool_param_schemas 不讀寫(關聯機制) profile 欄位的 options_source: "profile_library" 宣告落在此表新版 schema——本頁資料成為該宣告的下拉來源 任務配置 §9.3

9.2 ER 圖

dpm_er profiles config.detection_tool_profiles uid scope(SYSTEM / TENANT) tenant_id(SYSTEM 掛 root) detection_tool_id(分池) name / platform_hint source_type(file / url) file_id / url / sha256 version / is_current / is_active tools config.detection_tools id / uid / code name / status profiles->tools detection_tool_id soft-ref(P7 分池) files public.upload_files id / uid / file_name sha256(本案回填) storage_type / storage_scope profiles->files file_id soft-ref(file 型;fork 共用實體) schemas config.detection_tool_param_schemas detection_tool_id / version param_schema(profile 欄位帶 options_source=profile_library 宣告) is_current schemas->profiles options_source 宣告 → menu API 動態餵下拉 agent_tasks config.agent_tasks params.profile = profile:<uid> params._profile(派工展開快照: uid / version / sha256 / file_name 或 url) agent_tasks->profiles profile:<uid> 參照(派工當下展開) agent_tasks->files _profile.uid=upload_files.uid (agent 取檔端點認這個)
掃描設定檔庫 ER:detection_tool_profiles 以 detection_tool_id soft-ref 工具目錄、file_id soft-ref upload_files;param_schemas 以 options_source 宣告接庫;派工時 agent_tasks.params 內嵌 _profile 快照

9.3 核心表欄位(2026-08-01 DEV 實查)

config.detection_tool_profiles — 掃描設定檔庫

欄位 型別 說明
id / uid bigserial / varchar(36) NOT NULL UNIQUE 主鍵+對外識別
scope varchar(20) NOT NULL default 'TENANT' SYSTEM(公用版,owner 掛 root 租戶)/ TENANT(租戶自有)。CHECK chk_dtp_scopetenant_id IS NULL 永遠是壞資料(SYSTEM 列掛 root,不是 NULL)
tenant_id / org_unit_id bigint NOT NULL / bigint 擁有者租戶(TenantScopedMixinModel 自動填);org_unit_id 預留部門層級,本版邏輯只做租戶層
detection_tool_id bigint NOT NULL soft-ref config.detection_tools.id——分池綁定,上傳必選
name / description / platform_hint varchar(255) NOT NULL / text / varchar(50) 顯示名稱 / 描述 / 適用平台標記(純顯示用不參與過濾)
source_type varchar(10) NOT NULL file / url。CHECK chk_dtp_source_type;互斥 CHECK chk_dtp_source_exclusive(file 必有 file_id 無 url;url 反之)
file_id bigint soft-ref public.upload_files.id(file 型必填)
url text url 型必填;平台不驗證可達性
sha256 varchar(64) file 型對原始壓縮檔 bytes 計算——Agent 對帳信任根
version / is_current integer NOT NULL default 1 / boolean NOT NULL default TRUE 版本模型:版更=INSERT 新版+舊版降 FALSE
is_active boolean NOT NULL default TRUE 軟刪旗標(與 is_current 正交,但停用會同時降兩者,見 §12 坑 4)
created_user / updated_user / created_at / updated_at varchar(255) / varchar(255) / timestamptz / timestamptz 稽核欄位

唯一索引三組(皆含 version 或以 is_current 收斂——版更後同名多列並存是常態,全域唯一會在第一次版更撞索引):

  1. uq_dtp_tool_tenant_name_version (detection_tool_id, tenant_id, name, version)
  2. uq_dtp_system_tool_name_version (detection_tool_id, name, version) WHERE scope='SYSTEM' AND is_active(公用版跨租戶防撞名的防禦性第二道)
  3. uq_dtp_current_name (detection_tool_id, tenant_id, name) WHERE is_current AND is_active——「同名只有一個現行版」的真正保證

查詢索引:idx_dtp_tool_current_active(menu)、idx_dtp_tenant_scope(列表)。

RLS 四段 policydetection_tool_profiles_select / insert / update / delete):SELECT 公用版分支放最前(人人可讀)+super_admin+自己子樹;INSERT WITH CHECK 堵 SYSTEM 偽造;UPDATE USING+WITH CHECK 兩段都有(缺 WITH CHECK 的話租戶可把自己的列 UPDATE 成 scope='SYSTEM' 升格公用版);DELETE 非 super 不能刪 SYSTEM。app_tenant_allowed_for_session() 只接 integer,呼叫處對 bigint tenant_id 顯式 ::integer cast。

建表 migration:scripts/sql/2026-08-01-fr059-1-detection-tool-profiles.sql(含 GRANT cm_app+sequence);capability seed:2026-08-01-fr059-2-detection-profile-capability.sql;選單路由:2026-08-01-fr059-3-detection-profile-menu-route.sql三支皆只套 DEV,STG / POC 依環境異動鐵律等放行(§12 坑 12)。

10. 頁面邏輯與資料對應

載入時序onMountedfetchTools()(工具目錄——所屬工具欄顯示名與新增下拉的來源;拿不到不擋列表,只是工具欄退成 code、新增下拉為空)→ fetchProfiles()。搜尋 300ms debounce 重查並跳回第 1 頁;scope / 工具 / 歷史開關任一變動同樣重查跳頁。

關鍵欄位對應(畫面 ↔︎ API):

畫面元素 FE state API 欄位
列表 profiles / totalRecords POST /listdata / meta.total
來源 Tag 直接綁定 scope
所屬工具欄 toolNameByCode[data.detection_tool_code] detection_tool_codeGET /detection-tools 對照
狀態三態 Tag rowStatus(row) is_active × is_current
操作鈕顯示 canWriteRow(row) / fork 條件 can_editis_activeis_currentscope
操作鈕 disabled canCreate / canDelete 登入者 permissions(hasCap
詳細 Dialog detailDialog.row(列表列原資料,不另打 API) 同列表欄位(含 file_name / sha256 / url
更新者欄 直接綁定 updated_user_name(fallback updated_user

儲存流程:新增依 source_type 分流——file 組 FormData(欄位逐一 append、空值不帶)、url 組 JSON(空值帶 null)→ createProfile() → 成功 toast+關 Dialog+清 FileUpload+跳回第 1 頁重查。版更 / fork / 複製 / 停用成功後重查當前頁。

錯誤對應:BE error envelope → axios interceptor 依 error code 查 error-code.json 出 toast(八碼文案見 §6.4);Dialog 的 catch 留空不重複出錯。列表載入失敗另有本地 toast(toast_load_failed)並清空列表。

11. 背景行為與外部依賴

類型 內容
通知 無——本頁不觸發、不消費任何通知
Socket 無 socket / 輪詢;動作後重查
任務抽屜下拉動態化(本庫的消費端) param_schema 的 profile 欄位帶 "options_source": "profile_library" 宣告(inspec / gcb 已升 v2 帶宣告並拿掉靜態 options,migration 2026-08-01-fr059-3-param-schema-options-source.sql)→ FE DetectionConfigField 偵測宣告改呼 menu API(帶工具 uid 分池)動態組選項;機制 tool-agnostic,新工具 seed 宣告即接庫、FE 零改。詳見 任務配置 §5
派工展開profile:<uid>_profile 使用者選庫內設定檔發起執行時,params.profileprofile:<uid>派工當下detection_orchestration_service.py:197-207,非心跳組裝時)展開 _profile 內部 payload——file 型 {uid(=upload_files uid), version, sha256, source_type, file_name}、url 型 {uid, source_type, url}params.profile 原值保留(FE 執行紀錄顯示用);_profile 底線前綴被既有剝除機制擋掉不出 API(同 _credentials 慣例)。參照失效(查無 / 已停用)→ 400 明確報錯不靜默is_current 不檢查——歷史版本照使用者所選執行,凍結快照(version / sha256)正是稽核回溯要的
Agent 取檔授權 profile_ref resolver(app/detection_tools/service/agent_file_access_service.py:79-85):uid 是該 agent 名下 pending / running 任務 params._profile 引用的檔 → 放行 mTLS 取檔端點 GET /api/1.0/agents/files/<uid>(FR-058.7 通道,可插拔 resolver 清單)
Agent 端 cache evidence-agent core/profile_cache.pyinspec.py::_resolve_profile():file 型查 /data/content/cache/<uid>/v<version>/ 命中直用;miss → 拉檔 → sha256 對帳(不符即 fail)→ 安全解壓(agent 端同防 slip / bomb)→ 解壓後目錄餵 cinc-auditor(四格式一條路徑)。url 型 / 手填原樣入 argv(現行為零變化)。cache key 含 version → 版更自然失效;第一期無上限+手動清理
執行紀錄標籤對照 JobExecutionDrawer.vue::fetchProfileLibraryLabels:273-289:宣告欄位的值→顯示名對照另向 menu API 拿(param_schema 已無靜態 options),否則執行紀錄會露出 profile:<uuid> 裸值;拿不到退回顯示原值不擋畫面
搬遷 seed scripts/seed_2026-08-01_fr059_detection_profiles.py——8 支 TWGCB 打包走與使用者相同的上傳管線落 SYSTEM file 型+dev-sec linux / windows 基準 2 條落 SYSTEM url 型(名稱沿用原靜態選項 label,下拉零回歸);DEV 現況公用版 10 筆
jedi-* 套件 無新增依賴(@transaction / BaseRepositoryImpl / exception 為既有 jedi_common)
系統參數 無 feature flag;驗證上限(50MB / 500MB / 10,000 entry / 比 200)為 ProfileArchiveValidator 建構參數預設值,DI 可帶 config 覆寫

12. 邊界情況與已知坑

  1. 公用版新增無 FE 入口(現況):新增 Dialog 的 scope 寫死 TENANT——API 與 service guard 都支援 scope='SYSTEM'(平台管理員),但 FE 沒有渲染 scope 選擇欄位(i18n 已備 field_scope / hint_scope_system key 未使用)。公用版建立現況走 seed script;root 對既有公用版的版更 / 複製 / 停用則正常可用(can_edit=true)。要補公用版新增 UI 時 i18n 與 BE 都是現成的。
  2. detection-profile.update capability 是預留孤兒:四能力點全數 seed 且掛路由節點(read=ALL、寫入三個=ANY),但版更 / fork / 複製全是 INSERT 對 create、停用對 delete——沒有任何端點消費 update。日後若出現原地編輯類端點(如改描述不升版)再啟用;在那之前對角色勾 update 沒有實際效果。
  3. 讀端點僅 JWT 無 capability 守門:列表 / menu / 詳細只 @jwt_required()——menu 是任務抽屜全員要用的(刻意開放),列表與詳細跟著同粒度。可見範圍靠查詢收斂+RLS,不靠 capability;與寫入端點的守門粒度不一致,改動時勿混為一談(同 檢測工具管理 §12 坑 2 模式)。
  4. 停用同時降 is_current,FE 歷史開關必須一起放兩個條件deactivate_profileis_activeis_current 一起降 FALSE(留著 current 會讓同名後繼者撞 uq_dtp_current_name 建不起來)。因此 FE「顯示已停用與舊版」開關必須同時放掉 is_activecurrent_only——只放前者的話停用列仍被 current 條件濾掉、開關等於沒作用。代價是版更後的舊版一起現身,狀態欄因此做三態(啟用中 / 舊版 / 已停用)不把舊版誤標成啟用中。
  5. fork 與複製到其他工具共用檔案實體不複製 binaryfile_id / sha256 沿用來源列——profile 檔不可變(版更=新列新檔),同內容存兩次沒有意義。含意:來源公用版停用不影響 fork 列(upload_files 實體仍在);反向也一樣。
  6. fork 不走 SYSTEM guard、複製到其他工具走:fork 寫的是新租戶列(scope 硬寫 TENANT);複製沿用來源 scope——公用版複製到另一池結果仍是公用版,故需平台管理員。兩者語意差異就在這裡,改動守門時別對齊成同一套。
  7. 派工不檢查 is_current 只檢查 is_active:任務綁定存的是使用者當初選的那一版 uid,版更後舊版仍是合法歷史版本,掃描照使用者所選執行(要換版是使用者自己回任務改選,不是 BE 偷偷跳版);停用才是「不該再被使用」的管理決策。參照失效(查無 / 停用)→ 派工當下 400 明確報錯,不原樣下發(agent 會拿到無法解析的 profile:<uid> 變成 agent 端神祕失敗)、不默默拿掉(使用者以為在掃 A 基準實際參數沒了)。
  8. profile: 前綴契約單點:menu 的 value 已是 BE 組好的 profile:<uid>,FE 不自己拼前綴;BE 端前綴判斷只有 common/util/detection_profile_ref.py 一份,派工展開不可自己寫 startswith。手填值不可能以 profile: 開頭——這是三種取值來源(庫內 / 手填 / 舊靜態選項值)零歧義共存的基礎。
  9. _profile.uid 對 file 型是 upload_files 的 uid 不是 profile 列的 uid:agent 拿它直接打取檔端點(該端點認的就是 upload_files uid);url 型無檔案實體,uid 才是 profile 列自己的。讀 payload 時別搞混兩種 uid。
  10. 上傳驗證是第一道不是唯一一道:bomb 檢查靠 entry 自報的宣告 size(header 值理論上可偽造)——偽造後 agent 端真解壓時與宣告不符會失敗,且 agent 端另有一套同等的 slip / bomb 防護(BE 驗過不代表可信任傳輸後內容)。本驗證器是全 repo 第一個壓縮檔安全實作,之後任何要收壓縮檔的功能應來 common/util/detection_profile_archive.py 復用,不要各自再寫一套。
  11. url 型平台不驗證可達性、不代管憑證:登記時只驗 http(s):// 形狀。連結打不打得通、要不要憑證,是使用端網路與授權問題——此界線在設計即明寫(含 FE hint 文案),別當缺陷回報;需求出現再立案。
  12. ⚠️ 本功能現況只在 DEV:表 / capability / 選單路由 / param_schema 升版 / 搬遷 seed 全數只套 DEV(環境異動鐵律)。param_schema 升版 migration 尤其不可單獨誤套 STG / POC——它拿掉靜態 options,而該兩環境沒有搬遷 seed 資料,套下去任務抽屜下拉會直接空掉;必須整批依序等決策者放行。
  13. 同名檢查是業務層前置、唯一索引是兜底find_by_exact_name 查同租戶同工具現行名稱;公用版與某租戶自有版可以同名(不同 tenant_id 池)——fork 預設沿用原名能成立正是因為這點。

13. 開發與驗證

  • 跑起來:BE python main_socketio.py(port 8000,log 在 log/app.log);FE 在 compliance-manager-fe/ 起 Vite dev server。BE 改 service code 後必須重啟
  • 測試帳號:dev 環境持 detection-profile.create / delete capability 的帳號(比照流程範本管理授予的租戶系統管理員角色;公用版維護需 root 租戶平台管理員),密碼見 .env / 部署文件
  • 導航路徑:登入 → 合規稽核 → 「掃描設定檔管理」(/plugin/detection-profile-manage,選單掛在檢測工具管理與流程範本管理之間)
  • 前置資料config.detection_tools 已 seed(工具下拉來源);公用版 10 筆由 scripts/seed_2026-08-01_fr059_detection_profiles.py 落庫(需 BE 環境可連 DB 執行);上傳測試可用 content/detection-profiles/ 下任一 TWGCB 目錄自行打包 tar.gz
  • 驗證要點:上傳四格式正常樣本全過、惡意樣本(假副檔名 / 超大小 / traversal / 無 inspec.yml)各回獨立 error code;版更後 menu 指新版且舊版留存;fork 後副本可編輯;非 root 對公用版寫入 403;租戶 A 看不到租戶 B 自有列(RLS);任務抽屜 GCB 池只見 gcb、CINC 只見 inspec
  • E2Ecompliance-manager-test/ repo(Cucumber + Playwright);以 detection-profile / 掃描設定檔 搜尋
  • 相關文件:需求討論稿(決策脈絡與 mermaid 流程圖)docs/features/FR-059-2608-detection-profile-library/discussion.html;設計決策全文(P1–P7 + D1–D10)同資料夾 design.md;下拉動態化與三層取值來源見 任務配置;執行紀錄的 profile 標籤對照見 我的任務 §11.1;工具目錄與 param_schema 兩層 schema 關係見 檢測工具管理;使用手冊 docs/user-manual/scan-profile-guide.md