Guidant AI · FR-059 檢測工具 Profile / Content 庫

FR-059 · Detection Profile Library — 需求討論稿 · 2026-08-01(v1 待審)

Profile 庫:把掃描依據搬進後台管理

掃描工具(第一期 CINC Auditor / InSpecGCB)執行時需要 profile / content 檔作為掃描依據。目前這些檔案靠「BE repo 版控 → 人工 rsync 到 agent 主機 bind mount → migration seed 下拉選項」維運——每加一支 profile 都要動 agent 部署與 migration,上線後一線工程師無法維護。本案做一個後台管理功能:上傳 / 登記 profile(檔案或 URL)、公用版+租戶自有版雙軌(模式同流程管理)、掃描任務下拉動態選取、agent 動態拉檔+cache。本案即 Notion CM-992 範圍③「後台 content 管理機制」的立案,做完同時消掉 CM-992 記錄的兩個限制(封閉網路投放、私有版本庫憑證)。

已拍板 P1–P7(7 項) 待決策 D1–D10(10 項) 通道依賴:FR-058.7(agent 取檔端點) 承接:CM-992 範圍③ 母脈絡:FR-058 · 安全基建:FR-039
§1

需求背景:為什麼要做 Profile 庫

FR-058 檢測工具擴充上線後,profile 維運鏈路成為明顯的營運斷點。

1.1 現在每加一支 profile 要動幾個地方

以 GCB(TWGCB 政府組態基準)為例,目前一支新 profile 要走完這條鏈才會出現在使用者的下拉選單裡:

步驟動作誰做得到
1profile 原始碼進 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 版本紀律)
4migration 依環境異動鐵律套 DEV → 決策者放行後 STG / POC開發者+決策者

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

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

限制 ①:封閉網路無法投放 profile

封閉網路客戶的 agent 連不了外網,只能用「agent 本機路徑」型 profile;但目前沒有任何機制能把 profile 檔送進 agent container(docker-compose 的 content 掛載是人工 rsync,客戶做不來)。

本案解法:profile 上傳到平台 → agent 透過既有 mTLS 通道拉檔+cache,檔案遞送不再依賴外網或人工投放。

限制 ②:私有版本庫無憑證路徑

CINC 原生支援 URL 取 profile,但 params.profilesecret:false 純文字——token 塞進 URL 會洩漏到 FE 執行紀錄與稽核 log,等於私有 repo 的 profile 無路可走。

本案解法:私有 profile 直接打包上傳成租戶自有 profile,不再需要從私有 repo 拉;URL 憑證機制明列不做(P5),需求出現再立案。

1.3 目標狀態一句話

目標

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

§2

已拍板決策 P1–P7

以下七項決策者已定案,本稿不重議,後續設計皆以此為前提。

P1 · 範圍=只做形態 A(agent 本機讀取型)——已拍板

第一期只涵蓋 inspecgcb 兩工具——兩者的 profile 都是「agent 本機讀取後交給掃描引擎」的形態。OpenSCAP 排除(content 在目標主機、客戶用 apt/dnf 自裝、有上游 SSG 版本節奏問題);ZAP 排除(scan policy 放在客戶自己的 ZAP daemon 裡,平台碰不到)。資料模型帶 detection_tool_id 保持 tool-agnostic——未來要擴充其他形態,只加下發策略、不改表。

P2 · agent 取檔通道復用 FR-058.7——已拍板

FR-058.7(SonarQube 上傳掃描)會先動工並建立 GET /api/1.0/agents/files/<uid> 通道(純 mTLS+任務綁定驗證+串流不落地,該案 D25)。本案不重建通道,直接復用;本案對該通道的增量需求(授權 resolver 要多認得「任務引用的 profile 檔」)明列於 D6,須與 058.7 協調。

P3 · 既有 8 支 TWGCB profile 搬入新機制成為 SYSTEM 公用版——已拍板

