FR-058 第三批(Nmap)實作 session 派工交接(2026-07-30)

項目 內容
緣由 FR-058 分批出貨。第一批(ID 解耦 + 任務層敏感參數 + ZAP,CM-958〜970)實作已完成、agent 已 bump 0.2.13。本文是第三批(Nmap)實作 session 的派工單
Branch BE:feature/FR-058(不切 branch);evidence-agent:當下 branch(第一批 HEAD 8185893 bump 0.2.13)
本棒角色 實作者。 完成 FR-058.4(Nmap),依 T-4.1 → T-4.2 → T-4.3 順序做,每個子任務完成即 commit + 回寫 Notion 卡
與第二批的關係 完全獨立、可並行。 第二批(InSpec/CINC → GCB,CM-971〜979)與本批無任何依賴:不碰 inspec.py、不碰第二批的 seed 檔,各自新增檔案不衝突。但 evidence-agent 有三處共用檔會撞(factory __init__.py / Dockerfile / 版號),處理方式見 §7
⚠️ 改案(2026-07-30,本文已全份更新) D9 改案:Nmap 由 connection_type='CLI'(agent 本機執行)改為 'SSH'——nmap 由客戶安裝在自己指定的主機上,agent 透過 SSH 登入該主機執行,與 OpenSCAP 完全同構。觸發點是「客戶自備」在容器化部署下無法成立(agent 在容器內看不到主機的 nmap),查證 NPSL v0.95 §3 後確認不能 bundle 且同條款末段的自留出口讓 SSH 型成立。連帶:Nmap 需要 SSH 憑證、T-4.1 零憑證平台能力保留但 Nmap 不再是其使用者、connection_type='CLI' 至今仍無實際使用者。完整推導見 design.md §2 D9,本文摘要見 §2.1〜§2.3
接手前必讀 本文全份 + design.md §2(D9 含 2026-07-30 改案段,務必讀原文)+ §4.5(Nmap 詳細設計,已改為 SSH 型)+ §5.7(子任務表)+ §3.2 元件盤點的 connection_type / start_execution() 兩列
上游文件 docs/features/FR-058-2607-detection-tools-expansion/handoff/2026-07-30-fr058-planning-complete-handoff.md(全案大圖與需求演變;本文已摘實作所需部分,卡住時才回頭讀)

§0 讀序

  1. 本文 §1(範圍)→ §2(Nmap 的特殊性,本批最重要一節) → §3(第一批帶來的新事實)
  2. design.md §4.5(FR-058.4 Nmap 詳細設計)全讀 + §5.7(T-4.1〜T-4.3 子任務表與驗收)
  3. design.md §2 的 D9 定案原文,含 2026-07-30 改案段(本文 §2.1〜§2.3 是摘要,原文含 NPSL 條款原文、改案的完整推導與三個被排除方案的理由)
  4. design.md §3.2 元件盤點表的兩列:config.detection_tools(「加是否需要憑證宣告欄位」)與 start_execution() 憑證檢查(「放行零憑證工具」)——這兩列是 T-4.1 的範圍界線
  5. §4 的範本檔案——動手寫每個子任務前先開對應範本照形狀寫

§1 任務範圍與 Notion 卡

1.1 範圍

第三批 = FR-058.4(Nmap),共 3 個子任務,序列執行(T-4.2 依賴 T-4.1 的欄位、T-4.3 依賴 T-4.2 的 seed)。

