FR-059 檢測工具 Profile / Content 庫(掃描設定檔後台管理)— 設計文件

狀態:設計定稿(P1–P7 + D1–D10 共 17 項全數拍板)|建立日期:2026-08-01|FR-058 檢測工具擴充續作 討論稿(含流程圖):discussion.html Notion 開卡:尚未進行——等 FR-058.7 開卡完成後接續(避免 Case No 跳號);屆時 CM-992 需關聯本 FR

變更紀錄

日期 變更 對應
2026-08-01 初版設計定稿:P1–P7(討論稿原樣)+ D1–D10 全數拍板。其中 D1 附 RLS 前例事實修正(討論稿「config schema 首例」說法有誤);D4 為決策者修訂版(收 zip / tar / tar.gz / tgz 四格式 + agent 端統一解壓目錄交付,與討論稿原建議不同) FR-059 母案
2026-08-01 resolver 介面契約定案(058.7 協調者已下達 T-7.1:(agent_uid, file_uid) callable + _FILE_ACCESS_RESOLVERS 清單);T-2.3 拒絕碼改為以端點實際落地為準 D6
2026-08-01 §5.2 capability 勘誤(T-1.2 實作 session 發現矛盾、決策者拍板):寫入 capability 由「參照 compliance-framework 不對稱模式(is_platform=true)」改為四個全 is_platform=false 比照 flow_template 平掛——原引用適用於純系統資產,profile 庫是租戶可寫雙軌資源,套用會使租戶自助全滅、service guard 成死碼。CM-1013 卡同步更正 §5.2

1. 案件基本資料

項目 內容
FR 編號 FR-059-2608-detection-profile-library
標題 檢測工具 Profile / Content 庫(掃描設定檔後台管理)
日期 / 狀態 2026-08-01|設計定稿(決策 P1–P7 + D1–D10 全數拍板)
關聯 FR-058(母脈絡)/ FR-058.7(agent 取檔通道依賴,該案先行)/ Notion CM-992(範圍③「後台 content 管理機制」由本案承接)/ FR-039(agent 安全基建與不變式)
討論稿 discussion.html(同資料夾)
Notion 開卡 尚未進行——等 FR-058.7 開卡完成後接續,避免 Case No 跳號

2. 需求背景與端到端流程

2.1 現況:每加一支 profile 的四步人工維運鏈

掃描工具(第一期 CINC Auditor / InSpec 與 GCB)執行時需要 profile / content 檔作為掃描依據。以 GCB(TWGCB 政府組態基準)為例,目前一支新 profile 要走完這條鏈才會出現在使用者的下拉選單裡:

步驟 動作 誰做得到
1 profile 原始碼進 BE repo content/detection-profiles/ 版控 開發者(要會 git + InSpec)
2 人工 rsync -a --delete每一台 agent 主機 /opt/evidence-agent/deploy/content/ 有主機 SSH 權限的工程師
3 寫一支 migration 改 param_schema 的 options(加一個容器內路徑選項) 開發者(要懂 param_schema 版本紀律)
4 migration 依環境異動鐵律套 DEV → 決策者放行後 STG / POC 開發者+決策者

四步裡沒有任何一步是一線工程師或客戶端管理員做得到的。現況投放狀態也印證維運成本:8 支 TWGCB profile 只投放到 DEV agent(192.168.50.123),STG / POC 的 agent 根本沒有這些檔案——選了下拉選項也掃不動。

2.2 CM-992 記錄的兩個限制(立案理由)

  • 限制①:封閉網路無法投放 profile——封閉網路客戶的 agent 連不了外網,只能用「agent 本機路徑」型 profile;但目前沒有任何機制能把 profile 檔送進 agent container(content 掛載是人工 rsync,客戶做不來)。本案解法:profile 上傳到平台 → agent 透過既有 mTLS 通道拉檔+cache,檔案遞送不再依賴外網或人工投放。
  • 限制②:私有版本庫無憑證路徑——CINC 原生支援 URL 取 profile,但 params.profilesecret:false 純文字,token 塞進 URL 會洩漏到 FE 執行紀錄與稽核 log,私有 repo 的 profile 無路可走。本案解法:私有 profile 直接打包上傳成租戶自有 profile,不再需要從私有 repo 拉;URL 憑證機制明列不做(P5),需求出現再立案。

2.3 目標狀態

agent 只升級一次(學會「拉檔+cache」),之後 content 的新增 / 改版 / 停用全部在後台完成——不再重建 agent image、不再人工 rsync、不再為選項寫 migration。公用版由 root 租戶維護全租戶可見,各租戶也能上傳自己的 profile,模式與流程管理(flow_templates)的雙軌完全一致。

2.4 端到端流程(文字版)

管理者在「掃描設定檔管理」頁上傳 profile(zip/tar/tar.gz/tgz 或登記 URL)
  → BE 驗證(格式 / 大小 / 結構含 inspec.yml / 防 slip・bomb)→ 計算 sha256 → 存平台儲存 + 落 config.detection_tool_profiles
  → 任務抽屜 profile 下拉動態列出庫內項目(按 detection_tool_id 過濾)
  → 使用者選庫內 profile 發起執行(params.profile = "profile:<uid>")
  → 派工 _collect_pending_tasks() 偵測前綴、展開 _profile 內部 payload(uid/version/sha256/...)
  → agent 心跳領工 → 查本機 cache /data/content/cache/<uid>/v<version>/
      命中 → 直接用;miss → GET /api/1.0/agents/files/<uid>(FR-058.7 通道)拉檔
      → 重算 sha256 對帳(不符即 fail)→ 安全解壓進 cache 目錄
  → 把解壓後「目錄」餵給 cinc-auditor(四格式一條路徑,D4)→ 掃描 → 報告走既有 blob 回收鏈

3. 決策定案(P1–P7 + D1–D10,共 17 項)