既有 8 支 TWGCB profile 作為初始 seed 資料搬入庫,成為 SYSTEM 公用版——新機制上線第一天,下拉內容與今天完全一致(零回歸)。搬完退役「migration seed 選項+人工 rsync」舊路(退役時程見 D5)。

P4 · 版本模型= version + is_current 單現行版——已拍板

照抄 detection_tool_param_schemas 的欄位設計與操作紀律:版更=INSERT 新版+舊版 is_current=FALSE,不原地 UPDATE。不做「任務綁舊版」的 group+version 模型——任務永遠用現行版,歷史版本留在表裡供回溯。

P5 · profile 型態雙軌:source_type = file | url——已拍板

file=上傳 tar.gz 存平台儲存、agent 走通道拉+cache;url=只登記 URL 字串、agent 原樣交給 cinc-auditor(工具原生支援網址取 profile)、平台完全不經手檔案URL 的憑證 / 認證 / 權限一概不管——URL 打不打得通是客戶自己的網路與授權問題,平台不驗證、不代管憑證;user 有反應再開後續案。這條界線在此明寫,避免日後被當成缺陷回報。

P6 · select_or_text 手填能力保留——已拍板

下拉選項改由 profile 庫動態餵,但欄位型別維持 select_or_text——使用者仍可現場手打 URL 或容器路徑(不進庫、單次執行用)。取值來源三層並存:庫內 file 型 / 庫內 url 型 / 現場手填。既有任務存的手填值升級後照常可用(相容性零破壞)。

P7 · CINC 與 GCB 的 profile 池分開——已拍板

兩工具在 registry 是獨立兩筆(inspec id=6、gcb id=8),雖共用 connector 引擎,profile 庫仍按 detection_tool_id 綁定與過濾:上傳時必選所屬工具;GCB 任務抽屜只看得到 gcb 的 profile,CINC 同理。同一份檔兩工具都要用就各登記一筆(可提供「複製到另一工具」動作),不做跨工具共用——避免「GCB 池混進通用 baseline」的語意污染。

2.1 七項拍板一覽

編號主題拍板內容一句話
P1範圍只做形態 A(inspec+gcb);OpenSCAP / ZAP 排除;資料模型 tool-agnostic
P2取檔通道復用 FR-058.7 的 GET /api/1.0/agents/files/<uid>,不重建
P3存量搬遷8 支 TWGCB 搬成 SYSTEM 公用版 seed,上線第一天下拉零回歸
P4版本模型versionis_current 單現行版,INSERT 新版不原地改
P5型態雙軌source_type = file | url;URL 憑證一概不管(界線明寫)
P6手填保留select_or_text 不變,三層取值來源:庫內 file / 庫內 url / 手填
P7工具隔離CINC 與 GCB profile 池分開,按 detection_tool_id 綁定過濾
§3

現況盤點:鏈路事實與可複用資產

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

3.1 profile 現況鏈路

環節座標與說明
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」。
投放 人工 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 未投放
下拉選項 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 是建議值非白名單(手填任何字串都會被原樣下發)。
執行 agent connector evidence-agent/core/task_executor_connectors/inspec.pyparams.profile 原樣塞進 cinc-auditor exec argv。CINC 原生支援三種 content 來源:網址 / agent 本機路徑 / Supermarket 名稱。
架構前提 CINC Auditor 裝在 agent 本機、由 agent 連出去掃目標(SSH / WinRM),profile 由 agent 本機讀取後交給 cinc-auditor,不送到目標主機——與 OpenSCAP(引擎與 content 都在目標主機)方向相反。設計 content 遞送時不可用 OpenSCAP 心智模型。(FR-058 design.md:363-370)
痛點量化 每加一支 profile=人工 rsync 每台 agent 主機+一支 migration 改 options+三環境套用。

3.2 BE↔Agent 通訊現況