# 子任務 Repo 一句話
T-4.1 detection_tools 加「是否需要憑證」宣告欄位(schema 異動 + ORM model + migration);start_execution() 依該欄位放行零憑證工具(D9);三環境套用 BE 本批唯一動到平台本體的一棒,迴歸風險最高。改案後 Nmap 已不是此能力的使用者,但這一棒照做——它是平台級能力(見 §2.2)
T-4.2 seed Nmap(connection_type='SSH'requires_credentials=TRUE)+ config_field_schema(SSH 憑證,對照 OpenSCAP)+ param_schema(掃描目標 / 掃描類型 / 埠範圍)+ setup_guide客戶在自己指定的主機安裝 nmap、NPSL 授權說明、SSH 前提、主動掃描授權警語);三環境套用 BE migration
T-4.3 nmap.pySSH 登入執行主機nmap -oX → XML 解析 summary + 報告上傳 + probe()(SSH 連線 / 認證 / 該主機 nmap 可執行 / 權限四段分類)+ cancel_event / timeout;不 bundle 進 image;factory 依 code 註冊 evidence-agent connector(主要對照 openscap.py

驗收(design.md §5.7 原文)

  • T-4.1:零憑證工具可正常開始執行,不再誤回 DETECTION_TOOL_CONFIG_NOT_FOUND需憑證的既有工具檢查行為不變(← 迴歸紅線,見 §2.2)
  • T-4.2:設定頁出現 Nmap(available) 並可設定 SSH 憑證;任務可選 Nmap 並填參數發佈
  • T-4.3:報告進證據池、summary 有開放埠數;執行主機未安裝 nmap 時 probe() 回明確訊息(且可與「連不上」「認證失敗」區分) (其中「手塞派工掃真實網段」屬端到端驗收,依 §5 測試紀律延後到全案最後

1.2 Notion 卡對照(完成後逐卡回寫,母案 CM-957:https://app.notion.com/p/3ad346da4cd081859d2ed8b21ca2697c

對應 Notion URL
CM-980(FR-058.4 母卡) Nmap https://app.notion.com/p/3ad346da4cd081159daae65d4b08c80d
CM-981 T-4.1 從母卡 CM-980 子卡清單進入
CM-982 T-4.2 同上
CM-983 T-4.3 同上

§2 Nmap 的特殊性(本批核心,動手前先讀透)

⚠️ 2026-07-30 D9 改案(本節已依此改寫):Nmap 由原定的 connection_type='CLI'(agent 本機執行)改為 connection_type='SSH'——nmap 由客戶安裝在自己指定的主機上,agent 透過 SSH 登入該主機執行,與 OpenSCAP 完全同構。改案的完整推導、NPSL 條款原文與三個被排除方案見 design.md §2 的 D9(本節 §2.1〜§2.3 是摘要,動手前務必讀 D9 原文)。

Nmap 在四個工具裡「技術上最單純」(呼叫 CLI + XML 解析)。改案前它是「唯一一個逼平台改自己的工具」(零憑證),改案後它變成 OpenSCAP 的同構複製,實質工作量從「改平台」轉為「照抄 SSH 型範本」。本節四點動手前逐點讀完。

2.1 走 SSH 型、與 OpenSCAP 同構(CLI 型態至今仍無使用者)

detection_tools.connection_type 在 FR-056 就定義了三種型態(API / CLI / SSH),至今只有 API(OpenVAS、ZAP)與 SSH(OpenSCAP)被真正用過。原本 Nmap 要當 CLI 的首個實際使用者,改案後這件事沒有發生——CLI 這一格從 FR-056 定義出來到現在仍然沒有任何工具走過,本批不會改變這個事實。

這對本批的意義是好消息:走 SSH 就是走已被 OpenSCAP 實戰驗證過的路徑,BE 派工鏈、設定頁憑證渲染、測試連線端點、agent 的 SSH 連線處理全部有現成可照抄的形狀,原本「未經驗證路徑可能藏著缺口」的風險不復存在

因此實作方向是「照抄 OpenSCAP」而不是「開新路」

對照點 照誰寫
detection_tools seed(connection_type / config_field_schema / requires_credentials OpenSCAP 那一列 + scripts/sql/2026-07-28-fr057-1-openscap-seed.sql
設定頁 SSH 憑證欄位(host / port / 帳號 / 密碼或私鑰) OpenSCAP 的 config_field_schema不需要新欄位型態
agent connector 的 SSH 連線 / 私鑰暫存 / 多目標逐台 / host_failures openscap.py(見 §4.3)
測試連線(probe())的分段錯誤分類 openscap.py 的 probe,再加一段「該主機有沒有 nmap」(§2.3)

唯一要留意的新事實:Nmap 現在需要 SSH 憑證requires_credentials=TRUE),設定頁必須先設定憑證才能執行——與 OpenSCAP 行為一致,不再是「無需填憑證」。

發現與 OpenSCAP 形狀不一致而需要偏離時的處置先停下回報再決定——這屬 D1–D12 沒涵蓋的新決策,依 §6 規範不自行開新方向。

2.2 D9 零憑證放行:本批唯一動到平台本體的部分(迴歸風險最高)

⚠️ 改案後的定位(先讀這段再往下):Nmap 改為 SSH 型後需要 SSH 憑證它自己不再是零憑證工具,也就不會被 start_execution() 的憑證檢查誤擋。
但 T-4.1 這一棒照做、範圍不變——「是否需要憑證」宣告欄位 + 放行邏輯是平台級能力(design.md §1.2 已把它列為平台結構性補強),日後真正零憑證的工具會用到,只是第一個使用者不再是 Nmap
對本批的具體差異只有兩點:① T-4.2 seed Nmap 時 requires_credentials 要填 TRUE(不是 FALSE);② T-4.1 的驗收要另找方式驗(見本節末的「改案後怎麼驗 T-4.1」)。下面的現況分析與紅線全部仍然適用

現況(已查證,app/detection_tools/service/detection_orchestration_service.py:105-110

creds = self._resolve_credentials(
    binding.tenant_config_id, tenant_id, binding.detection_tool_id
)
if not creds:
    # 空憑證派下去 agent 必炸(缺 host/帳密),且錯在雲端卻只能從 agent log 看到
    # → 提前擋下並講清楚要去設定工具憑證。
    raise BadRequestError(DetectionToolsErrorCode.DETECTION_TOOL_CONFIG_NOT_FOUND)

改案前的問題敘述:Nmap 不需要任何帳號密碼,走到這裡必被誤擋。改案後 Nmap 有 SSH 憑證,不會走到這個死路——但下面的平台能力仍要補上,理由見本節開頭的框。)

D9 定案做法detection_tools 增加「是否需要憑證」的宣告欄位,讓平台知道零憑證是一種合法狀態start_execution() 依該宣告放行。

D9 明確排除的做法:為 Nmap 建立一筆空的租戶設定當佔位——不要這樣做。理由:會留下一筆語意不明的資料,日後看到的人無法判斷那是刻意還是遺漏。

這一棒的風險與紅線

  1. 既有四個工具都需要憑證,不可因這次改動被放行。新欄位的預設值必須讓既有列維持「需要憑證」語意(NOT NULL DEFAULT TRUE 之類),migration 不可讓既有四列變成零憑證。驗收的第二句「需憑證的既有工具檢查行為不變」就是在講這件事。
  2. 改動範圍是三處,缺一不可
    • DB schema:config.detection_tools 加欄位(migration,三環境都套)
    • ORM model:infra/detection_tools/model/detection_tool.py(32 行,加 Mapped[bool] 欄位 + comment=;本專案 ORM comment= 是 DB COMMENT 的單一來源)
    • domain entity / mapper:domain/detection_tools/entity/detection_tool_entity.py + infra/detection_tools/mapper/detection_tool_mapper.py(欄位要能傳到 service 層才判斷得了)
    • service 邏輯:start_execution()if not creds 改為「若該工具宣告需要憑證才擋」
  3. 判斷資料要從哪裡拿start_execution() 目前手上只有 binding.detection_tool_id,需要取到該工具的宣告欄位。依 DDD 層級規範,走 detection tool 的 domain service 取,不要在 app service 直接 import ORM model 查(既有 self._jedt_domain / self._exec_domain 的注入形狀可照抄;缺 domain service 就在 __init__ 加參數並到 di_containers/ 對應 container wiring)。
  4. 欄位命名建議requires_credentials(BOOLEAN NOT NULL DEFAULT TRUE)——語意正向、預設安全(新工具沒宣告就是需要憑證,往嚴的方向失敗)。若採別的命名,理由寫進 migration 檔頭註解。
  5. FE 影響要一併確認:設定頁對 requires_credentials=false 的工具應該不顯示憑證表單 / 不要求「先設定才能執行」。若 FE 需要這個欄位,API response 要一併帶出detection_tools 列表端點的 schema)——這點在 T-4.1 就要決定,不要留到 T-4.2 才發現前端拿不到。
    改案後:Nmap 是 requires_credentials=true,走的是既有的「顯示憑證表單」路徑,與 OpenSCAP 一致,因此本批不會實地踩到 false 分支的 FE 行為。仍建議在 T-4.1 把 false 分支的 FE 行為想清楚並寫進 commit message,避免日後第一個真正零憑證的工具進來時才發現前端沒處理。)

改案後怎麼驗 T-4.1(原驗收假設「用 Nmap 驗零憑證放行」已不成立):

  • 放行邏輯本身:不必為了驗證而 seed 一個假工具。可用既有工具暫時把 requires_credentials 設 false 跑一次(DEV 上驗完改回),或在 BE 加一個針對 start_execution() 憑證分支的單元測試(mock 工具宣告的兩種值各跑一次)——後者較乾淨且可留下迴歸保護,優先選它
  • 迴歸紅線不變且更重要:既有四個工具加上改案後的 Nmap 在憑證為空時都仍要被擋。Nmap 現在也在這個名單裡。

2.3 Nmap 不 bundle 進 agent image(NPSL 授權)→ 這正是改走 SSH 型的原因

Nmap 採 NPSL(Nmap Public Source License,基於 GPLv2 但加了額外限制),不可打包進我們發布的 agent image

法律依據(2026-07-30 查證,比原記載的「NPSL 授權」四個字強得多)——NPSL v0.95 §3(出處 https://svn.nmap.org/nmap/LICENSE)列舉的「衍生作品」定義明文包含這一條:

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 輸出」,正中此條。因此「只用子行程呼叫、不連結函式庫」這個 GPL 慣用的免責理由在 NPSL 底下不成立——該條款是特意加來堵掉這個論點的。GPLv2 的 mere aggregation 條款也救不了(它只適用於「not based on the Program」的作品,而 §3 已把我們定義為 based on)。官方 Legal Notices(https://nmap.org/book/man-legal.html)另明說免費授權「doesn't allow Nmap to be used and redistributed within commercial software or hardware products」(明列 appliances / virtual machines / traditional applications),並為此販售 Nmap OEM Edition(https://nmap.org/oem/)。

但 §3 最後一段留了出口:軟體若執行的是「使用者早已安裝在自己系統上的 nmap」,授權方不主張控制(Licensor does not purport to control through this license any software which does not require the rights granted herein)。SSH 型正好落在這個描述裡——nmap 是客戶裝在自己主機上的,我們只是遠端下指令。

原 CLI 型為何撐不住:實作前發現「客戶自備」在容器化部署下無法成立——agent 跑在 Docker container 內,看不到主機上安裝的 nmap。原設計隱含假設 nmap 在 agent 可執行範圍內,但「不 bundle」又「不能裝進容器」互相矛盾。被評估並排除的三個方案(掛載主機 binary 進容器 / 客戶自建一層 image / 購買 Nmap OEM 授權)各自的理由見 design.md §2 D9 的改案段——若你在實作中想走其中任何一條,先讀那段再停下回報

對實作的四個具體要求

  1. Dockerfile 不加 nmap。(對照組:第二批要在 Dockerfile 加 CINC Auditor,本批不動 Dockerfile 的套件安裝——若你發現自己在改 Dockerfile 裝 nmap,停下來重讀這段。也不可用 volume 掛載主機 binary 的方式繞過,那是 D9 明確排除的方案之一。)
  2. probe() 必須能在「執行主機」未安裝 nmap 時回明確訊息,且要與「SSH 連不上」「認證失敗」明確區分(四段式分類,見 §4.3)。這是驗收條件之一,不是選配。遠端 command -v nmap / nmap --version 失敗時的訊息要讓管理員一看就知道「要去那台執行主機裝 nmap」,而不是丟一個 exit code 127 的原始輸出。
  3. setup_guide 要寫清楚「裝在哪裡、怎麼裝」:在客戶自己指定的執行主機上安裝(各主流發行版的安裝指令 + 驗證指令),無任何 docker 操作;SSH 連線與帳號權限前提(部分掃描類型需要提權);並註明「本平台不隨附 nmap,需由貴公司在指定主機自行安裝」與 NPSL 授權說明。
  4. SSH 憑證屬租戶層設定tenant_detection_tool_configs,走既有 Fernet 鏈,對照 OpenSCAP 的 config_field_schema),不放任務參數、不涉及 FR-058.0 的任務層敏感參數機制。

2.4 主動掃描性質:setup_guide 需比照 ZAP 加授權警語

埠掃描會對目標送出真實探測流量——不是被動觀察,而是主動連線嘗試。在客戶網路環境中,這可能觸發 IDS/IPS 告警、被誤判為攻擊行為,或違反目標方的使用條款。

要求setup_guide 比照 ZAP 的寫法加上授權警語。ZAP seed 的原句可直接照抄改寫:

⚠️ 主動掃描會對目標網站送出真實的攻擊測試流量。 請務必確認已取得目標方授權,並優先在測試環境執行。

Nmap 版的措辭要對上埠掃描的實際行為(送出探測封包、可能觸發資安設備告警),並提醒「掃描網段前確認該網段屬於受稽核範圍且已獲授權」。

param_schema 的掃描類型欄位若含侵入性較高的選項(例如版本偵測 -sV、OS 偵測 -O、腳本掃描 -sC),預設值要選保守的那一個——這條沿用 D2 的判斷原則:預設值選錯的代價不對等,預設保守最多是掃得淺,預設侵入則可能在使用者未察覺下對正式環境送出大量探測流量。


§3 第一批帶來的新事實(第三批必須遵守 / 直接受益)

3.1 factory 已改依 code 取 connector(agent commit f17a311,D11)

  • get_connector() 現在優先吃 detection_tool_codedetection_tool_id 降為 fallback(走 fallback 記 warning log)。
  • 新 connector 的註冊方式:在 core/task_executor_connectors/__init__.py 加一個 _build_nmap() builder(import 留在 builder 內部,維持 lazy import 慣例——某支 connector 的第三方相依缺失時只有該工具不可用,不會讓整個 factory import 失敗),並在 _CONNECTOR_BUILDERS dict 加 "nmap": _build_nmap(實際 code 以 T-4.2 seed 的 detection_tools.code 為準)。
  • 絕不把新工具加進 _LEGACY_TOOL_ID_TO_CODE——檔頭註解已明訂該表只涵蓋 FR-056/057 已出貨四筆({1: openvas, 2: nessus, 3: sonarqube, 4: openscap}),新工具加入等於把剛移除的脆弱性種回去。ZAP 已照此模式(參考 _build_zap)。
  • factory 迴歸測試在 test/test_connector_factory.py,新 code 註冊後補對應 case。

3.2 BE 派工 payload 已夾帶 detection_tool_code(BE commit 5151fb2c + 082a02d8,T-X.1 已上線)

_collect_pending_tasks() 已加欄位;082a02d8 補齊了心跳以外的第二條派工路徑(測試連線 probe payload)。第三批不需要動 BE 派工鏈的 code 夾帶部分——seed 進 detection_tools 後派工自然帶 code,agent 端照 §3.1 註冊即可接上。

(注意:T-4.1 仍要動 start_execution(),但那是憑證檢查的分支,與 code 夾帶無關。)

3.3 任務層敏感參數平台能力已就緒(同 commit 5151fb2c,T-0.1〜0.3)

param_schema 欄位標 secret: true 即自動走 Fernet 加密落庫 / 派工解密下發 / FE 與稽核 log 剝除(實作在 common/util/detection_secret_params.py)。

Nmap 本批預設用不到(埠掃描不需要憑證,這正是本批要解的問題)。但若日後 Nmap 要做認證掃描(例如需要目標主機帳密的服務探測),直接在 param_schema 標 secret: true 即可,不需自建機制——這條在 design.md §1.2 已被列為此能力的預期受益者之一。

3.4 ⚠️ FE 必填驗證的已知邊界(設計 param_schema 時要避開)

第一批已在 FE 補上任務參數的必填驗證(FE commit 82208529,helper ~/Projects/Billows/Audit-Manager/compliance-manager-fe/src/utils/detectionFieldValidation.js):任務存檔時會擋 condition-aware 的必填欄位(condition 不成立的欄位不渲染也不驗證)。

已知邊界(helper 檔頭註解已載明):空值判斷刻意沿用 falsy(!value),與 DetectionConfigField.showError 同源。當時的前提是「有 required 的欄位型態不含 number / boolean」,所以 falsy 判斷不會誤殺 0false

對 Nmap 的直接影響:若 Nmap 的 param_schema 出現 type: "number"type: "boolean"required: true 的欄位(很可能——例如埠號範圍起點、逾時秒數、某個布林開關),使用者填 0false會被誤判為缺漏而擋下存檔

處置(依序)

  1. 優先避開:把這類欄位設計成 required: false + 給 default(timeout 類本來就該這樣,ZAP 的 timeout_sec 即是 required:false + default:3600),或改用 text / select 型態(埠範圍本來就常寫成 "1-1024" 這種字串)。
  2. 真的必須是必填 number / boolean停下回報,說明需要一併修 helper(把空值判斷改成明確的 value === undefined || value === null || value === ''),不要自己在 seed 裡繞過或默默改 FE。

3.5 D4 教訓通則化:報告取得方式必須先確認拿得到「內容本身」

ZAP 原定用 reports.generate 產 PDF,實作時發現該 API 只把檔案寫進 ZAP daemon 主機的磁碟、回傳路徑字串,官方沒有回傳檔案內容的端點(zaproxy issue #7821)——遠端部署模型下 agent 拿不到那個路徑的檔案,被迫改案(改 core.htmlreport 取 bytes)。

通則:任何「工具端產檔」的設計,動手前都必須先驗證取得管道回的是內容本體而不是「對方主機上的路徑」。

Nmap 原則上沒有此問題,但改案為 SSH 型後這條要多留意一層——nmap -oX遠端執行主機上跑,輸出必須真的傳回 agent。最直接的做法是 -oX - 讓 XML 走 stdout,SSH 直接收到 bytes(省掉遠端暫存檔的清理問題);若改用遠端暫存檔,就必須自己把檔案抓回來並清掉遠端殘檔。寫 run() 前確認一次:確定拿到的是 XML bytes 而不是「遠端主機上的路徑字串」、確定遠端沒留下暫存檔。openscap.py 已經解過同一題,照它的形狀。

3.6 agent 版號

第一批已出到 0.2.13(DEV 已部署;HEAD 8185893,含 ZAP 主動掃描修正)。第三批出貨版 bump 0.2.14——但兩批可能同時進行,若第二批先出 0.2.14 就順延到 0.2.15。commit 前先 git log --oneline -5 + 看 pyproject.tomlversion 確認目前版號,不要拿本文的數字當現況。

bump pattern 照 evidence-agent commit c775fc8——四處版號同步pyproject.toml / config/config.pyAGENT_VERSION 預設值)/ deploy/docker-compose.ymlAGENT_IMAGE + AGENT_VERSION 兩處)/ rebuild.sh(註解範例)。依 §5 測試紀律,開發期間不重建 image、不部署,bump commit 做完留著等全案端到端才出貨。

3.7 第一批可照抄的實作水準基準

  • seed SQLscripts/sql/2026-07-30-fr058-1-zap-seed.sql(見 §4.1)——檔頭把決策背景寫成註解、ON CONFLICT (code) DO NOTHING、用 nextval 自然拿 id 且註明「code 派工後 id 不影響正確性」、schema_migrations 收尾。
  • mock 單元測試test/test_zap_connector.py(485 行,47 項全 mock + 九輪突變測試驗斷言真的會咬)——Nmap connector 測試照此水準寫 test/test_nmap_connector.py。Nmap 的 XML 解析特別適合這種寫法:準備幾份假 XML fixture(正常多埠 / 全關閉 / 主機不可達 / 格式殘缺),對解析邏輯跑純 mock 測試,完全不需要真的掃任何東西。

§4 實作範本座標(照形狀寫,不重新發明)

4.1 BE seed / migration 範本(T-4.1 / T-4.2)

檔案 用法
/Users/chouraymond/Projects/Billows/Audit-Manager/compliance-manager-be/scripts/sql/2026-07-30-fr058-1-zap-seed.sql SQL 寫法的最新範本——nextval 拿 id + 「code 派工後 id 不影響正確性」註解、ON CONFLICT (code) DO NOTHING、param_schema 的 condition / secret / default 寫法、setup_guideE'' 多行 markdown 寫法(含表格與程式碼區塊)、detection_tool_param_schemasis_current 寫法、schema_migrations 收尾
/Users/chouraymond/Projects/Billows/Audit-Manager/compliance-manager-be/scripts/sql/2026-07-28-fr057-1-openscap-seed.sql 內容形狀的主要對照(2026-07-30 改案後)——Nmap 與 OpenSCAP 同為 SSH 型,connection_type='SSH'config_field_schema 的 SSH 憑證欄位(host / port / 帳號 / 密碼或私鑰)直接照它的形狀寫,不要自創欄位。SQL 寫法本身仍以上面的 ZAP 檔為準(較新)
/Users/chouraymond/Projects/Billows/Audit-Manager/compliance-manager-be/scripts/sql/2026-07-30-fr058-2-fix-zap-login-required-flags.sql 既有資料補正檔的範本——T-4.1 的欄位新增若要同時回填既有四列,形狀照它(改既有列 + 檔頭寫清楚為何要補、新環境走 seed 檔不必再跑此檔)

T-4.1 的 SQL 特別注意:本批是 FR-058 唯一有 ALTER TABLE 的一棒(其餘都是 INSERT seed)。CLAUDE.md SQL 鐵則照舊(檔頭 -- Date:、每語句加日期註解、收尾 INSERT public.schema_migrationspsql --single-transaction -v ON_ERROR_STOP=1);本批只加欄位不建表,故不涉及 GRANT ... TO cm_app(既有表的授權不變)。

4.2 BE 程式碼座標(T-4.1)

檔案 用途
app/detection_tools/service/detection_orchestration_service.pystart_execution()(憑證檢查在 105-110 行附近) D9 放行邏輯的落點;同檔可看到 self._jedt_domain / self._exec_domain / self._agent_task_domain 的 DI 注入形狀
infra/detection_tools/model/detection_tool.py(32 行) ORM model 加欄位(Mapped[bool] + comment=comment= 是 DB COMMENT 單一來源,見 memory project_erd_tooling_convergence
domain/detection_tools/entity/detection_tool_entity.py + infra/detection_tools/mapper/detection_tool_mapper.py entity 欄位 + mapper 對映,缺一則 service 層拿不到值
infra/detection_tools/repository/detection_tool_repo_impl.py 若需依新欄位查詢時看這裡(預期不必改)
common/code/detection_tools_error_code.pyDETECTION_TOOL_CONFIG_NOT_FOUND = DETECTION_TOOLS_404002 放行後這個 error code 對零憑證工具不再觸發;不要刪除它(既有工具仍在用)

4.3 agent connector 範本(T-4.3)

檔案 用法
~/Projects/Billows/Audit-Manager/evidence-agent/core/task_executor_connectors/openscap.py(578 行) 主要對照範本(2026-07-30 改案後)——Nmap 與 OpenSCAP 同為 SSH 型,形狀幾乎可整套照搬:SSH 連線建立與認證、私鑰暫存與用完清除、遠端指令執行與輸出取回、多目標主機逐台執行、host_failures 逐台失敗回報、遠端子行程的取消與清理、probe() 的分段錯誤分類。改案前這裡只列為「多目標處理的參考」,現在它是第一順位
~/Projects/Billows/Audit-Manager/evidence-agent/core/task_executor_connectors/zap.py(631 行,第一批最新出爐) 次要對照(通用紀律):cancel_event / timeout 輪詢寫法、_check_cancelled() 的「停止呼叫失敗不可蓋掉取消語意」處理、_probe_failure_message() 錯誤訊息組裝、回傳值驗證(錯誤碼偽裝成正常回傳的防護)、summary 解析容錯、log 紀律(憑證絕不進 log)——這些與連線型態無關的形狀照抄
~/Projects/Billows/Audit-Manager/evidence-agent/core/task_executor_connectors/base.py(63 行) DetectionConnector ABC、ScanResult(content bytes / filename / content_type / summary)、ScanCancelledError、注入點 cancel_event / host_failures / content_mismatch_hosts——介面不動

Nmap 特有的實作要點(design.md §4.5,已依 2026-07-30 D9 改案更新):

  • run()SSH 登入客戶指定的執行主機 → 執行 nmap -oX → 取回 XML → 解析產出 summary(開放埠數、服務清單等)→ XML 或轉出的人可讀報告上傳當證據。多台執行主機時逐台一份證據,某台失敗以 host_failures 明確回報(不靜默假成功)——形狀照 openscap.py
  • probe()四段式錯誤分類,各段回各自明確的訊息——① SSH 連得上嗎(網路 / port)→ ② 認證過嗎(帳密 / 私鑰)→ ③ 該主機上有沒有 nmap 且可執行(遠端 command -v nmap + nmap --version)→ ④ 執行權限夠不夠(部分掃描類型需要提權)。第 ③ 段的訊息是驗收條件(§2.3 第 2 點)
  • cancel_event 與 timeout:取消時要中止遠端執行中的 nmap 並清理 SSH channel,raise ScanCancelledError。照 openscap.py 的遠端子行程取消形狀處理,不要留下對端的孤兒行程。(注意:這與改案前規劃的「本機 subprocess terminate」不同,也與 ZAP 的 HTTP 輪詢不同)
  • 證據檔格式:XML 原檔或轉出的人可讀報告擇一上傳。選定前先想清楚證據池的可讀性——ZAP 的 D4 教訓是「證據池要人可讀」(原文見 design.md §2 D4);若上傳 XML,考慮同時在 summary 帶足夠的統計數字,或轉一份人可讀格式。此處若要偏離 design.md §4.5 的描述,停下回報

4.4 factory 註冊(T-4.3 的一部分)

~/Projects/Billows/Audit-Manager/evidence-agent/core/task_executor_connectors/__init__.py(82 行)——f17a311 之後改依 code 註冊(見 §3.1)。照 _build_zap 的形狀加 builder + _CONNECTOR_BUILDERS 一列,不動 _LEGACY_TOOL_ID_TO_CODE

⚠️ 此檔第二批也會改(加 _build_inspec),是 §7 三個衝突點之一。

4.5 其他座標

檔案 用途
~/Projects/Billows/Audit-Manager/evidence-agent/Dockerfile 本批不加任何套件(§2.3 第 1 點);只有第二批會動它
~/Projects/Billows/Audit-Manager/evidence-agent/test/(跑法 PYTHONPATH=. poetry run pytest test/ -q,repo 缺 conftest.py 必帶 PYTHONPATH) 單元測試落點
~/Projects/Billows/Audit-Manager/compliance-manager-fe/src/utils/detectionFieldValidation.js §3.4 的必填驗證 helper(設計 param_schema 前先讀檔頭註解)
~/Projects/Billows/Audit-Manager/compliance-manager-fe/src/components/.../DetectionConfigField.vue FE 七種欄位型態 + condition 條件顯示;設計 param_schema 時確認型態在支援清單內

§5 測試紀律

決策者 2026-07-30 裁示,取代原「批次結束測一次」。與第二批派工單完全一致。

5.1 端到端驗收整批延後

端到端驗收延後到全案最後一次做(與第一批、第二批合併驗收)。開發期間:

  • 不重建 agent image
  • 不部署(121 / 122 / 123 三台都不動)
  • 不跑真實掃描(不對真實網段 / 主機發動 nmap 掃描)

版號 bump 的 commit 照做(§3.6),但不出貨。

5.2 做中持續做的便宜檢查(每個子任務完成前必過)

檢查 做法
import / 語法檢查 agent:PYTHONPATH=. poetry run python -c "from core.task_executor_connectors.nmap import NmapConnector";BE:改完 model / entity / service 後起服務或跑既有測試確認 import 鏈完整
mock 單元測試 全 mock、不碰網路與真實主機(SSH client 一併 mock)。Nmap 特別適合:準備假 XML fixture(正常多埠 / 全部關閉 / 主機不可達 / 格式殘缺)+ 假的遠端執行結果,對解析邏輯與 probe() 四段錯誤分類(SSH 連不上 / 認證失敗 / 該主機沒裝 nmap / 權限不足,四種要能各自回不同訊息)跑測試。水準對齊第一批 test/test_zap_connector.py(47 項全 mock + 突變測試驗斷言真咬——刻意改壞被測邏輯確認測試會紅,避免寫出永遠綠的假測試)。openscap.py 若已有對應測試檔,mock SSH 的形狀照它
factory 迴歸 test/test_connector_factory.py 補 nmap code 的 case,確認既有四個工具取 connector 行為不變
**T-4.1 的 BE 迴歸(本批特有,不可省) 放行邏輯改完後,確認需憑證的既有工具(openvas / openscap / zap)在憑證為空時仍被擋**——這是驗收明文要求。用既有 BE 測試或手動觸發皆可,但要有實際執行紀錄,不能只是「看程式碼覺得沒問題」
SQL 套 DEV 後 SELECT 驗證 T-4.1:SELECT id, code, connection_type, requires_credentials, status FROM config.detection_tools ORDER BY id(確認既有四列仍是「需要憑證」);T-4.2:同一查詢確認 Nmap 進去且 connection_type='SSH'status='available'requires_credentials=true(← 2026-07-30 改案,原為 CLI + false),再查 config.detection_tool_param_schemas 確認 schema 進去且 is_current 正確,並確認 config_field_schema 有 SSH 憑證欄位(對照 OpenSCAP 那一列)

5.3 seed / migration 三環境都要套

DEV → STG → POC 三環境(指令照 CLAUDE.md「DB 環境清單」段;帳號 cmmgr密碼請查 .envDB_SECRET 或部署文件,禁入任何版控檔案):

# DEV
psql -h 192.168.50.188 -p 25432 -U cmmgr -d guidant_ai_dev \
  --single-transaction -v ON_ERROR_STOP=1 -f scripts/sql/<本批 SQL 檔>.sql
# STG(同台不同 DB)
psql -h 192.168.50.188 -p 25432 -U cmmgr -d guidant_ai_stg \
  --single-transaction -v ON_ERROR_STOP=1 -f <同上>
# POC(另一台)
psql -h 192.168.50.189 -p 25432 -U cmmgr -d guidant_ai_poc \
  --single-transaction -v ON_ERROR_STOP=1 -f <同上>

⚠️ T-4.1 是 ALTER TABLE,三環境不同步的後果比 seed 嚴重(BE 程式碼會讀新欄位,某環境沒套就整個 detection tools 查詢炸掉)。T-4.1 套完三環境確認無誤,才開始 T-4.2。

SQL 撰寫鐵則:檔頭 -- Date:、每個語句加日期註解、收尾 INSERT public.schema_migrations


§6 行為規範(實作 session 會踩到的,逐條遵守)

  • 不切 branch:任何 git checkout <branch> / git switch <branch> 一律不執行。BE 永遠在 feature/FR-058 工作;發現 branch 不對停下回報,不自己 fix(「為了在正確 branch commit 而切 branch」也不允許)
  • push 永遠等決策者明示,不自動 push;階段性 commit(子任務完成後)直接做,毋需停下問
  • 顯式 git add 檔名,禁用 -am——避免誤 add 其他 arc 留下的 untracked / modified 檔案(BE working tree 現有一批與 FR-058 無關的既有檔案,不歸本批管、不要清理)
  • 跨 repo 各自分開 commit:BE 與 evidence-agent 的改動不混在一個 commit 敘事裡
  • 憑證禁入版控:密碼、token、API key 絕不寫進任何會 commit 的檔案(含 SQL migration、docs、handoff)。連線資訊只寫帳號 / host / port / db 名,密碼一律寫「請查 .env 或部署文件」
  • DDD 層級規範(T-4.1 會踩到):route 層不做 DB 查詢;app service 不直接 import ORM model,走 domain service → repository;新增 domain service 依賴時在 __init__ 加參數並到 di_containers/ 對應 container wiring
  • 收尾類動作等命令:SPEC / SUMMARY / memory / Notion 母案收尾一律等決策者明確下令,子任務 commit 完只回報 status,不往收尾跑
  • Notion 卡回寫格式(每個子任務完成後):狀態改「修正待驗證」+ 補一段白話說明(做了什麼、怎麼驗證的)+ commit hash + 執行紀錄(跑了哪些檢查、結果)。格式參考第一批 CM-967〜970 的回寫
  • BE 出錯先看 logtail -200 log/app.log | grep -A 30 -i 'Traceback\|ERROR',log 沒線索才回報
  • 遇到 D1–D12 沒涵蓋的新決策停下來回報,不要自己開新方向(判斷原則見 design.md §2 各條的「被排除方案與原因」欄)
  • 不要晶晶體(中英夾雜語法),不用「速贏」一詞

§7 與第二批的協調注意(兩批可能同時進行)

第二批(InSpec/CINC → GCB,CM-971〜979,派工單 2026-07-30-fr058-batch2-dispatch.md)與本批無功能依賴、可同時進行,但兩批都會動 evidence-agent 這個共用 repo。

7.1 不會衝突的部分

項目 為何不衝突
connector 檔本體 第二批新增 inspec.py,本批新增 nmap.py——不同檔案
單元測試檔 test_inspec_connector.py vs test_nmap_connector.py——不同檔案
BE seed SQL 各自新檔(檔名帶 fr058-<序>-<工具>),內容互不相干
BE 平台邏輯 本批動 start_execution() 憑證檢查與 detection_tools schema;第二批只 seed 不動 BE 程式碼

7.2 ⚠️ 三處會撞的共用檔

檔案 兩批各自要做什麼 處置
core/task_executor_connectors/__init__.py 第二批加 _build_inspec + "inspec" 一列;本批加 _build_nmap + "nmap" 一列 都是「加一個 builder 函式 + dict 加一列」,語意上不衝突,但文字上會撞同一個區塊。commit 前先 git pull / git log --oneline -5 看對方有沒有先動;若已被改,把自己的那一列加上去即可(不要覆蓋對方的
Dockerfile 第二批加 CINC Auditor;本批不動(Nmap 不 bundle,§2.3) 本批不碰即無衝突。若你發現自己在改 Dockerfile,先確認自己不是在裝 nmap
版號(四處同步) 兩批各自要 bump 一版 git log + 看 pyproject.tomlversion 確認目前版號再決定自己 bump 到多少(第一批已到 0.2.13;先出貨者拿 0.2.14,後者順延 0.2.15)。不要拿本文的數字當現況

7.3 操作紀律

  • commit 前先 git log --oneline -5(agent repo),確認對方的 commit 有沒有進來
  • 顯式 git add <檔名>,絕不 -am——-am 會把對方正在改的檔案一起帶走
  • 若真的撞成 merge conflict:兩邊的內容都要保留(兩個 builder、兩列 dict),不要二選一
  • BE repo 兩批共用 feature/FR-058 branch,但改動範圍不重疊(本批動 detection_tools schema + orchestration service;第二批只加 SQL 檔),一樣照顯式 git add 原則

§8 給 fresh session 的超短派工 prompt(直接複製貼上)

接手 FR-058 第三批(Nmap)的實作工作。

完整讀序入口:
/Users/chouraymond/Projects/Billows/Audit-Manager/compliance-manager-be/docs/features/FR-058-2607-detection-tools-expansion/handoff/2026-07-30-fr058-batch3-nmap-dispatch.md
先讀它全份(特別是 §2 Nmap 的特殊性與 §3 第一批新事實),再照它的 §0 讀序開
design.md §4.5 + §5.7 + §2 的 D9。

範圍:T-4.1〜T-4.3(CM-981〜983,母卡 CM-980),序列執行。
與第二批(InSpec/CINC → GCB)完全獨立、可並行,但 evidence-agent 有三處共用檔會撞
(factory __init__.py / Dockerfile / 版號),處理方式見文件 §7。

硬紅線速記:Nmap 走 connection_type='SSH'(2026-07-30 D9 改案,原定 CLI 已作廢)——nmap 由
客戶裝在自己指定的主機上,agent 走 SSH 登入該主機執行,與 OpenSCAP 完全同構,connector 主要
對照 openscap.py 而非本機子行程模式;Nmap 需要 SSH 憑證,seed 時 requires_credentials=TRUE。
Nmap 不 bundle 進 agent image(NPSL v0.95 §3 的衍生作品條款明文涵蓋「專門執行本軟體並解析其
結果」的程式,正中我們的 connector),也不可用 volume 掛載主機 binary 繞過;probe() 要四段式
分類(SSH 連線 / 認證 / 該主機 nmap 可執行 / 權限),第三段訊息要能讓管理員知道「去那台主機裝
nmap」;setup_guide 要寫在哪台主機裝、怎麼裝(無 docker 操作)+ 埠掃描的授權警語。
T-4.1 的零憑證放行照做但 Nmap 已不是它的使用者——那是平台級能力,既有工具(含 Nmap)都必須
維持「憑證為空即擋」,新欄位預設往嚴的方向;不建空的租戶設定當佔位。
param_schema 避免出現必填的 number/boolean 欄位(FE 驗證用 falsy 判斷,0/false 會被誤判為
缺漏,詳見文件 §3.4);新 connector 依 code 註冊,絕不加進 _LEGACY_TOOL_ID_TO_CODE。

注意 connection_type='CLI' 至今仍無任何實際使用者(改案後 Nmap 不再是它的首個使用者),
本批不會改變這個事實;走 SSH 是已被 OpenSCAP 實戰驗證過的路徑,照抄即可(文件 §2.1)。

測試紀律:端到端驗收整批延後到全案最後,開發期間不重建 image、不部署、不跑真實掃描;
只做 import 檢查 + 全 mock 單元測試(假 XML fixture)+ SQL 套 DEV 後 SELECT 驗證;
T-4.1 額外必做「既有需憑證工具仍被擋」的迴歸驗證;migration 三環境都要套,
且 T-4.1 是 ALTER TABLE,三環境套完確認無誤才開始 T-4.2。

BE 在 feature/FR-058 工作,不切 branch、不 push、顯式 git add 禁 -am;每個子任務完成即
commit 並回寫 Notion 卡(修正待驗證 + 白話說明 + commit hash + 執行紀錄);收尾動作等我下令。

📌 寫完自檢:下個 session 只看這一份 + design.md §4.5 / §5.7,能不能不回頭問任何人就開工?——本文所有路徑 / 卡號 / commit hash / SQL 指令皆可直接使用;密碼類資訊一律指向 .env