P1–P7 為前期已拍板事項(討論稿 §2 原樣);D1–D10 於 2026-08-01 全數定案。

# 主題 定案內容 被排除方案與理由
P1 範圍 第一期只做**形態 A(agent 本機讀取型),涵蓋 inspecgcb 兩工具。資料模型帶 detection_tool_id 保持 tool-agnostic——未來擴充其他形態只加下發策略、不改表 OpenSCAP 排除**(content 在目標主機、客戶用 apt/dnf 自裝、有上游 SSG 版本節奏問題);ZAP 排除(scan policy 放在客戶自己的 ZAP daemon 裡,平台碰不到)
P2 取檔通道 復用 FR-058.7 建立的 GET /api/1.0/agents/files/<uid>(純 mTLS+任務綁定驗證+串流不落地,該案 D25),不重建;本案增量=授權 resolver 多一種 profile_ref claim(見 D6) 自建第二條通道——重工且違反 FR-058「不做抽象層」定案
P3 存量搬遷 既有 8 支 TWGCB profile 搬入庫成 SYSTEM 公用版 seed——新機制上線第一天下拉內容與今天完全一致(零回歸);搬完退役「migration seed 選項+人工 rsync」舊路(時程見 D5) 不搬遷——上線即造成下拉回歸,且兩套維運方式並存無收斂點
P4 版本模型 照抄 detection_tool_param_schemasversionis_current 單現行版;版更=INSERT 新版+舊版 is_current=FALSE,不原地 UPDATE 「任務綁舊版」的 group+version 模型——任務永遠用現行版即可,歷史版本留表內供回溯,不引入綁版複雜度
P5 型態雙軌 `source_type = file urlfile=上傳打包檔存平台儲存、agent 走通道拉+cache;url`=只登記 URL 字串、agent 原樣交給 cinc-auditor、平台完全不經手檔案。URL 的憑證 / 認證 / 權限一概不管——打不打得通是客戶自己的網路與授權問題,平台不驗證、不代管憑證;此界線明寫,避免日後被當缺陷回報,user 有反應再立案
P6 手填保留 欄位型別維持 select_or_text——下拉改由 profile 庫動態餵,使用者仍可現場手打 URL 或容器路徑(不進庫、單次執行用)。取值來源三層並存:庫內 file 型 / 庫內 url 型 / 現場手填;既有任務存的手填值升級後照常可用(相容性零破壞) 改純 select——破壞既有手填相容,且封殺臨時性 ad-hoc 掃描的彈性
P7 工具隔離 CINC 與 GCB 的 profile 池分開,按 detection_tool_id 綁定與過濾:上傳時必選所屬工具;GCB 任務抽屜只看得到 gcb 的 profile,CINC 同理。同一份檔兩工具都要用就各登記一筆,提供「複製到另一工具」動作 跨工具共用池——會造成「GCB 池混進通用 baseline」的語意污染
D1 表落點與命名 config.detection_tool_profiles——語意上 profile 庫是 detection_tools 家族的延伸,放 config 最自然;RLS policy 是表級設定與 schema 無關。事實修正:討論稿稱「本表帶 RLS 是 config schema 首例」有誤——config.tenant_detection_tool_configs 本來就有 RLS(policy tdtc_tenant_isolation,見 scripts/sql/2026-07-26-fr056-1-detection-tools-config-schema.sql:65),config schema 早有前例,此顧慮不成立 compliance schema——跨 schema 拆家族反而讓 detection_tools 的讀者找不到;「RLS 慣例一致」的動機在前例修正後也不存在
D2 庫參照下發格式 profile:<uid> 顯式前綴字串存在 params.profile,派工時展開。與手填值零歧義(手填不可能以 profile: 開頭);單欄位不動 FE 主參數顯示邏輯(_PRIMARY_PARAM_KEYS);派工展開時前綴判斷一行搞定 UUID 裸值格式判別——手填值理論上可以長得像 UUID(低機率但非零),靠格式猜測是隱性契約;雙欄位(另開 profile_ref——要處理互斥驗證與 FE 顯示分流,成本高於前綴
D3 FE 管理頁入口 系統管理下獨立新頁「掃描設定檔管理」——tenant 管理員與 root 用同一頁,靠 scope 區分能力(公版對非 root 唯讀)。雙軌管理語意與流程管理一致(流程管理也是獨立頁) 工具管理頁(tool-plugin-manage)內嵌 tab——工具管理頁是純平台目錄頁(root 專用、受眾不同),內嵌 tab 會把租戶級功能塞進平台級頁面,權限模型混濁
D4 上傳格式與大小(決策者修訂 zip / tar / tar.gz / tgz 四種常用打包格式、大小上限 50MB。設計含意兩點:① BE 驗證層用 tarfilezipfile 雙支援做結構檢查(找 inspec.yml)與安全防護——path traversal / bomb 兩種格式都要防,streaming 檢查不落地解壓;② agent 端交付統一化:不按格式分支餵 cinc-auditor,agent 拉檔後一律安全解壓到 cache 目錄、把目錄餵給 cinc-auditor(CINC 原生吃目錄),四種格式一條路徑、行為一致好除錯。cache 目錄結構 /data/content/cache/<uid>/v<version>/(存解壓後內容) 討論稿原建議「僅收 .tar.gz / .tgz」——決策者裁示擴大到常用打包格式、降低使用門檻;「按格式分支處理(tarball 直接餵、zip 才解壓)」——多格式多路徑行為不一致、難除錯
D5 舊路退役時程 過渡一版——新機制上線驗收通過後,下一版拿掉 agent 的 /data/content bind mount 與 param_schema seed 的靜態 options。過渡期兩路並存(舊容器路徑選項照常可用),確認庫路徑穩定再收。手填容器路徑能力因 P6 永遠保留——舊路徑字串日後仍可手填使用,只是平台不再維護該目錄內容 立即退役——庫路徑未經線上驗證即斷舊路,風險不對稱
D6 058.7 通道增量(跨案協調項) 通道端點共用、授權 resolver 按檔案用途分流——source_file(058.7:一次性輸入檔)與 profile_ref(本案:任務 params 中引用的 profile 檔,cache miss 拉檔也發生在任務執行中)兩種 claim 各自驗證;本案負責 profile_ref resolver 的實作。🔴 跨案依賴提醒:058.7 實作時須把授權驗證做成可插拔的 resolver 介面(而非硬編 source_file 查詢),本案接入成本才最低——此協調應在 058.7 開工前談定,寫進其 T-7.1 規格 另開專用端點——通道重複建設,違反 P2
D7 URL 型公版搬遷 dev-sec linux-baseline / windows-baseline 兩條公開 tarball URL 搬成 SYSTEM url 型公版,與 P3 的 TWGCB 搬遷同批 seed——上線第一天 CINC 下拉也零回歸,順帶成為 url 型的首批示範資料(驗證 url 型下發路徑) 不搬——CINC 下拉回歸,且 url 型上線時無示範資料可驗證
D8 上傳驗證深度 只驗「結構+檔案安全」——壓縮檔可開、頂層或一層內有 inspec.yml、大小 / entry 數 / 解壓比防 bomb、entry 路徑防 slip(streaming 檢查不落地解壓)。profile 正確性由掃描執行結果反映(壞 profile 掃了會 fail,錯誤訊息可回溯)——此界線在文件明寫,不做「上傳即保證能掃」的承諾。BE 裝 CINC 做 pre-check 列未來選項 上傳即跑 cinc-auditor check——BE 端沒裝 CINC,第一期不引入該執行環境
D9 agent cache 管理 第一期無上限+手動清理指引——單支 profile 幾百 KB~幾 MB,量級離磁碟壓力很遠;cache key 含 version版本更新自然失效(舊版目錄不再被引用);agent 重啟不清(磁碟 volume 保留,避免每次重啟全量重拉)。LRU 上限(如 2GB)列 follow-up 第一期即做 LRU 上限——量級尚遠,過早最佳化
D10 三環境節奏(提醒項) 環境異動鐵律的提醒,非新決策——DEV 先行驗收,STG / POC 一律等決策者明示放行才套 migration 與 seed;搬遷 seed(P3 / D7)與 param_schema 升版都適用。附帶效益:STG / POC 的 agent 目前沒有 profile 檔(§4),新機制上線後它們第一次有了可用的 GCB 掃描能力——放行時程可跟這個效益一起評估 (無替代方案——鐵律)

4. 現況接入點盤點

相關 repo 皆已實查(附檔案座標)。結論:雙軌資料模型、檔案儲存、agent 安全通道全有現成拍板模式可抄;真正的新造點集中在「profile 庫本體」「派工展開」「agent cache」「上傳安全驗證」四處。

元件 現況 本案動作
profile 實體 BE repo content/detection-profiles/(8 支 TWGCB+gcb-demo-win);產生器 content/detection-profiles/tools/twgcb2inspec.py(讀 NICS 官方 .docx 產出 InSpec profile)。content/detection-profiles/README.md 有完整說明,含「為什麼不放 agent repo」:agent Dockerfile 最後一行 COPY . .,放進去就違反 FR-058 D7「content 不可打包進 agent image」 8 支打包上傳成 SYSTEM seed(§5.6);此目錄過渡一版後不再是投放通道(D5),保留作 profile 原始碼版控與產生器工作區
投放 人工 rsync -a --delete 到 agent 主機 /opt/evidence-agent/deploy/content/,靠 evidence-agent/deploy/docker-compose.yml:82 的唯讀 bind mount - ./content:/data/content:ro 讓容器看到。只投放 DEV agent(192.168.50.123),STG / POC 未投放 由 agent 拉檔+cache 取代(§5.4);bind mount 過渡一版後拿掉(D5)
下拉選項 config.detection_tool_param_schemasparam_schema.profile 欄位 type=select_or_text。GCB 的 8 個選項是容器內路徑(/data/content/<dir>),由 migration scripts/sql/2026-07-31-fr058-18-twgcb-os-profiles-8-options.sql:83-91 硬 seed;CINC 的 2 個選項是公開 tarball URL(dev-sec linux / windows-baseline)。options 是建議值非白名單(手填任何字串都會被原樣下發) options_source: "profile_library" 宣告+param_schema 升版拿掉靜態 options(§5.5)
執行 agent connector evidence-agent/core/task_executor_connectors/inspec.pyparams.profile 原樣塞進 cinc-auditor exec argv。CINC 原生支援三種 content 來源:網址 / agent 本機路徑 / Supermarket 名稱 _profile 展開:file 型改餵 cache 解壓目錄(§5.4、D4);url 型 / 手填現行為不變
架構前提 CINC Auditor 裝在 agent 本機、由 agent 連出去掃目標(SSH / WinRM),profile 由 agent 本機讀取後交給 cinc-auditor,不送到目標主機——與 OpenSCAP(引擎與 content 都在目標主機)方向相反(FR-058 design.md:363-370) 約束:content 遞送設計不可用 OpenSCAP 心智模型
派工鏈路 使用者按執行 → detection_orchestration_service.start_execution()agent_tasks(pending) → agent 心跳 POST /api/1.0/agents/heartbeat(mTLS)回應夾 pending_tasks(每筆 {uid, detection_tool_id, detection_tool_code, params, credentials})→ agent ack → connector run → 報告走 blob 上傳回收。心跳間隔預設 300 秒 可複用:在 _collect_pending_tasks()app/remote_agent/service/agent_enrollment_service.py:241-249)組 payload 階段加庫參照展開(§5.4)
agent 面控制端點 api/remote_agent/__init__.py:34-38 僅四支:register / heartbeat / tasks/<uid>/ack / tasks/<uid>/result,純 mTLS 不掛 jwt。agent 從 BE 拉檔的通道目前不存在——由 FR-058.7 D25 新建 GET /api/1.0/agents/files/<uid>(純 mTLS、任務綁定驗證、串流回傳 BE 不落地;agent 端加 _get_stream() 復用(P2);本案新增 profile_ref 授權 resolver(D6、§5.4)
既有安全基建 infra/upload_file/remote_agent_adapter.py 一整套:mTLS context(common/util/agent_auth/tls.py:build_cloud_mtls_context)、短效綁定 JWT(common/util/agent_auth/jwt_util.mint)、回應指紋比對、SHA-256 雙邊對帳(雲端自算為信任根、下載重算比對、不符 → audit FILE_INTEGRITY_TAMPER_DETECTED 直接沿用(sha256 對帳模式照搬到 agent 拉 profile 檔路徑)
FR-039 不變式 docs/features/FR-039-2606-distributed-file-agent/design.md:284-291:雲端不落地 binary(串流)、agent 不連雲端 DB、完整性信任根=雲端 sha256 約束:本案所有設計不得破壞(參照由心跳 payload 下發、agent 拉檔後重算對帳)
公用 / 租戶雙軌模式 docs/analysis/2026-06-29-tenant-scope-data-model.md Category C 已定案:scope('SYSTEM'|'TENANT')+tenant_id NOT NULL(SYSTEM 掛 ROOT(1);tenant_id IS NULL 永遠是壞資料);Model 繼承 BaseModel, TenantScopedMixinModel;RLS 四段 policy 範本 scripts/sql/2026-06-29-fr042-tenant-scope-module-frames-flow-templates.sql:41-72(module_frames 版);程式層三層防禦抄 flow_templates(route capability 參照 2026-06-30-compliance-framework-platform-capability.sql 的「read is_platform=false、create/update/delete=true」不對稱模式;service 層 guard 抄 app/flow_engine/service/flow_template_app_service.py:187-197 _guard_builtin_writable);root 判定 canonical=common/authz/platform.py viewer_is_platform_admin();唯一索引形狀抄 2026-05-12-flow-template-management-schema.sql:70-73 照抄不重議(§5.1、§5.2)。RLS 前例事實(D1 修正):config schema 已有 RLS 前例(config.tenant_detection_tool_configstdtc_tenant_isolation policy),本表帶 RLS 並非首例
檔案儲存 canonical 上傳 service app/upload_file/service/managed_file_upload_service.py——TENANT scope 用 upload_files_for_tenant()(:281)、SYSTEM 公版用 upload_system_file()(:298,storage_scope=system 存 root tenant 儲存)。後端三種:local / minio / remote_agent(無 S3)。metadata 表 public.upload_files 單表(uid / file_name / save_file_name / size / sha256 / storage_type / storage_scope,無 tenant_id 欄——隔離靠存進哪個租戶的後端);業務表存 upload_files.id soft-ref(範本:infra/flow_engine/models/job_evidence.py:41 直接複用,不另造儲存抽象
使用者面下載 既有 GET /file/download/<uid>+signed token(TTL 120 秒);agent 拿不到 signed token(換 token 端點掛 @jwt_required 不適用 agent——agent 必須走 P2 的專用通道
上傳驗證現況 各 service 手寫「副檔名+seek 大小」三步(如 app/grc/service/ap_docx_import_app_service.py:74-79),無 magic number 檢查、無 zip-slip / bomb 防護(全 repo 零處理) 本案新造點:四格式結構驗證(內含 inspec.yml)與解壓安全防護(§5.3)

5. 詳細設計

5.1 資料模型

新表 config.detection_tool_profiles(D1),Category C 雙軌形狀+P4 版本模型:

欄位 型別 / 約束 說明
id / uid BIGSERIAL / UUID 標準主鍵+對外識別
scope VARCHAR(20) NOT NULL DEFAULT 'TENANT' 'SYSTEM'(公用版,root 維護)| 'TENANT'(租戶自有)——Category C 拍板形狀
tenant_id / org_unit_id BIGINT NOT NULL / BIGINT TenantScopedMixinModel 自動填;SYSTEM 列掛 ROOT(1)。tenant_id IS NULL 永遠是壞資料
detection_tool_id BIGINT NOT NULL soft-ref config.detection_tools;inspec 與 gcb 各自綁定(P7)
name / description VARCHAR / TEXT 顯示名稱與說明
platform_hint VARCHAR NULL 適用平台標記(如 windows / linux),純顯示用不參與過濾邏輯
source_type VARCHAR(10) NOT NULL 'file' | 'url'(P5 雙軌)
file_id BIGINT NULL soft-ref upload_files.idsource_type=file 時必填
url TEXT NULL source_type=url 時必填;平台不驗證可達性(P5 界線)
sha256 VARCHAR(64) NULL file 型上傳時計算(對原始壓縮檔 bytes),為 agent 對帳的信任根(FR-039 不變式)
version / is_current INTEGER NOT NULL DEFAULT 1 / BOOLEAN NOT NULL DEFAULT TRUE P4 版本模型:版更=INSERT 新版+舊版 is_current=FALSE
is_active BOOLEAN NOT NULL DEFAULT TRUE 軟刪(停用後下拉不出現,歷史執行紀錄參照仍可回溯)
稽核欄位 created_user / updated_user / 時間戳 API 回傳時依專案規範 enrich *_user_name(nickname,見 §5.2)

CHECK 約束(source_type 與來源欄位互斥成對):

  • source_type='file'file_id IS NOT NULL AND url IS NULL
  • source_type='url'url IS NOT NULL AND file_id IS NULL

唯一索引三組(形狀抄 2026-05-12-flow-template-management-schema.sql:70-73):

  1. (detection_tool_id, tenant_id, name, version) unique——租戶內同工具同名同版唯一
  2. 公版名稱全域唯一 partial index(WHERE scope='SYSTEM' AND is_active
  3. 同名 is_current=TRUE 僅一筆的 partial unique——版本模型完整性

RLS 四段 policy:直接抄 scripts/sql/2026-06-29-fr042-tenant-scope-module-frames-flow-templates.sql:41-72(module_frames 版)——SELECT 的 scope='SYSTEM' 分支獨立放最前(公版人人可讀)+super_admin+path 子樹;INSERT WITH CHECK 堵 SYSTEM 偽造;UPDATE 要 USING+WITH CHECK 兩段(防租戶把自己的列升級成公版);DELETE 非 super 不能刪 SYSTEM。helper app_tenant_allowed_for_session() 已存在直接呼叫。

Migration 鐵則:新表必加 GRANT ... TO cm_app+sequence 權限;語句加日期註解+檔頭 -- Date:;收尾 INSERT public.schema_migrations;套用節奏依 D10(DEV 先行)。

ORM Model:繼承 BaseModel, TenantScopedMixinModel(jedi_common before_flush listener 自動填 tenant_id;SYSTEM 列由 root session 建立自然掛 ROOT);Repo 繼承 BaseRepositoryImpl(session lazy,禁止 __init__get_session())。

5.2 API 設計

管理頁六個動作(實際 route 命名與 schema 於 plan 階段依 docs/claude/api-patterns.md 定稿):

動作 方法(草案) 行為
列表 POST /detection-tool-profiles/list(分頁,RequestMetaSchemaEnvelopeSchema 公版+自有合併查詢 or_(tenant_id==ctx.tenant_id, scope=='SYSTEM'),公版排前面;欄位含 scope / 工具 / source_type / 版本 / 狀態;DTO 回 scope 讓 FE 決定編輯鈕(公版對非 root 唯讀)
下拉 menu GET /detection-tool-profiles/menu?detection_tool_id=List[MenuDto] 形狀,不包 PageDataDto) 任務抽屜下拉用:只回 is_current=TRUE AND is_active=TRUE,帶 detection_tool_id 過濾(P7);回 uid / name / source_type / platform_hint
上傳 / 登記 POST(multipart 或 JSON) 選工具(P7 必選)→ 選 source_type → file 走 §5.3 驗證管線後存儲(SYSTEM 公版走 upload_system_file()、TENANT 走 upload_files_for_tenant());url 只登記字串(P5)
版更 POST /<uid>/new-version 同名 INSERT 新 version+舊版 is_current=FALSE(P4 紀律);file 型換新檔重算 sha256
fork + 複製到另一工具 POST /<uid>/fork/POST /<uid>/copy-to-tool fork=公版複製成租戶自有可編輯副本(硬寫 scope='TENANT', tenant_id=ctx.tenant_id);複製到另一工具=同內容在另一 detection_tool_id 池各登記一筆(P7)
停用 PUT /<uid>/deactivate 軟刪 is_active=FALSE,下拉即不出現;歷史執行紀錄參照可回溯

三層防禦守門(抄 flow_templates 模式,§4 盤點):

  1. Route capability:讀寫四個 capability 全部 is_platform=false(比照 flow_template 平掛)——租戶管理員可建 / 改 / 停用自己的 TENANT profile 與 fork 公版,這正是雙軌功能的核心;「公版只有 root 能動」交給第 2 層 service guard 與第 3 層 RLS,不在 capability 層鎖(實際 capability 命名與角色矩陣掛法 plan 階段定;守門一律走 common/authz/,禁止另立 helper)。勘誤(2026-08-01,實作 session 發現):初稿此處誤引 2026-06-30-compliance-framework-platform-capability.sql 的「寫入 is_platform=true」不對稱模式——那適用於「整個資源寫入天生 root-only」的純系統資產(框架目錄);profile 庫是租戶可寫的雙軌資源,套該模式會讓租戶自助全滅、service guard 淪為死碼。
  2. Service 層 guard:抄 app/flow_template_app_service.py:187-197 _guard_builtin_writable 模式,改判 scope=='SYSTEM' and not viewer_is_platform_admin()ForbiddenError(root 判定 canonical=common/authz/platform.py viewer_is_platform_admin(),不硬編 id)。
  3. DB RLS 兜底(§5.1 四段 policy)。

審計欄位 enrich:response 有 created_user / updated_user(login_name)時必同時回 created_user_name / updated_user_name(nickname)——在 app service 層批次查 User.login_name → User.nickname,不在 infra 層 JOIN(pattern 見 app/oscal/service/ssp_docx_import_app_service.py:_enrich_party_user_org_names())。

Error code:遵循 CLAUDE.md「<模組前綴>_<HTTP狀態碼><序號>」命名規則,落在 detection_tools 家族檔 common/code/detection_tools_error_code.py(前綴 DETECTION_TOOLS_,序號接續既有)。需新增(草列,plan 階段定稿序號):

情境 HTTP 例外類別
profile 不存在 404 NotFound
同工具同名已存在 409 ConflictError
上傳格式不在四格式白名單(副檔名 / magic number 不符) 400 BadRequestError
超過 50MB 大小上限 400 BadRequestError
壓縮檔不安全(path traversal / bomb / entry 數超限) 400 BadRequestError
結構驗證失敗(找不到 inspec.yml 400 BadRequestError
source_type 與 file / url 欄位互斥違反 400 BadRequestError
非 root 對 SYSTEM 公版寫入 403 ForbiddenError

DDD 分層:route 不碰 session;app service public method 一律 @transaction;DB 操作走 domain service → repository interface → infra 實作;DI 於 di_containers/ 對應 container wiring。

5.3 上傳驗證管線(本案新造點)

現況全 repo 無壓縮檔安全處理先例(§4),本管線為第一個實作,設計如下:

格式偵測:副檔名白名單(.zip / .tar / .tar.gz / .tgz)+ magic number 雙重確認(zip=PK\x03\x04、gzip=\x1f\x8b、tar=offset 257 的 ustar),不符即拒收。

檢查順序(streaming,全程不落地解壓;D4 / D8):

  1. 大小上限——Content-Length 預檢+實讀累計雙保險,超過 50MB 即斷。
  2. 開檔——依格式用 tarfilezipfile 開啟(兩者皆標準庫;zip 需 seekable 來源,以記憶體 / spooled buffer 承接)。
  3. 逐 entry 安全檢查
    • 防 path traversal(slip):entry 路徑正規化後不得為絕對路徑、不得含 .. 逸出;tar 的 symlink / hardlink entry 一律拒收。
    • 防 bomb:entry 數上限+宣告解壓總量上限+壓縮比上限(實際數值 plan 階段定,量級參考:現有 TWGCB 單支數百 KB)。
  4. 結構驗證——頂層或一層目錄內須有 inspec.yml(D8:只驗結構,不驗 profile 語意正確性;正確性由掃描執行結果反映,界線明寫)。
  5. sha256 計算——對原始壓縮檔 bytes 計算,落 detection_tool_profiles.sha256 作 agent 對帳信任根。
  6. 存儲——SYSTEM 公版走 upload_system_file()、TENANT 走 upload_files_for_tenant()(§4 canonical service,不另造)。

兩種格式共防:tar 系與 zip 的 slip / bomb 檢查邏輯共用同一套 entry 檢查器(僅迭代介面不同),避免 zip 路徑漏防。

5.4 下發機制(核心)

派工時 _collect_pending_tasks()app/remote_agent/service/agent_enrollment_service.py:241-249)組 payload 階段,偵測 params.profile 的值並分流:

params.profile 值 BE 派工行為 agent 執行行為
庫內參照・file 型profile:<uid> 前綴,D2) 展開成 _profile 內部 key:{uid, version, sha256, source_type, file_name}_ 前綴不落 FE 紀錄,沿用 _credentials 慣例) 查本機 cache /data/content/cache/<uid>/v<version>/ → 命中直接用;miss 則打 GET /api/1.0/agents/files/<uid>(P2 通道)拉壓縮檔 → 重算 sha256 對帳(不符即 fail,FR-039 不變式)安全解壓進 cache 目錄 → 把解壓後目錄餵給 cinc-auditor(D4:四格式統一走此一條路徑)
庫內參照・url 型 展開成 _profile{uid, source_type, url} 把 url 原樣交給 cinc-auditor(現行為;打不通是客戶網路問題,P5 界線)
手填值(不以 profile: 開頭) 原樣下發,不展開 原樣塞進 cinc-auditor exec argv(現行為,P6 相容)

agent cache 細節(D4 / D9):

  • 目錄結構 /data/content/cache/<uid>/v<version>/,存解壓後內容;解壓完成後寫入完成標記(含來源壓縮檔 sha256),命中判斷=目錄存在+標記 sha256 與 _profile.sha256 一致。
  • agent 端解壓同樣做 slip / bomb 防護(BE 驗過不代表 agent 可信任傳輸後內容——對帳通過後解壓仍走安全解壓器)。
  • cache key 含 version → 版本更新自然失效(舊版目錄不再被引用);agent 重啟不清;第一期無上限+手動清理指引,LRU 列 follow-up(D9)。

取檔授權(profile_ref resolver,D6):復用 058.7 端點、授權 resolver 分流——profile_ref claim 的驗證語意=「該 uid 是該 agent 名下 pending / running 任務 params 中引用的 profile 檔」(cache miss 拉檔發生在任務執行中,任務綁定天然成立)。本案實作此 resolver;前置條件=058.7 把授權驗證做成可插拔 resolver 介面(須在該案開工前談定、寫進其 T-7.1 規格)

具體介面契約(兩案凍結契約):resolver 是一個 callable,收 (agent_uid, file_uid),回傳「認不認這個 uid」(bool 或含理由的結果物件);端點授權段逐一詢問模組層 resolver 清單(058.7 落地 _FILE_ACCESS_RESOLVERS = [source_file_resolver]),任一認可即放行、全部不認才拒絕。本案 T-2.3 的動作=實作 profile_ref_resolver 並 append 進該清單,不動端點本體。此契約 2026-08-01 已由 058.7 協調者下達其 T-7.1 執行者,D6 前置條件已滿足(058.7 側並會留註解標明「FR-059 將 append profile_ref resolver,resolver 介面即兩案契約,不要在重構中收掉」)。

5.5 下拉動態化

param_schema 的 profile 欄位加宣告 "options_source": "profile_library"(type 維持 select_or_text,P6)——FE 看到此宣告,改呼叫 profile menu API(帶 detection_tool_id 過濾,P7)動態組 options,不再讀死的 options 陣列。

  • 機制 tool-agnostic:未來任何工具任何欄位要接庫,seed 宣告即可,FE 零改(§5.7 checklist 的基礎)。
  • param_schema 升版動作:inspec 與 gcb 各 INSERT 新 version(舊版 is_current=FALSE,照該表既有版本紀律),新版 profile 欄位帶 options_source 宣告並拿掉靜態 options——8 支 TWGCB 容器路徑選項與 dev-sec 2 條 URL 選項由搬遷 seed(§5.6)轉為庫資料承接。升版 migration 套用節奏依 D10。

5.6 搬遷 seed(P3 / D7)

批次 內容 做法
**8 支 TWGCB(file 型) content/detection-profiles/ 下 8 支 TWGCB profile 目錄 各自打包成 tar.gz、走與使用者相同的上傳管線**(§5.3 驗證+sha256+upload_system_file())落成 SYSTEM 公用版——不繞道直插 DB,確保 seed 資料與使用者上傳資料形狀完全一致(有 upload_files 實體與 sha256)。實作形式=python seed script(呼叫 app service),SQL migration 只負責表結構
dev-sec 2 條(url 型) linux-baseline / windows-baseline 公開 tarball URL 登記成 SYSTEM url 型公版(純資料列,無檔案實體),與 TWGCB 同批執行;兼作 url 型首批示範資料(D7)
  • 顯示名稱沿用現行 param_schema options 的 label——確保下拉零回歸(P3 驗收條件)。
  • 所屬工具:TWGCB 8 支綁 gcb、dev-sec 2 條綁 inspec(P7 分池)。
  • 執行節奏:DEV 先行驗收,STG / POC 等決策者明示放行(D10)。

5.7 新工具接入 checklist(後續接工具照表操課,不用考古)

⑴ 同形態新工具(形態 A:content 在 agent 本機讀取)=零開發接入,三步驟

前提:該工具的 connector 已存在、且其掃描引擎吃「agent 本機 content」(同 CINC / GCB 形態)。

步驟 動作 動到什麼
1 registry 加工具:config.detection_tools seed 一筆(照 FR-056 / FR-058 既有 seed 慣例) 一支 SQL migration
2 param_schema 欄位宣告:該工具的 profile 類欄位 type=select_or_text"options_source": "profile_library" 同一支 migration 的 param_schema seed
3 後台上傳 profile(上傳時選該工具)→ 任務抽屜下拉即可用 純後台操作

BE / FE / agent 程式碼零改——profile 表帶 detection_tool_id 天然分池(P7)、FE 靠 options_source 宣告驅動(§5.5)、agent 的 _profile 處理與 cache 是 tool-agnostic 通用路徑(§5.4)。

⑵ 異形態工具(如 OpenSCAP content 在目標主機、ZAP policy 在客戶 server)

  • profile 庫本體全形態共用:儲存(file / url 雙軌)、版本模型、雙軌 scope、管理頁與全部管理 API 一概不動、直接沿用表結構不改(P1 tool-agnostic 設計的意義所在)。
  • 只需為該形態新增「下發策略」段——即派工展開 _profile 之後「檔案最後一哩怎麼到位」的策略。例:OpenSCAP 型=agent 拉檔後經 SSH 推送至目標主機再執行;ZAP 型=透過客戶 ZAP daemon 的 API 匯入 policy。接入時要寫的東西=該形態的下發策略設計(agent 端行為)+connector 對接;不要動 profile 庫表與管理 API
  • 接入時機的判斷素材:該工具的 content 由誰讀(agent / 目標主機 / 第三方 daemon)——是形態 A 就走 ⑴,不是就寫下發策略走 ⑵。

6. 拆分(4 子需求 × 12 子任務)

依賴鏈:FR-059.1(BE 地基)→ FR-059.2(下發整合)∥ FR-059.4(FE,管理頁部分)→ FR-059.3(Agent 端,硬依賴 058.7 通道)

並行契約凍結項(FR-058 arc 既有教訓:並行前先凍結契約):

  1. 庫參照格式 profile:<uid>(D2)
  2. _profile payload 形狀(file 型 {uid, version, sha256, source_type, file_name};url 型 {uid, source_type, url}
  3. cache 目錄結構 /data/content/cache/<uid>/v<version>/(存解壓後內容+完成標記)
  4. 取檔端點的 profile_ref 授權 resolver 介面(D6,已於 2026-08-01 談定:callable 收 (agent_uid, file_uid) 回傳認可與否,端點逐一詢問 _FILE_ACCESS_RESOLVERS 清單、任一認可放行;詳見 §5.4)

FR-059.1 BE 地基 — 依賴:無(決策已全數定案)

# 子任務 驗收 依賴 Repo
T-1.1 migration+model:config.detection_tool_profiles 表(Category C 欄位+CHECK 約束+三組唯一索引+RLS 四段 policy+GRANT/sequence 鐵則);ORM model(BaseModel, TenantScopedMixinModel)+repo(BaseRepositoryImpl DEV 可套;RLS 手測三件套通過:租戶 A 看不到租戶 B 的 TENANT 列、SYSTEM 列人人可讀、非 root 寫 SYSTEM 被 RLS 擋 BE
T-1.2 管理 API 六動作:列表(分頁合併查詢公版前置)/ menu / 上傳登記(file・url 雙軌)/ 版更 / fork+複製到另一工具 / 停用;三層防禦守門;審計欄位 enrich;error code 新增 Swagger 全端點可操作;非 root 對公版寫入回 403(service guard);DTO 回 scope;版更後舊版 is_current=FALSE 且僅一筆 current;menu 只回 current+active 並按工具過濾 T-1.1 BE
T-1.3 上傳驗證管線:四格式(zip / tar / tar.gz / tgz)偵測(副檔名+magic number)、tarfilezipfile streaming 安全檢查(slip / bomb / entry 數 / 解壓比)、inspec.yml 結構驗證、50MB 上限、sha256 計算 惡意樣本測試全數被擋且錯誤訊息可辨識(traversal 樣本 / bomb 樣本 / 無 inspec.yml / 超大小 / 假副檔名);四格式正常樣本全部通過;不落地解壓(過程無暫存解壓目錄) T-1.1 BE
T-1.4 搬遷 seed:8 支 TWGCB 打包經上傳管線落 SYSTEM file 型+dev-sec 2 條落 SYSTEM url 型(python seed script);名稱對齊現行 options label DEV 庫內 10 筆 SYSTEM 公版;menu API 回傳集合與現行 param_schema 靜態 options 一致(零回歸前置驗證);TWGCB 綁 gcb、dev-sec 綁 inspec T-1.2、T-1.3 BE

FR-059.2 下發整合 — 依賴:FR-059.1;058.7 resolver 介面談定(D6)

# 子任務 驗收 依賴 Repo
T-2.1 param_schema 動態化:inspec / gcb 兩工具 profile 欄位加 options_source: "profile_library" 宣告+升版(INSERT 新 version 拿掉靜態 options,舊版留存) 新版 param_schema 生效(is_current 切換正確);舊版可回溯;宣告形狀與 FE 契約一致 T-1.4 BE
T-2.2 派工展開:_collect_pending_tasks() 偵測 profile:<uid> 前綴 → 依 source_type 展開 _profile payload;手填值原樣下發 三種取值來源(庫內 file / 庫內 url / 手填)payload 形狀各自正確;_profile 不落 FE 執行紀錄與稽核 log(比照 _credentials);參照失效(uid 不存在 / 已停用)時明確報錯不靜默 T-1.2 BE
T-2.3 profile_ref 授權 resolver:接 058.7 通道的可插拔 resolver 介面,實作「uid 是該 agent 名下 pending / running 任務 params 引用的 profile 檔」驗證 合法任務引用可取檔(串流);非引用 uid / 無任務綁定被拒(拒絕碼以 058.7 端點實際落地為準,其 T-7.1 原卡為 404 不區分情況);單元測試綠 T-2.2;058.7 通道與 resolver 介面已存在(硬依賴) BE

FR-059.3 Agent 端 — 依賴:058.7 通道先存在(硬依賴);FR-059.2 契約凍結

# 子任務 驗收 依賴 Repo
T-3.1 cache+拉檔:cache 目錄 /data/content/cache/<uid>/v<version>/+完成標記;_get_stream() 拉檔;sha256 對帳(不符即 fail);四格式統一安全解壓器(agent 端同防 slip / bomb) cache miss → 拉檔 → 對帳 → 解壓全鏈通;cache 命中不重拉(log 佐證);sha256 竄改樣本 fail 且錯誤可辨識;四格式各一樣本解壓正確 契約凍結;058.7 通道 evidence-agent
T-3.2 connector 目錄交付:inspec.py_profile——file 型把 cache 解壓目錄餵 cinc-auditor exec;url 型 / 手填維持現行為 file 型 profile 掃描成功、報告回收;url 型與手填路徑迴歸綠(現行為零變化);_profile 缺漏或形狀錯誤時明確報錯 T-3.1 evidence-agent
T-3.3 封閉網路情境驗證:agent 無外網環境(模擬)下,僅靠平台通道取得 file 型 profile 完成端到端掃描 斷外網的 agent 完成 GCB 掃描全鏈(CM-992 限制①的直接驗證);過程無任何外網請求 T-3.2 evidence-agent

FR-059.4 FE — 依賴:FR-059.1 API 落 DEV(管理頁);FR-059.2(下拉)

# 子任務 驗收 依賴 Repo
T-4.1 管理頁:系統管理下新頁「掃描設定檔管理」——列表 / 上傳(雙軌表單)/ 版更 / fork / 複製到另一工具 / 停用;靠 scope 區分能力(公版對非 root 唯讀) tenant 管理員與 root 各自能力正確(root 可維護公版、租戶只能動自有+fork 公版);上傳表單依 source_type 切換欄位;驗證失敗錯誤訊息可讀 T-1.2 FE
T-4.2 任務抽屜下拉動態化:偵測 options_source: "profile_library" 宣告改呼叫 menu API(帶 detection_tool_id 過濾)動態組 options;手填能力保留 GCB 抽屜只見 gcb 池、CINC 只見 inspec 池(P7);手填照常(P6);下拉內容與搬遷前一致(零回歸);無宣告的欄位維持靜態 options 行為 T-2.1 FE

後續版(非本案第一版範圍,D5):新機制上線驗收通過後的下一版,拿掉 agent /data/content bind mount 與 param_schema 靜態 options 舊路(手填容器路徑能力因 P6 永遠保留)。


7. 驗收(端到端)

  1. 上傳四格式:管理頁分別上傳 zip / tar / tar.gz / tgz 四種格式的 profile → 全部通過驗證入庫;惡意樣本(path traversal / 解壓 bomb / 無 inspec.yml / 超過 50MB / 假副檔名)全數被擋且錯誤訊息明確可辨識。
  2. 下拉即選:上傳後任務抽屜下拉立即可選(不經任何 migration / 部署動作);GCB 抽屜只見 gcb 池、CINC 只見 inspec 池(P7)。
  3. 派工取檔全鏈:選庫內 file 型 profile 發起執行 → agent cache miss → 走 058.7 通道拉檔 → sha256 對帳通過 → 安全解壓 → 解壓目錄餵 cinc-auditor → 掃描成功、報告回收進證據池。
  4. cache 命中:同 profile 再次派工 → cache 命中不重拉(agent log 佐證);版更後派工 → 因 cache key 含 version 自然 miss、拉新版。
  5. 對帳防線:sha256 不符(竄改模擬)→ 掃描 fail 且錯誤可辨識,不靜默使用髒檔(FR-039 不變式)。
  6. 三層取值相容:庫內 url 型(dev-sec 搬遷公版)與現場手填值照現行為執行成功(P6 零破壞)。
  7. 雙軌守門:公版 fork 成租戶自有副本可編輯;非 root 對公版寫入被擋(403);租戶 A 看不到租戶 B 的自有 profile(RLS)。
  8. 封閉網路情境模擬:agent 無外網、僅靠平台通道取得 profile 完成掃描——CM-992 限制①的直接驗證。
  9. 下拉零回歸:搬遷 seed(P3 / D7)+param_schema 升版後,下拉內容與搬遷前完全一致(8 支 TWGCB+2 條 dev-sec 名稱不變)。
  10. 三環境節奏:以上驗收全數在 DEV 完成;STG / POC 的 migration / seed / agent 部署一律等決策者明示放行(D10 環境異動鐵律)。