能力狀態座標與說明
派工鏈路 可複用 使用者按執行 → 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 秒。
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)。
既有安全基建 可複用 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)。
FR-039 不變式 約束 docs/features/FR-039-2606-distributed-file-agent/design.md:284-291:雲端不落地 binary(串流)、agent 不連雲端 DB、完整性信任根=雲端 sha256。本案所有設計不得破壞。

3.3 公用 / 租戶雙軌的拍板模式(Category C,直接照抄)

依據 docs/analysis/2026-06-29-tenant-scope-data-model.md,「系統公版+租戶私有混合表」已有定案模式(Category C),本案照抄不重議:

層面拍板模式與抄襲來源
欄位形狀 scope VARCHAR(20) NOT NULL DEFAULT 'TENANT'('SYSTEM'|'TENANT')+ tenant_id NOT NULL(SYSTEM 掛 ROOT(1))。核心不變量:tenant_id IS NULL 永遠是壞資料,不賦予語意。不要學 flow_templates 的 is_builtin+NULL(已被拍板取代的先驅版)。
Model 繼承 BaseModel, TenantScopedMixinModel(jedi_common 的 before_flush listener 自動填 tenant_id;SYSTEM 列由 root session 建立自然掛 ROOT)。
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() 已存在直接呼叫。
程式層三層防禦 抄 flow_templates:① Route capability(讀=所有租戶;寫入 capability 標 is_platform,參照 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,改判 scope=='SYSTEM' and not viewer_is_platform_admin() → ForbiddenError)③ DB RLS 兜底。
root 判定 canonical=common/authz/platform.py viewer_is_platform_admin()(allowed_tenant_paths 單層=root,不硬編 id)。
列表 / fork 單一端點合併查詢 or_(tenant_id==ctx.tenant_id, scope=='SYSTEM'),公版排前面;DTO 回 scope 讓 FE 決定編輯鈕。fork=duplicate 端點硬寫 scope='TENANT', tenant_id=ctx.tenant_id(公版複製成自有可編輯副本)。
唯一索引形狀 2026-05-12-flow-template-management-schema.sql:70-73:租戶內唯一(partial WHERE is_active)+公版全域唯一。

3.4 檔案儲存複用(不另造儲存抽象)

能力狀態座標與說明
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)。
使用者面下載 agent 不適用 既有 GET /file/download/<uid>+signed token(TTL 120 秒);agent 拿不到 signed token(換 token 端點掛 @jwt_required),所以 agent 必須走 P2 的專用通道。
上傳驗證現況 缺口 各 service 手寫「副檔名+seek 大小」三步(如 app/grc/service/ap_docx_import_app_service.py:74-79),無 magic number 檢查、無 zip-slip / bomb 防護(全 repo 零處理)——tar.gz 結構驗證(內含 inspec.yml)與解壓安全防護是本案新造點。
§4

詳細設計草案

四塊:資料模型、上傳 / 管理流程、下發機制(核心)、下拉動態化。此為討論基準,細節以 design.md 落版為準。

4.1 資料模型草案

新表建議 config.detection_tool_profiles(schema 歸屬見 D1),Category C 雙軌形狀+P4 版本模型:

