FR-058 · Detection Tool Integration — 四工具接入
FR-056 建立的檢測工具整合平台是 tool-agnostic 的,加一個工具原則上只要一條 SQL seed,實際工作量集中在 evidence-agent 的 connector。本案接入四個工具,並補上平台唯一的結構性缺口——任務層敏感參數(讓 ZAP 能做登入後掃描)。交付上採分批出貨、ZAP 優先上線(2026-07-30 拍板,見 §10.2),不是四個工具一起收。2026-07-31 更新:原移出本案的 SonarQube 經決策者裁示重新納入為 FR-058.5(pull-snapshot 拉取快照型,D13–D16 定案),本稿維持原四工具敘事不重寫,FR-058.5 詳見 design.md §4.6。
這一節先確立「哪些事情已經不必再做」。FR-056 把工具知識完全逐出後端,FR-057 補齊了 SSH 型態與多檔證據回收——本案是站在這兩層地基上做接入,不是重建管線。
detection_tools(工具目錄)/tenant_detection_tool_configs(租戶層憑證,Fernet 加密)/detection_tool_param_schemas(版本化參數 schema)。工具=一筆 DB row 加一份 JSONB schema。
BE 對任何具體工具零知識,不存在 if (tool.code == "openvas") 這類分支。新增工具不必改 BE 程式碼。
ToolPluginManage.vue 依 schema 渲染,DetectionConfigField.vue 支援七種欄位型態(text / textarea / password / number / boolean / select / select_or_text)與 condition 條件顯示,同樣沒有工具專屬分支。
走 FR-039 agent 心跳夾帶 pending_tasks(預設 300 秒輪詢)。憑證在派工組裝當下才解密下發(FR-056 D9),agent 端用完即丟、不落地。
connector 回 ScanResult(content bytes + filename + content_type + summary)→ task_executor 走既有 blob 通道上傳,寫成 job_evidences(source=DETECTION_TOOL)。
connection_type='SSH' 第三型態、setup_guide markdown 欄位、多檔證據(result_ref.upload_uids),以及 host_failures 與 content_mismatch_hosts 兩條「靜默假成功」防護通道。
core/task_executor_connectors/base.py 定義 DetectionConnector 抽象類別,介面只有兩個方法:
| 方法 | 回傳 | 語意 |
|---|---|---|
run() | ScanResult 或 list[ScanResult] | 執行掃描並回收證據;FR-057 起支援多檔 |
probe() | None(失敗拋例外) | 測試連線——只驗「連得到 + 帳密正確」,不建立任何掃描資源 |
connector 可用的注入點:cancel_event(CM-931 任務取消)、host_failures(CM-952 逐台失敗回報)、content_mismatch_hosts(CM-954 內容錯配回報)。
既有結構性弱點 · 本案會放大
factory core/task_executor_connectors/__init__.py 用 _TOOL_ID_TO_CODE = {1: openvas, 2: nessus, 3: sonarqube, 4: openscap} 硬編 id 到 code 的映射——agent 不連雲端 DB 查工具目錄,只靠這張表認工具。目前四個工具還能勉強維持,一次再加四個工具會讓「三環境 seed id 必須完全一致,錯一位就整條派工錯配」的脆弱性顯著放大。處理方式見決策 D11。
順序不是依重要性排的,而是依「摸索成本」與「相互依賴」排的——先把能被複用的引擎做出來,後面的工具才不必重複扛同一份工程。這個優先序在 2026-07-30 改採分批出貨後維持不變,變的只是四個工具改為序列分批交付(ZAP 先上線),不再四個一起收;批次安排見 §10.2。
| 序 | 工具 | 類型 | 排這個位置的理由 |
|---|---|---|---|
| 1 | ZAP | DAST 網頁動態掃描 | 有可參考的既有實作(前同事的 auto-pentest 專案),API 呼叫序列與踩過的坑都已成文,摸索成本最低 |
| 2 | InSpec / CINC | 組態檢測(Windows / Linux) | 先做這個,GCB 的引擎就是現成的——讓 GCB 只剩驗證鏈路一件事,不必同時扛引擎 |
| 3 | GCB | 政府組態基準 | 引擎複用第 2 棒;本案只做引擎與一個 Windows 最小 Demo profile,content 產製與後台更新機制已裁示不納入(見 D7) |
| 4 | Nmap | 網路埠掃描 | 技術上最單純(呼叫 CLI 加 XML 解析)。原以「排除平台零憑證阻礙」為主要工作,2026-07-30 改案為 SSH 型後(D9),主要工作變成複用 OpenSCAP 既有的 SSH 連線模式 |
以「誰驅動掃描」與「連線型態」兩軸攤開,可以看出這四個工具分屬幾種完全不同的整合模式——差別在於 connector 面對的是遠端服務 API、目標主機的遠端 shell,還是 agent 本機的引擎。
實作後的兩處更正(2026-07-31 回填)
① Nmap 不再是本機 CLI 型——2026-07-30 D9 改案改走 SSH(登入客戶的執行主機再掃目標),與 OpenSCAP 同構。connection_type='CLI' 因此至今仍無任何實際使用者。
② InSpec / CINC 實際上是第四種形態(設計時歸在「遠端登入目標主機」,方向其實相反):引擎裝在 agent 本機、由它自己主動連出去掃 agentless 的目標主機;OpenSCAP 則是引擎裝在目標主機上。connection_type 仍宣告 SSH / WinRM——那是目標主機開放的協定,不代表 connector 自己建連線。這個差異是 D6「雙 transport 邊際成本很低」的真正原因,也是 GCB content 外部載入接口的架構前提(若誤以為 content 要送到目標主機,設計會做錯且要到端到端驗收才會發現)。
%%{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'}}}%%
graph TD
ROOT["evidence-agent
connector"]
ROOT --> API["① 連遠端服務 API"]
ROOT --> REMOTE["② 遠端登入目標主機
(引擎裝在目標主機)"]
ROOT --> LOCALENG["②′ agent 本機引擎
由引擎自己連出去
(目標主機 agentless)"]
ROOT --> CLI["③ agent 本機 CLI
(至今無使用者)"]
API --> OV["OpenVAS
(已上線)"]
API --> ZAP["ZAP
FR-058.1"]
REMOTE --> OSCAP["OpenSCAP
SSH · Linux (已上線)"]
REMOTE --> NMAP["Nmap
SSH · FR-058.4
(D9 改案,原定 ③)"]
LOCALENG --> INSPEC["CINC Auditor
SSH + WinRM · FR-058.2"]
LOCALENG --> GCB["GCB
複用 CINC 引擎 · FR-058.3"]
CLI --> NONE["尚無實際使用者"]
classDef mode fill:#EEF2F3,stroke:#C7D1D2,color:#14201F;
classDef shipped fill:#E4F1EA,stroke:#2E7D5B,color:#14201F;
classDef newtool fill:#E2F0F1,stroke:#0E7C86,color:#14201F;
classDef root fill:#F6EBD5,stroke:#9C6B12,color:#14201F;
classDef empty fill:#FBFCFC,stroke:#C7D1D2,color:#7A8A8C;
class ROOT root;
class API,REMOTE,LOCALENG,CLI mode;
class OV,OSCAP shipped;
class ZAP,INSPEC,GCB,NMAP newtool;
class NONE empty;
這是本案唯一需要動平台本體的項目,而且必須先於 ZAP 完成。方案已拍板採選項 A,此處三案並陳說明取捨。
使用情境是「任務可輸入要掃描的網站,有可能提供一組登入帳密讓 ZAP 做登入後掃描,沒有的話就一般掃描」。但現有設計把憑證與參數分成兩處放,而只有憑證那一條有加密:
| 層級 | 存放位置 | 加密 | 適合放什麼 |
|---|---|---|---|
| 租戶層 | tenant_detection_tool_configs | Fernet 加密 | ZAP 服務本身的位址與 API key——全租戶共用、設定一次就不動 |
| 任務層 | job_execution_detection_tools.tool_params | 明文 | 掃描目標、profile 這類非敏感參數 |
| 任務層敏感 | 目前不存在 ——「這次掃 A 網站用 A 的帳密,下次掃 B 網站用 B 的帳密」正好落在這一格 | ||
若直接塞進 tool_params 會發生什麼
① 被掃網站的帳密會明文躺在資料庫。② FE 執行紀錄視窗會顯示任務參數——BE 現有的 secret 剝除機制是針對租戶層設定的 secret 定義寫的,任務層參數沒有這套保護,等於帳密直接顯示在畫面上。
這不是 ZAP 專屬問題:Nmap 若要做認證掃描、GCB 若每台主機帳密不同,都會撞上同一格。因此當成平台級能力補上,不做 ZAP 專屬的權宜處理。
| 選項 | 做法 | 評估 |
|---|---|---|
| A · 任務層敏感參數 已拍板 |
param_schema 支援 "secret": true;BE 存 tool_params 前對這些 key 加密(複用既有 Fernet 鏈),派工時解密下發,FE 執行紀錄不顯示。 |
一次補平台缺口,四個工具都受益,與現有 secret 機制同一套邏輯。成本約半天到一天(BE 加解密接線 + FE 欄位渲染 + 剝除規則)。 |
| B · 掛租戶層設定 不採 |
ZAP 設定頁多填一組「目標網站測試帳號」。 | 一個租戶只能存一組,不符「不同任務掃不同網站」的實際情境。 |
| C · 掃描目標憑證庫 不採,日後可再評估 |
另開一張表管理被掃目標的憑證,任務發佈時挑選引用。 | 最完整,但工程量大很多。適合日後被掃目標數量成長後再做。 |
| key | label | type | required | secret | condition |
|---|---|---|---|---|---|
target_url | 掃描目標網址 | text | 必填 | — | — |
scan_mode | 掃描模式 | select | 必填 | — | 被動 / 主動 / 登入後主動 |
login_url | 登入頁網址 | text | 條件 | — | scan_mode = authenticated |
login_username | 測試帳號 | text | 條件 | — | scan_mode = authenticated |
login_password | 測試密碼 | password | 條件 | ✓ | scan_mode = authenticated |
logged_in_indicator | 登入成功特徵字串 | text | 條件 | — | scan_mode = authenticated |
沒有選登入模式時,後四欄整組不顯示。這對上「有帳密就深掃、沒有就一般掃描」的需求,而且用的是 FE 現成的 condition 機制,不需要新元件。
實作時的修正 · 登入四欄改為「條件必填」
初版把這四欄做成純選填,結果是使用者少填一欄,錯誤要等到派工後 5 分鐘 agent 執行期才爆。改為條件必填(選了登入模式即為必填)後,錯誤提前到 FE 表單存檔當下。
這是本案反覆出現的一種形狀:同一個錯誤,在越靠近使用者的地方爆越便宜。另兩個同形的修正——probe 漏傳 tool code(原本靜默走 fallback,改成 payload 缺欄位即可查)、ZAP 爬取狀態誤判(原本回一個看不懂的字串,改成回非數字即刻拋錯)。
%%{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
participant U as 稽核員
participant FE as 前端
participant BE as 雲端 BE
participant AG as evidence-agent
participant Z as 客戶 ZAP daemon
participant W as 被掃網站
U->>FE: 發佈任務:填 target_url、選「登入後主動」、填測試帳密
FE->>BE: POST 任務參數
BE->>BE: 依 param_schema 對 secret:true 欄位 Fernet 加密
BE->>BE: 寫入 tool_params(敏感欄位為密文)
AG->>BE: 心跳(mTLS)
BE->>BE: 組裝派工時解密敏感參數
BE-->>AG: pending_tasks(含明文參數,僅傳輸中存在)
AG->>Z: 設定 context + 登入認證 + logged_in_indicator
AG->>Z: ajaxSpider.scan(爬取)
loop 輪詢爬取狀態
AG->>Z: ajaxSpider.status
end
AG->>Z: ascan.scan(主動掃描)
loop 輪詢掃描進度(支援 cancel_event / timeout)
AG->>Z: ascan.status
Z->>W: 送出測試請求
end
AG->>Z: core.htmlreport(HTML 給人看)
AG->>Z: core.jsonreport(JSON 供統計解析)
Z-->>AG: 報告內容(記憶體,不落地)
AG->>BE: ScanResult(HTML bytes + summary)
BE->>BE: 走既有 blob 通道寫 job_evidences
BE-->>FE: 執行紀錄顯示參數(敏感欄位已剝除)
U->>FE: 於證據附件檢視 ZAP 報告
原 OWASP ZAP,現歸 Checkmarx 維護,仍為 Apache 2.0 開源授權。屬 DAST(動態應用程式安全測試)——對執行中的網站主動送出測試請求,找出網頁層弱點。
走 API / daemon 模式(與 OpenVAS 同型),不走 baseline CLI 模式。理由:API 模式能做「登入後掃描」,那是稽核場景真正有價值的能力;CLI baseline 只能被動掃表層,對合規證據的價值有限。
connector 骨架照抄 openvas.py 的形狀:連客戶既有的 ZAP 服務(不自己拉容器)、輪詢進度、雙格式取報告(core.htmlreport HTML 給人看 + core.jsonreport JSON 給程式解析統計;2026-07-30 D4 改版,見 §8 D4)、支援 cancel_event 與 timeout。
使用者公司已安裝 2.17.0,以下四點在實作前必須對齊。
| 項目 | 內容 | 影響 |
|---|---|---|
| Python 套件版本 | 要用 zaproxy 0.6.0(2026-06 發布)。舊參考碼用的 0.4.0(2025-01)太舊;0.5.0 與 ZAP 2.17.0 同日(2025-12-15)發布以對齊新版 API。 |
ZAPv2 類別仍是進入點,呼叫風格不必改寫 |
| 警報去重 | 2.17.0 加入警報去重與 Systemic alert 支援(Issue 9067 / 9097)。 | alert 數量會比舊版少,統計解析要對齊新語意,不能沿用舊版計數假設 |
| 報告時間欄位 | JSON / XML 報告新增 ISO 8601 格式的 created 欄位;舊的 generatedString 官方已預告未來移除。 |
解析器不要依賴 generatedString |
| 暫存訊息保留 | headless 模式下,主動掃描產生的暫存 HTTP 訊息預設不再保留(節省磁碟)。 | 對我們的取報告流程無影響 |
沒有破壞性的 API 端點移除,主線呼叫序列可以沿用。
前同事寫的 auto-pentest 專案位於 ~/Documents/Projects/Jedicogy/auto-pentest/(DDD 分層 + MinIO 儲存)。它的價值不在架構,而在踩過坑才整理得出的那幾份知識。
| 可直接搬的知識 | 必須丟棄的部分 | 必須修的問題 |
|---|---|---|
API 呼叫主線序列ajaxSpider.scan → 輪詢 ajaxSpider.status(回字串 'running')→ ascan.scan → 輪詢 ascan.status()(回百分比數字)→ 取報告(舊碼用 reports.generate;本案依 2026-07-30 D4 改版改用 core.htmlreport / core.jsonreport 取 bytes)。注意 兩種輪詢的回傳型態不同,這個細節在 2.17.0 沒有改變。 |
整個 DDD 分層 domain service / app service / entity / MinIO storage adapter 全不需要——我們的 connector 是扁平單一類別,上傳走 task_executor 既有 blob 通道,沒有 storage adapter 的位置。 |
硬編憑證 紅線zap_auth.py 與 zap_domain_service.py 內有寫死的帳號密碼與固定 IP。憑證禁入版控是本專案紅線,一律改走 self.credentials(FR-056 D9 解密下發)與 self.params。
|
|
12 種報告模板 + sections / themes 相容矩陣 見 auto_pentest/common/enums/zap_report/report_enum.py 的 TEMPLATE_CONFIGS。2026-07-30 D4 改版 主線不再走 reports.generate 模板體系(改用 core.htmlreport / core.jsonreport 直接取 bytes),此矩陣降為背景知識,不再搬去當 FE 下拉選項來源。
|
自起 Docker 容器ZAPContainerDomainService 那一套。ZAP 應視為「客戶已安裝好的服務」,connector 只負責連上去(與 OpenVAS 同一模型)。
|
沒有取消機制、沒有 timeout 上限while status == 'running' 是無限等待。必須支援 cancel_event 與 timeout 上限。另外 print 當 log 要換成 logger。
|
|
四種登入認證設定方式 form-based / json-based / script-based / Selenium-based,加上 set_logged_in_indicator(見 auto_pentest/domain/zap/service/zap_auth.py)。登入後掃描是整個 ZAP 接入最難的部分,這裡有完整的 API 序列可參考邏輯。
|
報告落地檔案系統再上傳reports.generate(reportdir=...) 會寫檔到磁碟——而且寫的是 ZAP daemon 主機的磁碟,遠端部署模型下 agent 根本取不到(2026-07-30 D4 改版的直接動因,issue #7821)。本案改用 core.htmlreport / core.jsonreport 直接取回 bytes,記憶體轉手不落地。
|
Oracle Nashorn script engine JDK 15 之後已移除,ZAP 新版預設改用 Graal.js。舊碼的 upload_script() 已改用 Graal.js,但 java_script() 還停在 Nashorn——是會踩到的雷。
|
detection_tools 新增 ZAP(connection_type='API')+ config_field_schema(ZAP 服務位址、API key)+ param_schema(§3.3 那份,含 secret 欄位)+ setup_guide(daemon 啟動指令與 API key 設定說明)。core/task_executor_connectors/zap.py,實作 run()(依 scan_mode 分支:被動 / 主動 / 登入後主動)與 probe()(只驗連得到 ZAP 且 API key 正確,不建立掃描資源)。這一節是本案嚴重度最容易被低估的發現。設計階段原本把它記成「SPA 掃不進登入後頁面、報告涵蓋範圍侷限」——實測證明那個記載是錯的,真實後果高出一個層級。
實測後果更正 · 不是掃不到,是會打掛整台共用 ZAP
對 SPA 使用登入後掃描,掃描根本不會完成,而且傷害會擴散到其他人的任務。完整因果鏈:
logged_in_indicator 誤判——ZAP 比對的是 HTTP 回應的原始內容,而 SPA 初始 HTML 是空殼、登入後文字要靠 JS 渲染才進 DOM,原始回應裡永遠找不到特徵字串。Connection refused 而失敗。這已不是報告品質問題,而是營運層級風險——單一任務可造成全域服務中斷。
daemon log 摘錄(根因只有 insight.auth.failure : 100 那一行):
[ZAP-Insights] INFO org.parosproxy.paros.control.Control - Shutting down ZAP due to High Level Insight:
HIGH : EXCEEDED_HIGH : http://192.168.50.151 : insight.auth.failure : 100
[ZAP-Insights] INFO ... - Discard Session
... (後續大量 Selenium NoSuchSessionException,為 ZAP 死後的餘波,非原因)
ZAP 2.17.0 terminated.
zap exited with code 2 (restarting)
讀 log 時容易誤判的一點:NoSuchSessionException 洗版看起來像 Selenium 出問題,但它發生在 Shutting down ZAP 之後,是 ZAP 已死造成的下游噪音,不是根因。
僅限登入後掃描模式,被動與主動完全不受影響
被動掃描與主動掃描不建立 ZAP context、不設定任何認證、不比對登入特徵字串,因此不會產生認證失敗、不會累積計數、碰不到 Insights 門檻。第一批出貨的被動與主動兩種模式完全不受影響,兩者皆已實測通過。
SPA 的登入後掃描要通,三件事必須同時解決,缺一則等於沒做:
| # | SPA 為何不通 | 要補什麼 |
|---|---|---|
| ① | 表單登入無效——SPA 的登入是 JS 呼叫 API 取 token,頁面上沒有傳統表單提交;ZAP 的 form-based 認證是對登入頁 POST 帳密欄位,對 SPA 不會發生作用。 | json-based 認證(對登入 API 送 JSON payload) |
| ② | token 不會自動帶——SPA 多用 JWT 存 localStorage、靠 Authorization header 帶;ZAP 預設 session 管理是 cookie-based,不會把 token 塞進後續請求。 | script-based session management(要撰寫並上傳 ZAP script) |
| ③ | logged_in_indicator 不可靠——即上述無限重試的直接原因。 | 改變登入成功的判定方式 |
被排除的三個折衷走法:① 本案一併擴充 json 認證 + script session——那是「認證方式擴充 + script 管理 + 測試站台」三件獨立工作,塞進第一批會拖住已可出貨的部分,而第一批的核心價值(ZAP 接入、HTML 證據進證據池、任務層敏感參數平台能力)不依賴登入後掃描。② 只做 json 認證、session 管理留待日後——沒有中間地帶:認證送出去了但 token 帶不進後續請求,一樣掃不進登入後頁面,結果與完全不做相同,卻已付出工程成本並在 UI 上留下「看起來支援了」的誤導。③ 只換掉 logged_in_indicator 的判定——那只解第三個原因,前兩個原因未解則掃描本來就進不去,判定準不準沒有意義。
本案的處置 · 以文件與 setup_guide 擋,不做程式防護
已做:setup_guide 明白標示「請勿對 SPA 使用登入後掃描」;本案文件(本節與 D12)記載完整因果鏈與實測佐證。
裁示不做(只記錄):connector 端的自我保護——在登入模式下自行偵測認證失敗異常累積,逼近 Insights 門檻前主動中止任務並回明確訊息(如「目標網站可能是單頁式應用,登入後掃描不適用」)。這道防護可獨立於 SPA 支援先做(它不需要 json 認證與 script session 完成),即使 SPA 支援永遠不做,對「使用者誤把 SPA 當表單登入站台來掃」的情境仍然有效。決策者 2026-07-30 裁示本案只記錄不實作。
後續案:Notion CM-984(FR-058 後續:ZAP 登入後掃描支援 SPA)。實作時注意 ZAP script engine——Oracle Nashorn 在 JDK 15 後已移除、新版預設 Graal.js,舊參考碼 zap_auth.py 的 java_script() 還停在 Nashorn。
| 面向 | 實測結果 |
|---|---|
| 覆蓋範圍 | 掃到 10 個 URL,全部為登入前靜態資源——根路徑、_next/static 下的 JS / CSS chunk、favicon;無任何 API 端點、無任何登入後頁面 |
| 產出 | HTML 報告 57 KB——證據上傳鏈路本身正常(D4 的雙格式取報告已驗證可行) |
| alert 分佈 | 2 High / 8 Medium / 7 Low / 32 Informational,性質集中在站台層級組態問題(安全標頭、CSP、cookie 屬性、資訊洩漏),非應用邏輯漏洞 |
這組數字同時是匿名掃描價值邊界的具體證據:對 SPA 而言,未解決登入即等同匿名掃描——能查出組態層問題但碰不到應用邏輯。這正是登入後掃描在稽核場景的價值所在,也是它值得列為獨立後續案、而非在第一批做半套的理由。
原稱「InSpec / CINC Auditor」,2026-07-31 統一為「CINC Auditor」——採用的就是 CINC,名稱裡掛著會混淆授權判斷的 InSpec 字樣沒有好處(見 D5)。本節保留 InSpec 字樣之處均為指涉上游專案或 profile 格式,非產品內的工具名稱。
組態合規檢測引擎,用 Ruby DSL 撰寫檢測規則(profile)。本案接入它是為了解決 Windows 主機組態檢測這個既有缺口。
FR-057 的 OpenSCAP connector 是 SSH 加 Linux 寫死的(/usr/bin/oscap、/etc/os-release、sudoers),無法處理 Windows 主機。這個缺口已記錄於 Notion CM-953(「FR-057 後續:異質 OS 環境(Windows 等非 Linux 主機)的檢測支援方案」,狀態=討論)。
案件歸屬
本 FR 吸收 CM-953,不另開平行卡片。CM-953 於本案子需求 FR-058.2 落地時一併結案。
OpenSCAP 官方已正式聲明放棄 Windows native 支援——官方 docs/windows.md 明文標示 no longer usable。最後一個勉強可用的版本是 1.3.4,且不再修 bug。
不是設定問題,是能力缺失:OpenSCAP 沒有 WinRM transport、沒有 Windows OVAL probe。無論怎麼配置都掃不到 Windows 目標。
唯一能做「單機遠端 WinRM 掃多台 Windows」的 SCAP 系工具是 CIS-CAT Pro,但需要 CIS SecureSuite 付費會員資格。
SCC 偏「目標本機安裝 agent」模式,與我們 agentless 的既有模型不合。
| 理由 | 說明 |
|---|---|
| agentless | 目標主機不必安裝掃描器,與現有 OpenSCAP-SSH 是同一種遠端模型,架構一致。 |
| 雙 transport 單一介面 | SSH(Linux)與 WinRM(Windows)走同一套 CLI 與 profile 格式,--reporter json 統一輸出,connector 不必寫兩套解析。 |
| 授權成本 | CIS-CAT 要付費才能做的事,CINC Auditor 免費(Apache 2.0)就能做。 |
| 一個 connector 三條線 | Windows 組態檢測 + Linux GCB + Windows GCB,三者共用同一個 connector。 |
授權關鍵 · 必須用 CINC Auditor 而非官方 InSpec
採用 CINC Auditor——Apache 2.0 授權的自由重建版,是 Progress 官方認可的 drop-in 替代品。不要用官方 InSpec 6+ 商業 binary,後者需要接受 Chef EULA 並持有 license key。這一點在採購與部署文件上都要寫明。
CINC 裝在 agent 本機、由它自己連出去——與 OpenSCAP 方向相反
設計階段把 CINC 歸在「遠端登入目標主機執行」,與 OpenSCAP 同一類。實作後發現方向是相反的:
oscap)裝在目標主機上,connector 用 SSH 登入後在對端執行。/usr/bin/cinc-auditor,隨 agent image 出貨),由它自己主動連出去掃目標;目標主機完全 agentless、不必安裝任何東西。connection_type 仍宣告為 SSH / WinRM——那是目標主機開放的協定,不代表 connector 自己建連線(連線是 CINC 引擎建的)。
為什麼這件事非記不可:① 這才是 D6「雙 transport 邊際成本很低」的真正原因——兩個 transport 都是同一支 CLI 的參數,不是兩套連線程式碼。② D7 的 content 外部載入接口直接建立在此架構假設上——若誤以為 content 要送到目標主機,設計會做錯,而且要到端到端驗收才會發現。③ 原記載的「對照 openscap.py 的 SSH 模式」需更正:openscap.py 只有多目標處理與 host_failures 可借鏡,連線層完全不同。
| 約定 | 理由 |
|---|---|
憑證走 --config、不進 argv | argv 在目標主機的行程表上人人看得到,等於把密碼公開 |
| 授權紅線以 exit 172 硬失敗釘住 | CINC 若誤跑成需接受 EULA 的官方 InSpec,退出碼會是 172;把它當硬失敗處理,避免授權問題靜默通過(D5 的程式層防線) |
| probe 的憑證檢查排在引擎檢查之前 | 先報「你沒填憑證」比先報「引擎不存在」更貼近使用者真正要修的東西 |
detection_tools 新增 CINC Auditor,connection_type 需涵蓋 SSH 與 WinRM 兩種目標 transport(透過任務參數或設定欄位選擇)。inspec.py,包裝 CINC Auditor CLI,統一走 --reporter json 解析結果;沿用 FR-057 建立的 host_failures 逐台失敗回報通道。驗收結果 雙 transport 皆實跑通過
SSH(Linux 目標 192.168.50.151)與 WinRM(Windows 目標 192.168.50.160)各自實跑一輪完整掃描,非僅測試連線:151 為 pass 131 / fail 60、160 為 pass 300 / fail 472。D6 的雙 transport 定案已由實跑證實。
另註:顯示名稱已由「InSpec / CINC Auditor」統一為「CINC Auditor」——採用的就是 CINC,名稱裡掛著會混淆授權判斷的 InSpec 字樣沒有好處(D5)。
政府組態基準(TWGCB),我國公務機關資通設備的組態安全基準。屬合規稽核情境的高需求項目。
GCB 與 OpenSCAP 一樣要分 Windows 與 Linux 兩邊,差別只在 content 不同;而 FR-058.2 的 CINC Auditor 本來就是 SSH 加 WinRM 雙 transport 的同一套引擎,因此兩邊一次由它承擔,GCB 不另立引擎、不另寫 connector。這部分已拍板,見決策 D7 引擎層。
釐清 · Linux GCB 為何不沿用既有的 OpenSCAP
技術上 Linux GCB 確實可以用 OpenSCAP 執行——它是 Linux 原生工具,FR-057 也已經有可用的 SSH connector。不選它的原因不在 Linux 這一側,而在維護成本:Windows GCB 只有 CINC 這條路走得通(OpenSCAP 的 Windows 支援已被官方放棄,見 §5)。若 Linux 走 OpenSCAP,同一份 GCB 基準就要維護兩種 content 格式——Linux 寫成 XCCDF + OVAL、Windows 寫成 InSpec profile,同一條 TWGCB 要求要用兩種語法各寫一次,TWGCB 改版時也要改兩份。
引擎統一的目的是 content 格式統一,這才是 D8 定案 InSpec profile 的真正理由。
OpenSCAP 的定位不變,兩者按「基準來源」分工而非按 OS 分工:OpenSCAP 繼續服務 CIS / STIG 這類有官方 SCAP content(SSG)可直接取用的國際基準;CINC 負責 GCB 這種沒有官方 content、必須自產,且 Windows 端別無選擇的基準。同一台 Linux 主機要查 CIS 就派 OpenSCAP 任務、要查 GCB 就派 CINC 任務,兩個任務可掛在同一個控制項底下、證據進同一個證據池——這正是 CM-953 已定的「一任務一工具,靠多任務處理異質情境」模型,不需要改架構。
本案範圍 · 只做引擎與一個最小 Demo
決策者已裁示:本案只做引擎,並拿 Windows 最單純的一個基準當 Demo,證明端到端鏈路可運作即可。content 產製(把每一條 TWGCB-ID 轉成檢查規則)與後台 content 更新機制都列為後續,不在本案範圍。完整裁示見決策 D7。
這樣切的理由是:FR-058.3 因此有明確且短的終點,而且它驗證的正是風險最高的部分——WinRM 連線、認證、profile 執行、結果解析。content 反而是最沒有技術風險的部分(純人力活),一條規則也能證明鏈路通。
設計約束(本案必須做到):content 不可硬編進 connector 或 agent image,必須設計成可從外部載入,否則日後要接後台更新機制會很難改。
以下盤點不是本案的工作清單,而是後續評估 content 投入時的背景資料;在本案的作用只有一個——解釋為何 Demo 平台選 Windows。資料來源為 NICS 兩個官方頁面:GCB 說明文件(頁面標示 2026/6/26 更新)與 GCB 部署資源(頁面標示 2026/3/30 更新)。
| TWGCB 編號 | 基準對象 | 版本 | 提供格式 |
|---|---|---|---|
TWGCB-01-010 | Microsoft Windows 11 | v1.1 | 僅 DOCX / PDF |
TWGCB-01-007 | Microsoft Windows Server 2016 | v1.3 | |
TWGCB-01-009 | Microsoft Windows Server 2019 | v1.2 | |
TWGCB-01-011 | Microsoft Windows Server 2022 | v1.1 | |
TWGCB-01-008 | Red Hat Enterprise Linux 8 | v1.3 | |
TWGCB-01-012 | Red Hat Enterprise Linux 9(伺服器) | v1.2 | |
TWGCB-01-013 | Red Hat Enterprise Linux 9(工作站) | v1.2 | |
TWGCB-01-014 | Ubuntu 22.04 LTS | v1.2 | |
TWGCB-01-015 | Apple macOS 15 | v1.0(2026/6 才發布) |
另有瀏覽器說明文件:Google Chrome、Mozilla Firefox、Microsoft Edge、Safari——同樣只有 DOCX 與 PDF。
| 檔案 | 涵蓋對象 | 更新日期 |
|---|---|---|
| 作業系統 GPO 檔 | Windows 11/Windows Server 2016/2019/2022 | 2025/11/7 |
| 瀏覽器 GPO 檔 | Google Chrome、Microsoft Edge | 2026/3/30 |
| Mozilla Firefox 部署設定檔 | Firefox | 2022/12/26 |
| Windows 11 新增政策範本檔 | Windows 11 | 2025/11/7 |
| Google Chrome 政策範本檔 | Chrome | 2026/3/30 |
| Microsoft Edge 政策範本檔 | Edge | 2024/12/13 |
| LocalGPO 安裝程式 | Windows 共用工具 | 2022/12/28 |
關鍵落差 · 本次查訪最重要的發現
Linux 與 macOS 在部署資源頁完全沒有任何檔案。說明文件有 RHEL 8/RHEL 9(伺服器 + 工作站)/Ubuntu 22.04/macOS 15 的完整基準,但沒有任何對應的機器可讀部署檔——連 shell script 都沒有,只有 DOCX 與 PDF。
這證實並擴大了 NICS FAQ v1.6 §7.1「未提供 RHEL8 等平台自動化檢測工具」的說法:實際情況是除 Windows 之外,所有平台都沒有機器可讀來源。日後評估 content 投入時,成本不是一個平均值,而是分裂成兩個量級完全不同的世界。
| 平台 | 可用來源 | 產製方式 | 自動化程度 |
|---|---|---|---|
| Windows 4 個 OS + 2 瀏覽器 |
GPO backup ZIP(含 registry.pol + GptTmpl.inf) |
可寫產生器解析產出骨架,再人工補判定邏輯 | 約七成 |
| Linux RHEL 8/RHEL 9 ×2/Ubuntu 22.04 |
只有 DOCX / PDF | 逐條人工閱讀文件、手寫檢查規則 | 幾乎零 |
| macOS 15 | 只有 DOCX / PDF | 同 Linux;且現有 connector 未曾處理 macOS | 幾乎零 |
這份落差就是本案 Demo 選 Windows 的理由:Windows 有 GPO 檔這個結構化來源可直接參考,寫一個最小 Demo profile 的成本最低;Linux 只有 DOCX,連一條 Demo 規則都要先人工翻譯文件敘述。要再次強調——這是選 Demo 平台的理由,不是要在本案做 Windows content 工程。
澄清一個容易誤解的點
常見的提問是「是不是要把官方 PDF 轉成 content」。實際情況是:Windows 不需要——有 GPO 檔可解析;Linux 需要,但那不是「轉檔」,而是逐條人工把文件敘述翻譯成檢查邏輯。content 工程已不在本案範圍,此處僅為背景說明。
Windows 特有的陷阱 · tattoo settings
GPO 設定分兩種位置:HKLM\Software\Policies 底下屬「正規政策區」,套用前會先清空;不在此位置的設定則會殘留(業界稱 tattoo settings)。
對檢查規則的實際風險是:規則若讀取非 Policies 路徑,可能讀到上次殘留的舊值而誤判為通過,實際上政策根本沒生效。這種誤判在稽核情境屬嚴重問題(報告說合格、實際不合格)。日後撰寫 content 時必須處理,本案的 Demo profile 也應避開此陷阱。同列於 §9 風險表。
| 面向 | Linux GCB | Windows GCB |
|---|---|---|
| 引擎 | 統一走 FR-058.2 的 CINC Auditor——Linux 用 SSH transport、Windows 用 WinRM transport,同一套 CLI 與 profile 格式(OpenSCAP 在 Windows 走不通,不作為 GCB 的引擎選項) | |
| 引擎層工作量 | 已由 .2 承擔 GCB 本身不需要新的 connector | |
| 本案 content | 不做 | 一份最小 Demo profile 僅用於驗證鏈路 |
| content 載入方式 | 一律從外部載入,不硬編進 connector、不打包進 agent image(本案設計約束,見 D7) | |
| 後續 content 產製 | 列為後續 與後台 content 更新機制一併另案評估 | |
detection_tools 新增 GCB 條目,引擎指向 FR-058.2 的 CINC Auditor(Windows 與 Linux 共用)。原規劃的抽象層裁示不做——那個能力本來就在
原本 T-3.2 規劃要「做一個 content 外部載入接口」。實作前查證發現:CINC 原生就支援三種 content 來源,而 params.profile 直接進 cinc-auditor exec 的 argv——外部載入能力本來就存在,另做一層抽象只是把上游能力重新命名一次,而且會限縮它(上游支援的來源形式會被我們的抽象層擋掉一部分)。
裁示:不做抽象層,改為逐條驗收 D7 的三條約束並寫成文件。三條約束——不硬編、不打包進 agent image、換 content 零程式改動——皆已實跑驗證通過(兩份公開 content 對 SSH 與 WinRM 各跑一輪,error 0)。
實跑踩到的坑 · 裸 GitHub 網址走的是 git fetcher,不是 tarball 下載
把裸 GitHub 網址當 profile 來源時,CINC 走的是 git fetcher(需要主機上有 git),而不是直接下載 tarball。agent image 原本沒裝 git,首次實跑就炸在 profile 解析階段——已於 agent 端補裝。
做了對照實驗確認:tarball 形式對 git 零相依——把 /usr/bin/git 移走後,裸網址失敗、tarball 照常跑通。
驗收結果 · 鏈路通過,但 Demo profile 不足以對客戶展示
鏈路驗收通過:WinRM 連線 → 認證 → profile 執行 → 結果解析 → 證據上傳整條走得通,符合 D7 定的驗收條件(鏈路可運作,不是涵蓋規則數)。
但 Demo profile 只有 2 項檢查——決策者實測後表示不能拿去給客戶 Demo。這不推翻 D7 的範圍裁示(content 產製本來就不在本案),而是確認了「鏈路驗證用的最小 Demo」與「可對外展示的 Demo」是兩件事。後續案:Notion CM-992。
網路埠與服務探測工具。在合規情境對應「開放服務盤點」「非必要服務關閉」這類控制項的證據。技術上是四個工具裡最單純的一個。
(改案後定案)走 connection_type='SSH':nmap 由客戶安裝在自己指定的一台執行主機上,agent 透過 SSH 登入該主機執行掃描,與已上線的 OpenSCAP 完全同構。連帶結果——nmap 需要 SSH 憑證(requires_credentials=TRUE),不再是零憑證工具。
(原設計,已被取代)原定「我們驅動、agent 本機執行 CLI」,agent 直接呼叫 nmap -oX,並以此當作 connection_type='CLI' 型態的第一個實際使用者。改案後該型態至今仍無任何使用者(FR-056 定義後未曾被用過)。
理由一 · 技術:容器化矛盾——「不 bundle」與「agent 跑得到」互相打架
實作前才發現原 CLI 設計有個隱含假設站不住:agent 跑在 Docker container 內,看不到主機上安裝的 nmap(容器有獨立檔案系統)。於是「不 bundle 進 image」與「客戶自備、agent 呼叫得到」兩個條件互相矛盾——客戶把 nmap 裝在主機上,容器裡的 agent 依然找不到它。
理由二 · 授權:查證 NPSL 後確認不能 bundle,而且理由比原記載更強
原記載只寫「NPSL 授權不可 bundle,NPSL 對依賴使用者既有安裝副本的程式有豁免條款」。查證 NPSL v0.95 §3(出處 https://svn.nmap.org/nmap/LICENSE)後,實情是:
①「只用子行程呼叫、不連結函式庫」這個 GPL 慣用的免責論點,在 NPSL 底下不成立。§3 明文列舉的「衍生作品」定義包含這一條:
Is designed specifically to execute Covered Software and parse the results (as opposed to typical shell or execution-menu apps, which will execute anything you tell them to).
我們的 connector 正是「專門呼叫 nmap 並解析其 XML 輸出」,正中此條——該條款是特意加來堵掉這個論點的(NPSL 基於 GPLv2 但加了額外限制)。GPLv2 的 mere aggregation 條款也救不了,因為它只適用於「not based on the Program」的作品,而 §3 已把我們定義為 based on。官方 Legal Notices 另明說免費授權不允許 nmap 被用於商業軟體或硬體產品的再散布(明列 appliances / virtual machines / traditional applications),並為此販售 Nmap OEM Edition。
② 但 §3 最後一段自己留了出口,而 SSH 型正好落在裡面。該段表明:軟體若解析的是使用者提供的結果、或執行的是「使用者早已安裝在自己系統上的 nmap」,授權方不主張控制(Licensor does not purport to control through this license any software which does not require the rights granted herein)。SSH 型態就是這個描述——nmap 是客戶裝在自己主機上的,我們只是遠端下指令。
| 方案 | 排除理由 |
|---|---|
掛載主機 binary 進容器-v /usr/bin/nmap:/usr/bin/nmap:ro 加共享函式庫目錄 |
nmap 非靜態連結,依賴 libssl / libpcap / liblua 等一串共享函式庫。掛主機的 lib 目錄進容器是在與容器內 glibc 版本賭運氣,且可能蓋掉容器自身函式庫把 agent 弄壞;架構也綁死 x86_64。這把「客戶自備」變成一個需要客戶理解共享函式庫的任務,故障時難以遠端診斷。 |
| 客戶自建一層 image 以我方 image 為 FROM,自行 apt install nmap 後 build |
技術上可行且乾淨(函式庫由套件管理員解決),法律上也成立(nmap 由客戶裝入,非我方散布)。排除的是持續性負擔:客戶每次 agent 升版都要改 Dockerfile 版號並重新 build;且「誰把它裝進去」雖在法律上是關鍵,技術結果與我方 bundle 完全相同,這種形式上的區隔在交付溝通上彆扭。 |
| 購買 Nmap OEM 授權 | 本案不採,未評估成本。若日後產品策略需要零客戶設定的 nmap 整合,這是唯一能合法 bundle 的途徑,列為備查。 |
拍板走 SSH 型的理由 · 「最沒爭議」
法律上落在 NPSL 自留出口內;技術上與已上線的 OpenSCAP 同構(可複用 SSH 連線處理、私鑰暫存、host_failures 逐台回報);對客戶只需在指定主機 apt install nmap,無任何 docker 操作。
requires_credentials=TRUE)。connection_type='CLI' 仍然沒有實際使用者,原本「Nmap 是 CLI 型態首用」的價值消失。openscap.py 的 SSH 多目標模式。start_execution 認得「這個工具本來就不需要憑證」是合法狀態(平台能力保留,見 D9)。detection_tools 新增 Nmap(connection_type='SSH'、requires_credentials=TRUE)+ config_field_schema(SSH 憑證,對照 OpenSCAP)+ param_schema(掃描目標 / 掃描類型 / 埠範圍)+ setup_guide(客戶在自己指定的主機安裝 nmap、NPSL 授權說明、SSH 連線前提、埠掃描授權警語)。nmap.py——SSH 登入執行主機 → nmap -oX → XML 解析 summary + 報告上傳,逐台 host_failures 回報;probe() 分四段檢查(SSH 連線 / 認證 / 該主機 nmap 存在可執行 / 權限)。不 bundle 進 image。驗收結果 實跑通過
對真實網段實跑一輪,HTML 證據可產出並進入證據池。
決策全數拍板——原先唯一保留的 D7 content 層已於 2026-07-30 由決策者裁示收斂範圍,本案不再有開放問題。(原 D10 隨其對應工具一併移出本案,其餘編號維持不變,避免與先前溝通過的編號錯位。2026-07-31 更新:SonarQube 已重新納入為 FR-058.5,決策以 D13–D16 定案、D10 編號維持 tombstone 不重用,詳見 design.md §2 與 §4.6,本稿不重列。)實作期間有兩次改案與一項追加:D4 改版(ZAP 證據 PDF→HTML)、D9 改案(Nmap CLI 型→SSH 型)、D12 追加(ZAP 登入後掃描的 SPA 限制,實測後補列)。
D1 · 任務層敏感參數的實作範圍 已拍板
問題:只做加密存取,還是連 FE 執行紀錄的顯示剝除與稽核 log 一起做?
影響範圍:BE 加解密接線、FE 執行紀錄渲染、稽核事件內容。
定案:一起做。否則等於「加密了卻在 UI 洩漏」——資料庫是密文,但畫面上照樣看得到帳密,防護實質等於零。稽核 log 同理,寫入敏感值等於在另一張表留下明文副本。
D2 · ZAP 掃描深度的預設值 已拍板
問題:三種模式——被動 baseline(安全快速)/主動掃描(會產生實際攻擊流量,需目標方授權)/登入後主動(最有價值但需要測試帳號)。預設給哪一個?
影響範圍:param_schema 的預設值、FE 警語文案。
定案:schema 三種都支援,預設給被動。主動類選項在 FE 加明確警語(會產生攻擊流量,請確認已獲目標方授權)。預設值選錯的代價不對等——預設被動最多是掃得淺,預設主動則可能在使用者未察覺的情況下對正式環境送出攻擊流量。
D3 · ZAP 服務的部署形態 已拍板
問題:客戶自備已安裝的 ZAP daemon(同 OpenVAS 模型)/ agent 端自帶 ZAP container?
影響範圍:agent image 大小與維護責任、setup_guide 內容。
定案:客戶自備。與 OpenVAS / OpenSCAP 一致,agent 不背負拉容器的責任。setup_guide 欄位寫清楚 daemon 啟動指令與 API key 設定方式。使用者公司已安裝 2.17.0,這條路本來就是現況。
D4 · ZAP 證據格式 已拍板 2026-07-30 改版
問題:人可讀格式(給人看)+ JSON(解析統計)雙取,或只存單一格式?
影響範圍:connector 取報告邏輯、證據池內容。
定案(2026-07-30 改版):雙取,精神對齊 OpenVAS connector 既有模式——core.htmlreport() 回傳的 HTML bytes 上傳當證據(檔名 ZAP掃描報告_<target>_<date>.html)、core.jsonreport() 回傳的 JSON 只在記憶體解析出 summary 不上傳。這樣證據池維持人可讀,統計數字又有結構化來源,且不會在證據池塞入兩份等價內容,ZAP 端零設定、維持 D3 部署模型。
改版緣由:原定案「PDF 上傳當證據(reports.generate)」實作時發現不可行——ZAP 的 reports API 只把檔案寫進 ZAP daemon 主機的磁碟、回傳路徑字串,官方沒有回傳檔案內容的端點(issue #7821 至今未實作);而 D3 定的部署模型是遠端 daemon,agent 取不到那個路徑的檔案。
改版時被排除的走法:①「PDF + 要求客戶開檔案傳輸通道」——違反 D3 部署模型,等於從後門把部署負擔帶回來;②「檔案傳輸為主、HTML 自動退回」——兩條路徑的長期維護與測試成本換一個罕用 fallback,違反本案「不做無意義測試」紀律。
已知風險與處置:證據池 PDF 預覽鏈不吃 HTML,接受「可下載、不可預覽」為第一批出貨標準;若日後要求可預覽,可在 agent 端用既有 LibreOffice --convert-to pdf 補(ZAP 端仍零設定),但轉檔品質未驗證故不預做。
D5 · InSpec 版本選擇 已拍板
問題:官方 InSpec 商業 binary(需 Chef EULA + license key)/ CINC Auditor(Apache 2.0)?
影響範圍:授權合規、agent image 內容、客戶部署文件。
定案:CINC Auditor。授權乾淨(Apache 2.0),且是 Progress 官方認可的 drop-in 替代品,功能等價。採用商業 binary 會把 license key 管理問題帶進每一個客戶部署。
D6 · InSpec connector 的 transport 範圍 已拍板
問題:只做 WinRM(Windows)/ SSH + WinRM 雙 transport 一次到位?
影響範圍:FR-058.2 工作量、FR-058.3(GCB)的可複用程度。
定案:雙 transport 一次到位。同一套 CLI 與 profile 格式,多做一個 transport 的邊際成本很低,而 GCB 的 Linux 線立刻就能複用。只做 WinRM 的話,GCB Linux 那條線之後還要回頭補一次。這條同時是 D7 引擎層的實作依據——GCB 兩邊都掛在這個 connector 上。
D7 · GCB 的引擎優先與 content 後置 已拍板
釐清:GCB 本質上與 OpenSCAP 一樣要分 Windows 與 Linux 兩邊,差別只在 content 不同。這其實是兩個不同層次的問題:引擎覆蓋範圍(兩邊都要掃得到)與 content 產製(每條 TWGCB-ID 都要人工轉成檢測規則,且要跟著改版維護)。前者是一次性工程、後者是持續性投入,兩者不必綁在同一個決策裡——本案的裁示就是把它們拆開,只做前者。
Windows 與 Linux 雙邊一次到位,由 FR-058.2 的 CINC Auditor connector 承擔,GCB 不另立引擎。CINC Auditor 本來就是 SSH 加 WinRM 雙 transport 的同一套引擎(見 D6),GCB 的兩邊本來就該由它一起處理,不需要拆成兩條路徑。這一層跟著 FR-058.2 走,不產生額外的引擎工程。
決策者裁示:本案只做引擎與一個最小 Demo profile,證明端到端鏈路可運作即可。Demo 取 Windows 最單純的一個基準,驗證 WinRM 連線 → 認證 → profile 執行 → 結果解析 → 證據上傳整條走得通。不做 content 工程。
以下兩件事都列為後續,日後另案評估:
這樣切的理由:FR-058.3 因此有明確且短的終點,而且它驗證的正是風險最高的部分——WinRM 連線、認證、profile 執行、結果解析。content 反而是最沒有技術風險的部分(純人力活),一條規則也能證明鏈路通。
範圍裁示 · macOS 15 一併排除 已排除
macOS 15(TWGCB-01-015)不納入本案範圍。理由:基準本身 2026/6 才發布、版本尚新,且現有 connector 未曾處理 macOS。日後另議。
content 不可硬編進 connector 或 agent image,必須設計成可從外部載入。這是本案要做到的設計約束,不是後續事項——雖然 content 產製與後台更新機制都不在本案,但接口現在就要留。
理由:content 本質上是會持續變動的資料,不該綁在程式版本裡。日後要接後台更新機制時,若當初把 profile 打包進 agent,等於每次更新 content 都要重新發版並更新所有客戶站點的 agent。這個接口現在留,成本極低;事後補,要改動 connector 結構。
定案:引擎先行、content 後置,並在本案就把外部載入接口留好。FR-058.3 的驗收條件是「鏈路可運作」,不是「涵蓋多少規則」。
D8 · GCB 引擎選擇 已拍板
問題:InSpec profile / XCCDF + OVAL(沿用 OpenSCAP)?
影響範圍:內容格式、後續維護的技能需求。
定案:InSpec profile。Ruby DSL 比 XCCDF + OVAL 好寫也好維護,而且 Windows GCB 只有這條路走得通——選 XCCDF 等於同一份基準要維護兩種格式(Linux 用 XCCDF、Windows 用 InSpec),長期成本明顯較高。這與 D7 引擎層的定案一致:既然兩邊都由同一套 CINC Auditor 執行,content 自然也只需要維護 InSpec profile 一種格式。
D9 · Nmap 的整合型態與零憑證放行 已拍板 2026-07-30 改案
原問題:放寬 start_execution 的憑證檢查(依工具是否宣告需要憑證判斷)/為 Nmap 建立一筆空的租戶設定當佔位?
影響範圍:detection_tools 表結構、start_execution 檢查邏輯、Nmap 的 connection_type 與 agent connector 形狀。
定案(原有部分,仍然成立):放寬檢查。在 detection_tools 增加「是否需要憑證」的宣告欄位(requires_credentials),讓平台知道零憑證是一種合法狀態,而不是塞假資料繞過檢查。塞空設定當佔位會留下一筆語意不明的資料,日後看到的人無法判斷那是刻意還是遺漏。此為平台級能力,改案後仍然正確且保留——只是 nmap 不再是它的第一個使用者。
改案(2026-07-30):Nmap 由 connection_type='CLI' 改為 'SSH'。原定案是「agent 本機執行 CLI、客戶自備 nmap、不 bundle 理由僅記『NPSL 授權』」。改案後:nmap 由客戶安裝在自己指定的執行主機,agent 透過 SSH 登入該主機執行,需要 SSH 憑證(requires_credentials=TRUE)。
兩個獨立的改案理由:① 技術矛盾——agent 跑在 container 內看不到主機上的 nmap,「不 bundle」與「客戶自備、agent 呼叫得到」互相打架;② 授權查證——NPSL v0.95 §3 的衍生作品定義明文涵蓋「專門執行本軟體並解析其結果」的程式(Is designed specifically to execute Covered Software and parse the results),我們的 connector 正中此條,「只用子行程呼叫」這個 GPL 慣用免責論點在 NPSL 底下不成立;但同條末段自留出口——不主張控制「執行使用者早已安裝在自己系統上的 nmap」的軟體,SSH 型正落在此描述內。
被排除的替代方案:掛載主機 binary 進容器(共享函式庫版本賭運氣、可能弄壞 agent)/客戶自建一層 image(法律成立但每次升版都要重 build,且技術結果與 bundle 相同、交付溝通彆扭)/購買 Nmap OEM 授權(本案不採,列為備查——若日後要零客戶設定的整合,這是唯一合法 bundle 途徑)。完整推導見 §7.1。
連帶影響:connection_type='CLI' 至今仍無任何實際使用者;agent connector 範本從「本機子行程」改為對照 openscap.py 的 SSH 多目標模式。
D11 · _TOOL_ID_TO_CODE 硬編映射的處理 已拍板
問題:維持硬編(每加一個工具就要改 agent 並重新發版)/改為由派工 payload 夾帶 tool code?
影響範圍:agent factory、派工 payload 結構、三環境 seed 一致性要求。
定案:改為派工夾帶 code。一次加四個工具會讓硬編映射的脆弱性放大——DEV / STG / POC 三環境的 seed id 必須完全一致,錯一位就整條派工錯配到別的 connector(FR-057 的 seed SQL 檔頭已留有此警告:例如 ZAP 任務被送給 OpenSCAP 執行),而且這種錯誤不會拋例外、只會安靜地跑錯工具。趁這次一併解決。
界線說明:這項改動不會讓新增工具完全免於動 agent——connector 實作本身仍必須裝進 agent(那是實際執行掃描的程式碼),因此每加一個全新工具,agent 仍需更新一次。D11 消除的是另一個更脆弱的問題:額外維護一份 id 對照表、且三個環境的 seed id 必須完全一致。
D12 · ZAP 登入後掃描的 SPA 支援範圍 已拍板 2026-07-30 實測後追加
問題:本案的登入後掃描要不要涵蓋 SPA(單頁式應用)?
影響範圍:ZAP param_schema 的登入四欄語意、setup_guide 警語、共用 ZAP daemon 的服務穩定性。
定案:本案(第一批)只支援傳統表單登入(form-based),param_schema 的 login_url / login_username / login_password / logged_in_indicator 四欄即為此模型。SPA 的登入後掃描列為後續另案(Notion CM-984),本案文件與 setup_guide 明白標示此限制,不做半套。
技術原因有三,且三者必須同時解決才會通:① 表單登入無效(SPA 靠 JS 呼叫 API 取 token,需要 json-based 認證);② token 不會自動帶(SPA 多用 JWT 存 localStorage 靠 header 帶,ZAP 預設是 cookie-based session,需要 script-based session management);③ logged_in_indicator 對 SPA 不可靠(ZAP 比對 HTTP 回應原始內容,SPA 初始 HTML 是空殼、文字要 JS 渲染才進 DOM)。
🔴 實測後果更正(2026-07-30,嚴重度高於原記載一個層級):原記載「掃描仍會完成、只是涵蓋範圍侷限於登入前頁面」是錯的。第 ③ 項的重跑登入是無限迴圈——認證失敗持續累積,達到 ZAP 2.17.0 Insights 機制門檻後 ZAP daemon 主動關閉自己,掃描根本不會完成。log 關鍵行:Shutting down ZAP due to High Level Insight: HIGH : EXCEEDED_HIGH : insight.auth.failure : 100。由於 ZAP 是全租戶共用的單一服務,單一 SPA 登入任務會連帶打斷同一台 ZAP 上其他正在執行的掃描(實測同時發生:另一任務在重啟空檔收到 Connection refused)。這已非報告品質問題,而是營運層級風險。完整因果鏈與實測數據見 §4.2。
範圍界定:僅限登入後掃描模式。被動與主動模式不建 context、不設認證、不比對特徵字串,不累積認證失敗,碰不到 Insights 門檻——兩者皆已實測通過、完全不受影響。
被排除的三個折衷:① 本案一併擴充 json 認證 + script session(三件獨立工作,會拖住已可出貨的部分,而第一批核心價值不依賴登入後掃描);② 只做 json 認證、session 管理留待日後(沒有中間地帶——認證送出去但 token 帶不進後續請求,結果與完全不做相同,卻已付出成本並在 UI 留下「看起來支援了」的誤導);③ 只換掉 logged_in_indicator 的判定(只解第三個原因,前兩個未解則本來就進不去)。
已知但本案不做(只記錄):connector 端的自我保護——登入模式下自行偵測認證失敗異常累積、逼近門檻前主動中止並回明確訊息。可獨立於 SPA 支援先做,決策者 2026-07-30 裁示本案只記錄不實作。
以下不是待決策事項,是實作時會遇到、或本案會放大的既有問題。列出來是為了在拆任務時不被意外絆住。
| 項目 | 現況 | 本案的關聯 |
|---|---|---|
tenant_config_id 從未被寫入 |
job_execution_detection_tools.tenant_config_id 永遠是 NULL,實際靠 (tenant_id, detection_tool_id) 的 UNIQUE 約束回退查詢。 |
加工具不會惡化,但這個欄位存在卻不被使用,日後看到的人容易誤判其用途 |
| 證據關聯靠字串前綴 | 證據與執行的關聯是靠 job_evidences.description 的字串前綴 [檢測工具] {agent_task.uid}——脆弱的字串耦合。 |
四個工具都會走這條路徑,任何改動 description 格式的需求都會全面波及 |
id=3 SonarQube 佔位列 |
detection_tools 內仍有 FR-056 留下的 id=3 sonarqube 佔位列,status='coming_soon'、config_field_schema 為空。 |
(2026-07-31 更新)原「本案不處理維持 coming_soon」已被推翻——SonarQube 重新納入為 FR-058.5,此列改由 T-5.1 UPDATE 成可用工具(不是 INSERT 新列,詳見 design.md §4.6 seed 特殊點)。原記載「硬編 id 表中無對應 connector 的一格」問題在 D11 解耦 + T-5.2 補 connector 後消失 |
_pick_agent 無負載平衡 |
直接取 agents[0]。 |
工具變多後同一站點併行任務量上升,單一 agent 被打滿的機率提高 |
| 心跳延遲 5 分鐘 | agent 心跳預設 300 秒,任務發佈到實際開跑最長會有 5 分鐘延遲。 | UX 這是設計上的正常行為,但 UI 要讓使用者理解「按下開始後沒有立刻動」不是故障 |
| FE 硬編死碼工具清單 | WorkflowSetupEditor.vue 有一段 toolsMenu = ["Nessus","Nmap","OWASP ZAP","Wireshark"],與 detection_tools 完全脫鉤。 |
會造成混淆 本案加入 ZAP 與 Nmap 之後,畫面上兩處都出現 Nmap,但其中一處是死的。建議一併清掉——已列入本案收尾的技術債清理(清掉前須先確認該段是否只是選單裝飾,若有實際功能要改成讀 API 而非直接刪) |
| ZAP daemon 為全租戶共用單一服務 | 不是每個任務一個實例。實測已證實單一任務可造成全域中斷(SPA 登入掃描撞 Insights 門檻 → daemon 自關 → 其他任務收到 Connection refused)。 |
營運層級 本案以 setup_guide 警語與文件記載處置,connector 端自我保護裁示不實作(見 D12 與 §4.2)。這條風險在 SPA 支援(CM-984)落地前持續存在 |
config.detection_tools 無 i18n 機制 |
name / description / setup_guide 與 param_schema 的 label 全是中文寫死在 DB,切換英文介面整頁仍是中文。FR-056 建表時就存在。 |
平台級缺口 本案一次加四個工具、又大幅擴充八張卡片的描述文案,缺口被放大但未惡化結構。決策者裁示先不動 |
| Windows GPO tattoo settings | GPO 設定分兩種位置:HKLM\Software\Policies 底下屬正規政策區、套用前會先清空;不在此位置的設定會殘留(業界稱 tattoo settings)。 |
誤判風險 檢查規則若讀取非 Policies 路徑,可能讀到上次殘留的舊值而誤判為通過,實際上政策根本沒生效。在稽核情境屬嚴重問題(報告說合格、實際不合格)。日後撰寫 content 時必須處理,本案 Demo profile 也應避開此陷阱 |
| 文件債 | docs/claude/database-schema.md 完全沒有 config schema 與 detection 三表的記載(grep 零命中)。 |
本案要動這三張表,是補上記載的合適時機 |
依已拍板的優先序切成 5 個子需求加 1 個橫向項。2026-07-30 決策者進一步裁示分批出貨、ZAP 優先上線——不再四個工具一起收,改成三個批次序列推進,批次結束才做一次端到端測試。子需求本身的內容與優先序不變,變的是交付節奏。
%%{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'}}}%%
graph LR
subgraph B1["第一批 · ZAP 上線 (CM-958〜970)"]
PX["橫向項 FR-058.X
_TOOL_ID_TO_CODE 解耦
(D11 已拍板)"]
P0["FR-058.0
任務層敏感參數
(平台前置)"] --> P1["FR-058.1
ZAP connector"]
PX --> P1
end
subgraph B2["第二批 (CM-971〜979)"]
P2["FR-058.2
CINC Auditor connector
SSH + WinRM"] --> P3["FR-058.3
GCB
(引擎 + Windows Demo)"]
end
subgraph B3["第三批 (CM-980〜983)"]
P4["FR-058.4
Nmap
SSH 型 (D9 改案)"]
end
B1 -->|"批次結束測一次"| B2
B2 -->|"併第二批一起測"| B3
classDef pre fill:#F6EBD5,stroke:#9C6B12,color:#14201F;
classDef chain fill:#E2F0F1,stroke:#0E7C86,color:#14201F;
classDef indep fill:#E4F1EA,stroke:#2E7D5B,color:#14201F;
classDef cross fill:#E1ECF7,stroke:#2B6CB0,color:#14201F;
class P0 pre;
class P1,P2,P3 chain;
class P4 indep;
class PX cross;
| 子需求 | 大致工作內容 | 依賴 | 批次 / 節奏 | 涉及 repo |
|---|---|---|---|---|
| FR-058.0 任務層敏感參數 |
BE:param_schema 支援 secret: true、存 tool_params 前加密、派工組裝時解密。FE:欄位渲染與執行紀錄剝除。依 D1 決定是否含稽核 log。 |
— | 第一批 Session 1 | BE FE |
| FR-058.1 ZAP connector |
SQL seed(API 型態 + config/param schema + setup_guide);agent zap.py(依 scan_mode 分支、雙格式取報告、cancel/timeout);對齊 zaproxy 0.6.0 與 2.17.0 行為變更。 |
FR-058.0 | 第一批 Session 2;僅登入後掃描需等 .0 | BE agent |
| FR-058.2 InSpec / CINC connector |
SQL seed;agent inspec.py(SSH + WinRM 雙 transport、--reporter json 統一解析);agent image 加入 CINC Auditor。吸收並結案 Notion CM-953。 |
— | 第二批 | BE agent |
| FR-058.3 GCB/Windows 組態檢測端到端驗證 |
SQL seed;引擎直接複用 .2 的雙 transport connector(Windows 走 WinRM、Linux 走 SSH,不另立引擎);跑通 Windows 一條最小 Demo profile 的完整鏈路(WinRM 連線 → 認證 → profile 執行 → 結果解析 → 證據上傳);content 外部載入接口(不硬編進 connector 或 agent image)。驗收=鏈路可運作,不是涵蓋規則數。content 產製與後台更新機制依 D7 不納入本案。 | FR-058.2 | 第二批 .2 完成後 | BE agent |
| FR-058.4 Nmap |
平台調整:start_execution 放行零憑證工具(依 D9,平台能力保留);SQL seed(SSH 型 + requires_credentials=TRUE——D9 改案,原定 CLI + 零憑證);agent nmap.py(SSH 登入執行主機後 -oX + XML 解析,對照 openscap.py)。 |
— | 第三批 併第二批一起測 | BE agent |
橫向項 FR-058.X_TOOL_ID_TO_CODE 解耦 |
派工 payload 夾帶 tool code(欄位名 detection_tool_code),agent factory 改依 code 取 connector,移除硬編 id 映射(D11 已拍板)。 |
— | 第一批 與 ZAP 同批,不另開 session | BE agent |
已於 2026-07-30 被下方分批出貨計畫取代
以下這段是「三條線並行」時期的排程建議。決策者改採分批出貨、ZAP 優先上線後,並行前提已不成立,此段不再是執行依據,保留以還原推導軌跡。取代它的安排見 §10.2。
若人力允許並行,FR-058.0 與 FR-058.2 同時起跑是最有效率的開局:前者解鎖 ZAP,後者解鎖 GCB,兩條依賴鏈同時前進。橫向項(_TOOL_ID_TO_CODE 解耦)建議排在第一個新 connector 落地之前做掉——等四個 connector 都寫完再回頭改,要重測的範圍會擴大四倍。
改成三個批次序列推進,每批結束才做一次端到端測試。優先序本身(ZAP → InSpec → GCB → Nmap)沒有變,變的是「分批出貨而非三線並行」。
| 批次 | 內容 | 卡號範圍 | 端到端測試 |
|---|---|---|---|
| 第一批 ZAP 上線 |
ID 解耦(FR-058.X)+任務層敏感參數(FR-058.0)+ZAP(FR-058.1) | CM-958〜970 |
批次結束測一次 |
| 第二批 | InSpec / CINC(FR-058.2)→ GCB(FR-058.3) | CM-971〜979 |
批次結束測一次 |
| 第三批 | Nmap(FR-058.4) | CM-980〜983 |
併第二批一起測 |
原本把橫向項排在最前面的理由是「三條線同時開工會搶同一個 factory 檔」。改成序列執行後這個理由已不成立,但結論不變,換成另一組更硬的理由:
id=5、agent 端硬編再加一行 5: "zap"——正好踩中 D11 要解的那個「三環境 seed id 必須完全一致,錯一位就靜默跑錯工具」的坑。task_executor / factory),同一批做完一起測,省一輪。技術上 ZAP 的被動與主動掃描不需要登入帳密,只有「登入後掃描」才需要 .0,理論上可以先出不含登入的 ZAP。仍決定留在第一批:
.0 只有 4 個子任務,不值得為省它而多測一輪。| Session | 負責 | 卡號 |
|---|---|---|
| Session 1 平台側(BE + FE) |
T-X.1 派工 payload 加欄位 + .0 全部四個子任務 |
CM-959、CM-962〜965 |
| Session 2 agent 側 |
T-X.2 factory 改寫 + ZAP 四個子任務 | CM-960、CM-967〜970 |
兩個 session 可大幅重疊:Session 2 採「有 code 用 code、無 code 退回 id」的 fallback 寫法,不被 Session 1 擋住;只有最後的 T-1.4(登入後掃描)真正需要等 Session 1 的解密接線(CM-963)。欄位名稱固定為 detection_tool_code(§詳細設計已定),兩邊照此不需即時協調。
真正耗時的不是跑測試本身,而是重建 agent image、部署三台、等 300 秒心跳。因此測試動作依成本分兩類:
| 時機 | 做什麼 |
|---|---|
| 做中持續做 便宜,不要省 |
import/語法檢查;對假 XML/JSON fixture 跑解析邏輯的單元測試;SQL 套 DEV 後 SELECT 確認 seed 進去。 |
| 留到批次結束才做 貴,不要重複 |
BE 重啟;agent 重建部署;真實掃描端到端;多環境套 migration。 |
每個子任務做完不要各自跑一次端到端
一輪至少 10 分鐘起跳(重建 image → 部署三台 → 等心跳),那正是分批出貨要避免的重複成本。端到端只在批次結束做一次;第三批併第二批一起測,全案實際只測兩輪。
母案:CM-957
| 子需求 | 母卡 | 子任務 |
|---|---|---|
| FR-058.X ID 解耦 | CM-958 | CM-959 / CM-960 |
| FR-058.0 任務層敏感參數 | CM-961 | CM-962〜965 |
| FR-058.1 ZAP | CM-966 | CM-967〜970 |
| FR-058.2 InSpec / CINC | CM-971 | CM-972〜975 |
| FR-058.3 GCB | CM-976 | CM-977〜979 |
| FR-058.4 Nmap | CM-980 | CM-981〜983 |
共 27 張卡,Case No 連號無斷號。另有 CM-953(異質 OS 檢測方案)已由 FR-058.2 吸收並結案。實作期間另補開 CM-984、CM-986〜CM-988、CM-992 等卡——見 §11。
以下四項不在原規劃的 27 張卡內,是實作與實測過程中暴露出來的平台缺口。共同特徵是:都由某個具體工具觸發,但都不是那個工具的特例——因此一律做成宣告驅動或平台級機制,而非在程式碼裡寫工具分支。
credential_group_schema)觸發者:CINC Auditor 是平台第一個「一個工具帶兩組互斥憑證」的工具——SSH 憑證用於 Linux 目標、WinRM 憑證用於 Windows 目標,使用者通常只填打算掃的那一組。GCB 會是第二個,所以做成平台能力。
| # | 關掉的缺口 | 症狀 | 解法 |
|---|---|---|---|
| ① | 測試連線不知道該用哪組憑證 | BE 下發的 probe 參數只有 {"host": ...}、不含 transport;agent 的 _resolve_transport() 未指定時預設 SSH。只填了 WinRM 憑證的使用者按「測試連線」,收到「本任務的連線方式為 SSH(Linux 目標),但未填寫 SSH 稽核帳號」——訊息本身很清楚,但它回答的是使用者沒問的問題 |
新增 credential_group_schema 宣告欄位,FE 在測試連線對話框讓使用者明確選一組 |
| ② | 設定頁 11 個欄位平鋪一列 | 看起來全部必填,使用者看不出「這幾個一組、那幾個一組,只填一組就夠」 | config_field_schema 逐欄加 group + hint,FE 依宣告分區渲染(本輪 UI 再改為左右並排獨立面板,Dialog 依組數動態加寬) |
兩個關鍵設計決定(後續要改的人先讀這段)
① 不做自動偵測,由使用者明確選。「看使用者填了哪組就猜」在兩組都填齊時無從分辨——而那正是真實情境(客戶同時有 Linux 與 Windows 目標)。猜錯產生的恰恰是本項要消滅的誤導訊息。多問一題的代價遠低於答錯一題。未選定時測試連線鈕保持 disabled,不落回預設值;宣告了組別卻沒選 / 選了不存在的 key 一律明確回失敗,不預設挑第一組,也不記 last_test_result(沒真的測過)。
② FE 只送組別 key,probe 參數由 BE 依自己的 DB 宣告組出。request body 只收 credential_group,FE 不得帶 probe 參數的 key 或 value——probe 參數會一路流到 agent connector 決定程式路徑(其他工具則用來組檔案路徑),讓客戶端自由命名參數等於開一個注入面。FE 側也不硬寫任何協定字面值(transport / SSH / WinRM),全部由後端宣告驅動。
零影響保證:NULL / 缺席=該工具無互斥組別,測試連線與設定頁行為與加這欄之前完全相同。實查確認目前只有 inspec 一列有宣告,其餘工具全為 NULL。agent 側零 code 異動——_resolve_transport() 本來就吃 probe params,缺的只是 BE 沒傳。
Schema 形狀:{probe_param_key, prompt_label, groups:[{key, label, description, probe_param_value}]}。為什麼不塞進 config_field_schema:那是一個 JSONB 陣列,加頂層 metadata 會把形狀從陣列變成物件,所有讀取端都要跟著改。Notion CM-986。
Bug · 實測已發生,非理論風險
兩端對「沒送的 secret 欄位」理解不一致:FE buildPayload() 只送有填值的 secret(配合 UI 對使用者的承諾「留空=沿用原值」),BE update_tenant_config() 卻是整包 encrypt 覆蓋——沒送的 key 就消失了。
單一 secret 欄位的工具碰不到(只有一個 key,送不送都正確)。CINC 是第一個有三個 secret 欄位的工具(SSH 私鑰 / SSH 密碼 / WinRM 密碼),問題才被採出來——只填 SSH 私鑰存檔後,WinRM 密碼確實遺失、需重新填寫。
危險之處在於它不會報錯——存檔看起來成功,直到下一次用到那組憑證才發現它不見了。修法:改 merge 語意(解密既有 secret dict → 新送來的 key 逐個覆蓋 → 整包重新 encrypt 回寫)。刻意不支援「清空單一 secret」——merge 語意下「把某個 secret 改成空」與「不動它」在 payload 上完全同形,要支援得引進額外的明確清空標記,而該需求實際不存在;整組清空走既有 reset 路徑,不受本改動影響。Notion CM-987。
觸發者:CINC 對 Windows 的完整 baseline 掃描動輒十分鐘起跳(實測 894 項檢查),發現參數填錯時沒有任何止損手段。
新增 endpoint POST /detection-tools/jobs/<job_uid>/cancel,復用 CM-931 已建立的取消鏈路(不另寫一套)。FE 在任務執行抽屜內,每筆 running 狀態紀錄旁加「取消執行」按鈕(confirm → 成功 toast → 刷新,loading 防連點)。
設計要點 · 成功才標 cancelled,agent 離線就回 409 不標記
這是刻意的——若「標完就算取消」,agent 連不上時會出現「畫面顯示已取消、實際上那個掃描還在目標主機上跑」的假取消。既有機制的行為是直推 agent 真中斷,本項沿用該語意。新 error code DETECTION_TOOLS_409005(DETECTION_TOOL_NO_RUNNING_EXECUTION)。
順手補齊:FE 從 CM-931 時代就漏了 DETECTION_TOOLS_409002〜409004 三條 i18n——取消失敗時畫面顯示不出原因(使用者只看到錯誤碼)。一併補上。Notion CM-988。
requires_credentials / requires_target_host 宣告欄位兩個宣告欄位,共同目的是把「這個工具需要什麼」從程式碼的硬判改成 DB 的宣告。
| 欄位 | 解什麼 | 預設值與理由 |
|---|---|---|
requires_credentials |
D9 的零憑證放行——讓 start_execution() 知道「這個工具本來就不需要租戶層憑證」是合法狀態,不是設定遺漏。 |
預設 TRUE(往嚴的方向失敗):新工具忘了宣告時行為等同現況,不會意外被放行。目前所有工具皆為 TRUE——第一個真正的零憑證使用者尚未出現(原定的 Nmap 在 D9 改案後改走 SSH 型)。 |
requires_target_host |
解除 FE 原本硬判 connection_type === 'SSH' 的耦合。測試連線需要問主機的真正原因是「租戶層憑證不含 host」(FR-057 D1 定的),與走哪種協定無關——用協定當代理判準遲早會錯。 |
預設 FALSE(同樣往嚴的方向):忘了宣告不會拿一個使用者答不出來的問題擋在測試連線前面。 |
Notion CM-981。這兩欄與 §11.1 的 credential_group_schema 是同一種手法——平台對具體工具維持零知識,靠工具自己的宣告行事,這正是 FR-056 tool-agnostic 精神的延伸。
| 工具 | code | 連線型態 | 驗收狀態 |
|---|---|---|---|
| ZAP | zap | API(客戶自備 daemon) | 被動 ✓/主動 ✓/登入後對 SPA ✗(會打掛共用 ZAP,見 D12 / §4.2;後續案 CM-984) |
| CINC Auditor | inspec | SSH(Linux)+ WinRM(Windows) | ✓ 雙 transport 實跑通過151 為 pass 131 / fail 60、160 為 pass 300 / fail 472 |
| Nmap | nmap | SSH(登入客戶的執行主機再掃目標) | ✓ 實跑通過HTML 證據可產出 |
| GCB | gcb | 同 CINC(複用引擎,D7) | ✓ 鏈路實跑通過但 Demo profile 僅 2 項、僅供鏈路驗證,不足以對客戶展示(CM-992) |
上版前必須處理 · 非 DEV 環境的 agent 版本落差
POC / STG 上這四個工具已顯示 available,但兩地的 agent 停在舊版、沒有對應的 connector——使用者若在那邊設定並派工,會拿到跑不動的結果。目前無人使用(租戶設定 0 筆、派工 0 筆),但上版時必須一併處理。
這一條同時是本案的一則教訓:seed(讓工具在 UI 上出現)與 connector(讓它真的跑得動)是兩件事,分屬雲端與 agent 兩個部署單位。migration 同步到三環境時若沒有同步 agent,就會製造出「看得到、按得下、跑不動」的狀態。