狀態:設計定稿(P1–P7 + D1–D10 共 17 項全數拍板)|建立日期:2026-08-01|FR-058 檢測工具擴充續作 討論稿(含流程圖):
discussion.htmlNotion 開卡:尚未進行——等 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 |
| 項目 | 內容 |
|---|---|
| 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 跳號 |
掃描工具(第一期 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 根本沒有這些檔案——選了下拉選項也掃不動。
params.profile 是 secret:false 純文字,token 塞進 URL 會洩漏到 FE 執行紀錄與稽核 log,私有 repo 的 profile 無路可走。本案解法:私有 profile 直接打包上傳成租戶自有 profile,不再需要從私有 repo 拉;URL 憑證機制明列不做(P5),需求出現再立案。agent 只升級一次(學會「拉檔+cache」),之後 content 的新增 / 改版 / 停用全部在後台完成——不再重建 agent image、不再人工 rsync、不再為選項寫 migration。公用版由 root 租戶維護全租戶可見,各租戶也能上傳自己的 profile,模式與流程管理(flow_templates)的雙軌完全一致。
管理者在「掃描設定檔管理」頁上傳 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 回收鏈
P1–P7 為前期已拍板事項(討論稿 §2 原樣);D1–D10 於 2026-08-01 全數定案。
| # | 主題 | 定案內容 | 被排除方案與理由 |
|---|---|---|---|
| P1 | 範圍 | 第一期只做**形態 A(agent 本機讀取型),涵蓋 inspec+gcb 兩工具。資料模型帶 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_schemas:version+is_current 單現行版;版更=INSERT 新版+舊版 is_current=FALSE,不原地 UPDATE |
「任務綁舊版」的 group+version 模型——任務永遠用現行版即可,歷史版本留表內供回溯,不引入綁版複雜度 |
| P5 | 型態雙軌 | `source_type = file | url:file=上傳打包檔存平台儲存、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 驗證層用 tarfile+zipfile 雙支援做結構檢查(找 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 掃描能力——放行時程可跟這個效益一起評估 | (無替代方案——鐵律) |
相關 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_schemas 的 param_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.py 把 params.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_configs 的 tdtc_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) |
新表 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.id;source_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 NULLsource_type='url' → url IS NOT NULL AND file_id IS NULL唯一索引三組(形狀抄 2026-05-12-flow-template-management-schema.sql:70-73):
(detection_tool_id, tenant_id, name, version) unique——租戶內同工具同名同版唯一WHERE scope='SYSTEM' AND is_active)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())。
管理頁六個動作(實際 route 命名與 schema 於 plan 階段依 docs/claude/api-patterns.md 定稿):
| 動作 | 方法(草案) | 行為 |
|---|---|---|
| 列表 | POST /detection-tool-profiles/list(分頁,RequestMetaSchema+EnvelopeSchema) |
公版+自有合併查詢 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 盤點):
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 淪為死碼。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)。審計欄位 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。
現況全 repo 無壓縮檔安全處理先例(§4),本管線為第一個實作,設計如下:
格式偵測:副檔名白名單(.zip / .tar / .tar.gz / .tgz)+ magic number 雙重確認(zip=PK\x03\x04、gzip=\x1f\x8b、tar=offset 257 的 ustar),不符即拒收。
檢查順序(streaming,全程不落地解壓;D4 / D8):
tarfile 或 zipfile 開啟(兩者皆標準庫;zip 需 seekable 來源,以記憶體 / spooled buffer 承接)。.. 逸出;tar 的 symlink / hardlink entry 一律拒收。inspec.yml(D8:只驗結構,不驗 profile 語意正確性;正確性由掃描執行結果反映,界線明寫)。detection_tool_profiles.sha256 作 agent 對帳信任根。upload_system_file()、TENANT 走 upload_files_for_tenant()(§4 canonical service,不另造)。兩種格式共防:tar 系與 zip 的 slip / bomb 檢查邏輯共用同一套 entry 檢查器(僅迭代介面不同),避免 zip 路徑漏防。
派工時 _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 一致。取檔授權(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 介面即兩案契約,不要在重構中收掉」)。
param_schema 的 profile 欄位加宣告 "options_source": "profile_library"(type 維持 select_or_text,P6)——FE 看到此宣告,改呼叫 profile menu API(帶 detection_tool_id 過濾,P7)動態組 options,不再讀死的 options 陣列。
is_current=FALSE,照該表既有版本紀律),新版 profile 欄位帶 options_source 宣告並拿掉靜態 options——8 支 TWGCB 容器路徑選項與 dev-sec 2 條 URL 選項由搬遷 seed(§5.6)轉為庫資料承接。升版 migration 套用節奏依 D10。| 批次 | 內容 | 做法 |
|---|---|---|
| **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) |
gcb、dev-sec 2 條綁 inspec(P7 分池)。前提:該工具的 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)。
_profile 之後「檔案最後一哩怎麼到位」的策略。例:OpenSCAP 型=agent 拉檔後經 SSH 推送至目標主機再執行;ZAP 型=透過客戶 ZAP daemon 的 API 匯入 policy。接入時要寫的東西=該形態的下發策略設計(agent 端行為)+connector 對接;不要動 profile 庫表與管理 API。依賴鏈:FR-059.1(BE 地基)→ FR-059.2(下發整合)∥ FR-059.4(FE,管理頁部分)→ FR-059.3(Agent 端,硬依賴 058.7 通道)。
並行契約凍結項(FR-058 arc 既有教訓:並行前先凍結契約):
profile:<uid>(D2)_profile payload 形狀(file 型 {uid, version, sha256, source_type, file_name};url 型 {uid, source_type, url})/data/content/cache/<uid>/v<version>/(存解壓後內容+完成標記)profile_ref 授權 resolver 介面(D6,已於 2026-08-01 談定:callable 收 (agent_uid, file_uid) 回傳認可與否,端點逐一詢問 _FILE_ACCESS_RESOLVERS 清單、任一認可放行;詳見 §5.4)| # | 子任務 | 驗收 | 依賴 | 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)、tarfile+zipfile 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 |
| # | 子任務 | 驗收 | 依賴 | 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 |
| # | 子任務 | 驗收 | 依賴 | 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 |
| # | 子任務 | 驗收 | 依賴 | 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/contentbind mount 與 param_schema 靜態 options 舊路(手填容器路徑能力因 P6 永遠保留)。
inspec.yml / 超過 50MB / 假副檔名)全數被擋且錯誤訊息明確可辨識。