欄位型別 / 約束說明
id / uidBIGSERIAL / UUID標準主鍵+對外識別
scopeVARCHAR(20) NOT NULL DEFAULT 'TENANT''SYSTEM'(公用版,root 維護)| 'TENANT'(租戶自有)——Category C 拍板形狀
tenant_id / org_unit_idBIGINT NOT NULL / BIGINTTenantScopedMixinModel 自動填;SYSTEM 列掛 ROOT(1)。tenant_id IS NULL 永遠是壞資料
detection_tool_idBIGINT NOT NULLsoft-ref config.detection_tools;inspec 與 gcb 各自綁定(P7)
name / descriptionVARCHAR / TEXT顯示名稱與說明
platform_hintVARCHAR NULL適用平台標記(如 windows / linux),純顯示用不參與過濾邏輯
source_typeVARCHAR(10) NOT NULL'file' | 'url'(P5 雙軌)
file_idBIGINT NULLsoft-ref upload_files.idsource_type=file 時必填(CHECK 約束)
urlTEXT NULLsource_type=url 時必填;平台不驗證可達性(P5 界線)
sha256VARCHAR(64) NULLfile 型上傳時計算,為 agent 對帳的信任根(FR-039 不變式)
version / is_currentINTEGER NOT NULL DEFAULT 1 / BOOLEAN NOT NULL DEFAULT TRUEP4 版本模型:版更=INSERT 新版+舊版 is_current=FALSE
is_activeBOOLEAN NOT NULL DEFAULT TRUE軟刪(停用後下拉不出現,歷史執行紀錄參照仍可回溯)
稽核欄位created_user / updated_user / 時間戳API 回傳時依專案規範 enrich *_user_name(nickname)

索引與 RLS:唯一索引三組——(detection_tool_id, tenant_id, name, version) unique、公版名稱全域唯一 partial index(WHERE scope='SYSTEM' AND is_active)、同名 is_current=TRUE 僅一筆的 partial unique。RLS 四段 policy 照抄 module_frames 版(§3.3)。新表照 SQL migration 鐵則加 GRANT ... TO cm_app+sequence 權限。

4.2 上傳 / 管理流程

管理頁(FE 入口見 D3)提供五個動作:

file 型驗證(本案新造點,現況全 repo 無先例):副檔名 .tar.gz / .tgz+大小上限(建議 50MB,D4)+tar 結構驗證(頂層或一層內須有 inspec.yml)+解壓安全防護(streaming 檢查不落地解壓:entry 路徑防 traversal、entry 數與解壓比防 bomb),深度見 D8。

%%{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
    autonumber
    participant U as 管理者 (FE 管理頁)
    participant BE as BE
    participant TS as 平台儲存
(local/minio/remote_agent) participant DB as config.detection_tool_profiles Note over U: 選工具 (inspec | gcb, P7 必選)
選 source_type (file | url, P5) alt source_type = file U->>BE: 上傳 tar.gz + name/description BE->>BE: 驗證:副檔名/大小上限 (D4)
tar 結構含 inspec.yml (D8)
streaming 防 slip/bomb 不落地解壓 BE->>BE: 計算 sha256(信任根) alt scope = SYSTEM(root 維護公版) BE->>TS: upload_system_file()(root 儲存) else scope = TENANT(租戶自有) BE->>TS: upload_files_for_tenant() end BE->>DB: INSERT profile(file_id + sha256, version=1, is_current=TRUE) else source_type = url U->>BE: 登記 URL + name/description BE->>DB: INSERT profile(url 字串, 平台不經手檔案・不驗可達性 P5) end BE-->>U: 列表更新(任務抽屜下拉即刻可選) Note over U,DB: 版更=同名 INSERT 新 version + 舊版 is_current=FALSE(P4)
fork=公版複製成 scope='TENANT' 自有副本

圖 1 · 管理面:上傳 / 登記 / 版更流程(FE → BE → 儲存 → profile 表)

4.3 下發機制(核心)

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

params.profile 值BE 派工行為agent 執行行為
庫內參照・file 型
(參照格式見 D2)
展開成 _profile 內部 key:{uid, version, sha256, source_type, file_name}(_ 前綴不落 FE 紀錄,沿用 _credentials 慣例) 查本機 cache /data/content/cache/<uid>/v<version>/ → sha256 命中直接用;miss 則打 GET /api/1.0/agents/files/<uid>(P2 通道)拉 tar.gz 存 cache、重算 sha256 對帳後交給 cinc-auditor(CINC 原生吃 tarball 不必解壓
庫內參照・url 型 展開成 _profile:{uid, source_type, url} 把 url 原樣交給 cinc-auditor(現行為;打不通是客戶網路問題,P5 界線)
手填值
(不匹配庫參照格式)
原樣下發,不展開 原樣塞進 cinc-auditor exec argv(現行為,P6 相容)

效果:agent 只升級一次(學會拉檔+cache),之後 content 增改全在後台——不再動 agent image、不再 rsync、不再為選項寫 migration。

%%{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
    autonumber
    participant U as 使用者 (FE)
    participant BE as BE
    participant AG as agent
    participant TGT as 掃描目標主機
(SSH/WinRM) U->>BE: 發起執行(params.profile = 庫內參照) BE->>BE: start_execution() 建 agent_tasks(pending) AG->>BE: 心跳 POST /api/1.0/agents/heartbeat(mTLS) BE->>BE: _collect_pending_tasks() 偵測庫參照
展開 _profile {uid, version, sha256, source_type, ...} BE-->>AG: pending_tasks payload(夾 _profile) AG->>BE: ack alt file 型:cache 判斷 AG->>AG: 查 /data/content/cache/<uid>/v<version>/
sha256 命中 → 直接用 opt cache miss AG->>BE: GET /api/1.0/agents/files/<uid>
(mTLS,FR-058.7 通道,profile_ref 授權 D6) BE-->>AG: 串流回傳 tar.gz(BE 不落地) AG->>AG: 重算 sha256 對帳(不符即 fail)
存入 cache end AG->>AG: cinc-auditor exec <cache 內 tarball>
(CINC 原生吃 tarball 不解壓) else url 型 / 手填值 AG->>AG: cinc-auditor exec <url 或原樣字串>(現行為) end AG->>TGT: SSH/WinRM 連線掃描(profile 不送目標主機) TGT-->>AG: 掃描結果 AG->>BE: 回報 result(既有 mTLS 通道,報告走 blob 回收) BE-->>U: 執行紀錄顯示結果

圖 2 · 執行面:派工 → 心跳夾參照 → agent cache 判斷 → miss 拉檔 → sha256 對帳 → cinc-auditor 執行

4.4 下拉動態化

param_schema 的 profile 欄位加宣告(如 "options_source": "profile_library")——FE 看到此宣告,改呼叫 profile 列表 API(帶 detection_tool_id 過濾,P7)動態組 options,不再讀死的 options 陣列。機制 tool-agnostic:未來任何工具任何欄位要接庫,seed 宣告即可,FE 零改。既有 8 支 TWGCB options 與 dev-sec URL options 由搬遷 seed 轉為庫資料(P3、D7),param_schema 升版(INSERT 新 version)拿掉靜態 options。

%%{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
    subgraph LIB["config.detection_tool_profiles(雙軌)"]
      SYS["SYSTEM 公用版
root 維護・全租戶可見不可改
(8 支 TWGCB 搬遷 P3)"] TEN["TENANT 自有版
各租戶上傳・自管
(fork 公版亦落此軌)"] end subgraph SRC["任務抽屜 profile 三層取值來源(P6)"] F1["庫內 file 型
agent 走通道拉檔+cache"] F2["庫內 url 型
agent 原樣交 cinc-auditor"] F3["現場手填
不進庫・單次執行用"] end subgraph CH["agent 取檔通道(FR-058.7 建,本案復用 P2)"] EP["GET /api/1.0/agents/files/<uid>
純 mTLS+授權 resolver"] R1["source_file resolver
(058.7:一次性輸入檔)"] R2["profile_ref resolver
(本案新增:任務引用的 profile, D6)"] end AG["agent
cache /data/content/cache/<uid>/v<version>/
sha256 對帳"] FE["任務抽屜下拉
options_source=profile_library
帶 detection_tool_id 過濾(P7)"] SYS --> FE TEN --> FE FE --> F1 FE --> F2 FE --> F3 F1 -->|"派工展開 _profile"| EP EP --> R1 EP --> R2 R2 --> AG F2 -.->|"url 原樣下發"| AG F3 -.->|"原樣下發(現行為)"| AG

圖 3 · 架構關係:公版 / 自有雙軌 + 三層取值來源 + 與 FR-058.7 通道(雙 resolver)的關係

§5

與 FR-058.7 / CM-992 的關係

本案與兩個相關案的範圍切分,在此釘死避免重工或漏接。

5.1 FR-058.7(SonarQube 上傳掃描)——通道由它建,本案復用

面向FR-058.7FR-059(本案)
檔案性質單次執行的一次性輸入(源碼 zip,agent 掃完 workspace 即刪)長期 content 資產(版本管理+多任務共用+agent cache)
取檔通道建立 GET /api/1.0/agents/files/<uid>(該案 D25:純 mTLS+任務綁定驗證+串流不落地)復用(P2),不重建;增量=授權 resolver 多一種 profile_ref claim(D6)
授權語意uid 屬該 agent 名下 pending / running 任務的 source_fileuid 是該任務 params 中引用的 profile 檔(cache miss 拉檔也發生在任務執行中)
時程關係先動工依賴其通道先存在(FR-059.3 的前置條件);協調事項見 D6

討論稿座標:docs/features/FR-058-2607-detection-tools-expansion/discussion-fr058.7-upload-scan.html

5.2 CM-992(Notion)——範圍③由本案承接

CM-992 範圍去向
範圍③ 後台 content 管理機制本案承接 即 FR-059 全部;做完同時消掉 CM-992 記錄的兩個限制(§1.2)
待補對照 2,384 項(TWGCB 完整涵蓋)留原卡 content 產製工作,與管理機制無關
network / cloud / browser transport 形態留原卡 非形態 A,P1 明確排除

Notion 動作:CM-992 需關聯到本 FR(立案後補連結)。

5.3 母脈絡沿用(FR-058 / FR-039)

§6

待決策 D1–D10

每項附建議與理由,等決策者過稿。P1–P7 已拍板事項不在此重議。

D1 · 表落點與命名

問題:新表放 config schema(跟 detection_tools 家族同 schema)還是 compliance schema?

考量:語意上 profile 庫是 detection_tools 家族的延伸,放 config 最自然;但 config.detection_tools 家族目前無 RLS 前例(全是平台目錄表),本表帶 RLS 會是該 schema 首例。若要「RLS 慣例一致」也可放 compliance(該 schema 的 tenant-scoped 表都帶 RLS)。

建議:config schema、表名 detection_tool_profiles——語意歸屬優先;「schema 首例」不是實質障礙(RLS policy 是表級設定,與 schema 無關),且跨 schema 拆家族反而讓 detection_tools 的讀者找不到。請決策者裁。

D2 · 庫參照的下發格式

問題params.profile 存什麼值來代表「庫內某 profile」?必須與手填值(P6 保留)零歧義共存。

選項A:存 profile:<uid> 帶前綴的參照字串(派工時展開);B:存 uid 裸值,與手填字串靠格式判別(UUID 樣式=參照);C:param 另開 profile_ref 欄位與 profile 並存。

建議:方案 A(顯式前綴)——與手填值零歧義(手填不可能以 profile: 開頭);單欄位不動 FE 主參數顯示邏輯(_PRIMARY_PARAM_KEYS);派工展開時前綴判斷一行搞定。

排除 B:手填值理論上可以長得像 UUID(低機率但非零),靠格式猜測是隱性契約。排除 C:雙欄位要處理互斥驗證與 FE 顯示分流,成本高於前綴。

D3 · FE 管理頁入口

問題:管理頁放哪?選項:① 系統管理下新頁「掃描設定檔管理」(tenant 管理員與 root 用同一頁,靠 scope 區分能力);② 工具管理頁(tool-plugin-manage)內嵌 tab。

建議:獨立新頁——雙軌管理語意跟流程管理一致(流程管理也是獨立頁);且工具管理頁是純平台目錄頁(root 專用、受眾不同),內嵌 tab 會把租戶級功能塞進平台級頁面,權限模型混濁。

D4 · file 型大小上限與格式

問題:收什麼格式、上限多大?

建議:僅收 .tar.gz / .tgz、上限 50MB——CINC 原生吃 tarball(agent 拉下來直接餵,不必解壓);現有 8 支 TWGCB profile 單支僅數百 KB,50MB 已極寬裕。zip 第一期不收:收 zip 就要在 agent 端加解壓步驟(CINC 對 zip 的支援不如 tarball 直接),徒增解壓攻擊面;有需求再擴。

D5 · 舊路退役時程

問題:P3 搬遷後,agent 的 /data/content bind mount 與人工 rsync 何時退役?

建議:過渡一版——新機制上線驗收通過後,下一版拿掉 bind mount 與 seed 的靜態 options。過渡期兩路並存(舊容器路徑選項照常可用),確認庫路徑穩定再收。手填容器路徑能力因 P6 永遠保留——舊路徑字串日後仍可手填使用,只是平台不再維護該目錄內容。三環境節奏提醒見 D10。

D6 · 對 FR-058.7 通道的增量需求(跨案協調項)

問題:058.7 的任務綁定驗證是「uid 屬該 agent 名下 pending / running 任務的一次性輸入檔」;本案的 profile 檔是長期資產,且 agent cache miss 拉檔也發生在任務執行中——授權邏輯需擴充為「該 uid 是該任務 params 中引用的 profile 檔」。

建議:通道端點共用、授權 resolver 按檔案用途分流——source_fileprofile_ref 兩種 claim,各自驗證;本案負責第二種 resolver 的實作。要跟 058.7 協調的具體事項:058.7 實作時若能把授權驗證做成可插拔的 resolver 介面(而非硬編 source_file 查詢),本案接入成本最低——此協調應在 058.7 開工前談定,寫進其 T-7.1 規格。

D7 · URL 型公版:dev-sec baseline 是否搬入

問題:CINC 現有 2 個靜態選項是 dev-sec linux / windows-baseline 的公開 tarball URL,要不要也搬成 SYSTEM 公用版(url 型)?

建議:是——與 P3 的 TWGCB 搬遷同批 seed,上線第一天 CINC 下拉也零回歸;順帶成為 url 型的首批示範資料(驗證 url 型下發路徑)。

D8 · 上傳時的內容驗證深度

問題:file 型上傳只驗結構(tar 可開+含 inspec.yml)就好,還是進一步驗證 profile 正確性?限制:BE 端沒裝 CINC,無法跑 cinc-auditor check

建議:第一期只驗「結構+檔案安全」——tar 可開、頂層或一層內有 inspec.yml、大小 / entry 數 / 解壓比防 bomb、entry 路徑防 slip(streaming 檢查不落地解壓)。profile 正確性由掃描執行結果反映(壞 profile 掃了會 fail,錯誤訊息可回溯)——這條界線在文件明寫,不做「上傳即保證能掃」的承諾。BE 裝 CINC 做 pre-check 屬未來選項,第一期不做。

D9 · agent cache 管理

問題:cache 目錄(/data/content/cache/)容量上限與清理策略。

建議:第一期「無上限+手動清理指引」——單支 profile 幾百 KB~幾 MB,量級離磁碟壓力很遠;cache key 含 version,版本更新自然失效(舊版目錄不再被引用);agent 重啟不清(磁碟 volume 保留,避免每次重啟全量重拉)。LRU 上限(如 2GB)列 follow-up,等量級真的上來再做。

D10 · 搬遷的三環境節奏(提醒項,可併入 D5)

性質:這條是環境異動鐵律的提醒,非新決策——DEV 先行驗收,STG / POC 一律等決策者明示放行才套 migration 與 seed。搬遷 seed(P3 / D7)與 param_schema 升版都適用。附帶效益:STG / POC 的 agent 目前根本沒有 profile 檔(§3.1),新機制上線後它們第一次有了可用的 GCB 掃描能力——放行時程可跟這個效益一起評估。

6.1 十項決策一覽

編號主題建議一句話急迫度
D1表落點與命名config.detection_tool_profiles(語意歸屬優先;RLS 首例非障礙)高・影響 migration
D2庫參照下發格式profile:<uid> 顯式前綴,與手填零歧義、單欄位高・跨 repo 契約
D3FE 管理頁入口系統管理下獨立新頁「掃描設定檔管理」
D4大小上限與格式僅 .tar.gz / .tgz、50MB;zip 第一期不收
D5舊路退役時程過渡一版後拿掉 bind mount 與靜態 options;手填路徑永遠可用
D6058.7 通道增量端點共用、resolver 分流(source_file / profile_ref);058.7 開工前談定介面高・跨案協調
D7URL 型公版dev-sec 兩條 URL 搬成 SYSTEM url 型(下拉零回歸+url 型示範)
D8內容驗證深度只驗結構+檔案安全;正確性由掃描結果反映(明寫界線)
D9agent cache 管理第一期無上限+手動清理指引;version 進 cache key 自然失效
D10三環境節奏提醒項:DEV 先行,STG / POC 等放行(可併入 D5)提醒
§7

拆分建議

粗粒度子需求方向,供決策者看量級;正式拆卡(子任務粒度)在 design.md 階段做。

子需求Repo工作內容依賴
FR-059.1
BE 地基
BE 表+RLS+migration(D1 定案後);profile CRUD / fork / 版更 API;雙軌守門(三層防禦照抄 flow_templates);上傳驗證(tar 結構+防 slip / bomb,D8);TWGCB 8 支+dev-sec 2 條搬遷 seed(P3 / D7) D1、D2、D4、D8 過稿
FR-059.2
下發整合
BE param_schema 加 options_source 宣告+升版(拿掉靜態 options);派工 _collect_pending_tasks() 偵測庫參照展開 _profile;agent 取檔授權 resolver(profile_ref claim,接 058.7 通道,D6) FR-059.1;D2、D6 過稿;058.7 通道介面談定
FR-059.3
Agent 端
agent cache 機制(/data/content/cache/<uid>/v<version>/,D9);_get_stream() 拉檔;sha256 對帳(不符即 fail);connector 接 tar.gz(cache 內 tarball 直接餵 cinc-auditor) 058.7 通道先存在(硬依賴);FR-059.2 的 payload 契約凍結
FR-059.4
FE
FE 管理頁(列表 / 上傳 / 版更 / fork / 停用,入口依 D3);任務抽屜 profile 下拉動態化(options_source 宣告觸發、帶 detection_tool_id 過濾) FR-059.1 API 落 DEV 後開工;下拉部分依 FR-059.2
驗收
端到端
跨 repo 端到端驗收清單:上傳 → 下拉即選 → 派工 → cache miss 拉檔 → sha256 對帳 → 掃描成功;cache 命中不重拉;url 型 / 手填相容;公版 fork;封閉網路情境模擬(agent 無外網、僅靠平台通道取得 profile 完成掃描——CM-992 限制①的驗證);下拉零回歸(P3 / D7) FR-059.1–059.4 全數完成

並行與契約:FR-059.1 與 FR-059.3 可並行,但並行前必須先凍結契約(FR-058 arc 既有教訓)——契約項:庫參照格式(D2)、_profile payload 形狀、cache 目錄結構、取檔端點的 profile_ref 授權介面(D6)

下一步

決策者過稿 D1–D10 → 落 design.md(含正式拆卡)→ 與 FR-058.7 談定 resolver 介面(D6)→ Notion 開卡(CM-992 關聯本 FR)→ 依子需求順序開工。