FR-166 · 需求討論稿 v4 · 2026-10-26(D1~D16 已裁;§13 全案重新檢視;§14 全量工作清單 37 項給 PM 分階段)

主系統不在,Agent 也要能自己站著

直接打 Agent 網址就能登入、不靠任務就能按掃描工具、現場查連線問題、斷線不停工且復線自動補傳

Agent 現在是主系統的手腳:所有事都要主系統發令才動,主系統斷線它就只剩心跳在等。老闆要它變成能自己站著的工具箱——客戶的 Agent 跟台北主系統斷了線,現場的人照樣能登入 Agent、手動按掃描、查「為什麼連不到那台設備」;未來高雄、台南、日本、新加坡各裝一台,平時自己用、報告再回傳總部。本稿分析五件事:怎麼登入(業界常見的五種模式比較)、掃描怎麼不經任務就能按、網路除錯要包哪些工具(有些公司看到 traceroute 會跳告警,要讓使用者自己勾)、斷線期間任務怎麼續做與補傳、以及這樣開門後怎麼不被人當跳板或駭客工具。

D1~D16 已裁(2026-10-26) §14:37 工項/約 50 卡/五階段,最小可出貨 28 卡,等 PM 砍 §13 全案重新檢視:18 條死結推演、15 條新情境、2 條資安規則例外 沿用 FR-157 主控台殼、FR-156 入場券與 8444 門 跨 4 repo:BE/FE/jedi-python-package/evidence-agent 陷阱:獨立掃描不可放寬授權目標清冊 陷阱:除錯工具是雙面刃,不包的東西比包的重要

狀態:v5|日期:2026-10-26|已裁:D1~D23(D20~D22 是決策者看過十二幕雛形後補裁;D23 是手測時改裁)|LDAP 三選一納入、憑證改首次信任釘住(推翻 CM-2239)|§13 全案重新檢視|§14 全量工作清單給 PM(§9 彙整)|定案後落 design.md 再開卡 雛形:prototype/index.html(六幕:直接打網址的登入頁、總覽(含離線橫幅)、獨立掃描、網路除錯、執行紀錄與待回傳、主系統端的離線操作者授權與自主執行紀錄;右上可切「在線/離線/未同步」看三種狀態的差別)


1. 需求背景

1.1 老闆與決策者說了什麼

日期 誰 說什麼 白話翻譯 本稿對應
2026-10-26 老闆 Agent 跟主系統離線了,還是要能被手動觸發掃描或用 Agent 內其他功能 Agent 要能自己站著,不是主系統的手腳 §3 登入、§4 獨立掃描
2026-10-26 老闆 主系統在台北,會開放高雄、台南、日本、新加坡的點安裝,各區掃描與報告回傳,平時也能用我們的工具 多據點各自作業、報告彙整回總部 §7 離線續傳、§8 主系統端(據點)
2026-10-26 老闆 要有網路連線除錯工具(ping、traceroute 等),跟設備離線時能查原因,回報給我們或客戶網管 Agent 要能當現場的網路診斷箱 §5 網路除錯
2026-10-26 決策者 除錯時要讓使用者選要用哪些工具,有些公司會針對這些動作跳告警 診斷動作不能全自動一次全開 §5.3 勾選與噪音標示
2026-10-26 決策者 不能被人當作跳板或駭客工具,設計時資安細節都要注意 開門的同時要把不該開的鎖死 §6 資安守門
2026-10-26 決策者 登入方式提供幾種模式,最好是業界常見的 不自創登入機制 §3.2 五種模式比較(D1)
2026-10-26 決策者 萬一主系統沒先給、根本沒設過,這 100% 會發生;要防的是被當跳板 「誰能登入」與「能掃誰」是兩道門,後者不看身分 §3.3 三層身分、§6.1 三道門(D1、D12)
2026-10-26 決策者 都沒同步過就預設沒完成設定、拒絕用工具;用 Agent 管理員登進來查連線 未同步=只准主系統連線診斷 D12(已裁)
2026-10-26 決策者 離線超過 30 天掃描也停 清冊可能早過期,只剩診斷 D2(已裁)
2026-10-26 決策者 是不是乾脆 Agent 也起一個 Postgres? 分析後裁不起,本機狀態收進同一個 SQLite D11(已裁)
2026-10-26 決策者 據點不需要呀,不就是 Agent 名稱就好 名稱本來就由管理員取,「高雄機房 Agent-01」一看就知道在哪 D8(已裁:不做據點欄)
2026-10-26 決策者 第二期的東西這次就要做,除非太龐大再討論;「看自己這台」做成 UI mtr 式監測、網卡狀態、NetworkManager 狀態都進第一版;本機狀態是儀表板不是勾選項 D5(已裁)
2026-10-26 決策者 tcpdump 呢? 分析後裁不包:偷聽整條線、EDR 必告警、擷取檔是外洩點;替代由埠測試/TLS/MTU 補 D10(已裁)
2026-10-26 決策者 有 UI 嗎?會知道斷在哪嗎?安裝時就連不通怎麼知道本體在哪? 結果頁=路徑圖+判讀卡;D6 補未同步只准三項 §5.4、D6(已裁)
2026-10-26 決策者 順便加重啟 Agent;從主系統發動為什麼不行?他一樣可以開視窗連進來 首腦改口:跳轉進主控台按重啟本來就是從主系統發動;另加心跳旗標 D14(已裁)
2026-10-26 決策者 白名單也是從主系統同步進來? 兩種白名單都從主系統來;CIDR 安裝時設底線、主系統只能收緊 D9(已裁)
2026-10-26 決策者 整個重新分析:死結、沒想到的情境、既有機制、開發與資安規範 §13 §13
2026-10-26 決策者 LDAP 老闆會要,有現成的對不對?順便檢查那個功能有沒有缺漏 有(jedi-iam),登入本體對,但三個洞未修 §3.5、§13.8
2026-10-26 決策者 要讓他選從主機同步還是自己設,各區可能不同 LDAP 三選一:同步/自設/不接 D17(已裁)
2026-10-26 決策者 CM-2239 說不勾的是憑證嗎?使用者不會有這個憑證,要嘛我們自動信任,這太工程師了 改「首次信任、之後釘住」,拿掉勾選框 D18(已裁,推翻 CM-2239)
2026-10-26 決策者 抽出來是可以被復用的嗎?要可以復用抽出來才有意義;輪子不要一直重造 抽 jedi-ldap 零相依套件,三個使用者 D19(已裁)
2026-10-26 決策者 先用最嚴謹的設計看有多少工,再跟 PM 討論分階段或剔除;切階段切內容要分清楚,不能戰線太長沒辦法銜接 §14 全量工作清單 §14
2026-10-26 決策者 第三方元件掃描也是 call Agent 執行,應該整個在 Agent 上執行,只是看使用者要用哪一台 實查它本來就全在 Agent 跑,本案只補 Agent 自己的入口;結果走既有 source_scans §4.3b
2026-10-26 決策者 所有的掃描工具都要可以在 Agent 上獨立執行,這個是不是沒放進去 七支有、ZAP 被首腦以清冊為由排除——改為把網址型目標納清冊,八支全開 D3 修正

1.2 現況哪裡痛

實查 evidence-agent(主控台 FR-157 已上到 F2)與 jedi-remote-agent 後,五件事各自的現況:

需求 現況 差距
直接連到 Agent 操作 8444 主控台已存在,但進門只有兩條路:主系統簽五分鐘入場券跳轉、或登上 Agent 機下 sudo guidant-agent-ctl console-login 印一次性網址。直接打網址只看到「請從主系統開啟」指路頁 主系統斷線=第一條路斷;第二條要能 SSH 進機器,現場網管多半做不到
離線時的身分 Agent 不存任何人:驗票用雲端公鑰,session 存記憶體,沒有帳號表、沒有密碼 離線登入要先回答「Agent 怎麼知道你是誰、你能做什麼」
獨立觸發掃描 八支工具(OpenVAS、OpenSCAP、Nmap、CINC Auditor、ZAP、SonarQube、第三方元件掃描、SSH 主機金鑰)全由心跳領 pending_tasks 再開執行緒跑;主控台「檢測工具」頁只有帳密設定與「測試連線」 沒有「選工具→填目標→按執行」的入口;執行結果的落點(任務證據)也不適用於手動執行
網路除錯 無。最接近的是各 connector 的「測試連線」(只測工具本身) 整塊新做
斷線續傳 任務執行緒不受斷線影響會跑完;報告本來就存 Agent 自己的儲存(_upload_report 走本機 file_upload_service);但結果回報 POST /result 失敗只記 log,程式註解寫「本階段無自動重派邏輯,留待觀察」。主系統端任務停在 dispatched,畫面不會告訴使用者 Agent 離線了 要補「回報失敗進待傳佇列、復線重送」與主系統端「等待 Agent 回傳」的狀態顯示

1.3 已有的零件(本案不用重做)

零件 在哪 本案怎麼用
8444 瀏覽器門+主控台殼 evidence-agent core/browser_plane_server.py、core/console/(session、routes、overview、tools、files、audit、heartbeat) 登入頁、掃描頁、除錯頁、紀錄頁全掛在這套殼上,加分頁不加門
入場券跳轉 jedi-remote-agent agent_ticket_service+Agent verify_console_ticket 在線時的首選登入路,照舊
機上救援碼 core/console/local_login.py(sha256 落地、一次性認領、五分鐘) 保留為「什麼都不行時」的最後一條路
授權目標清冊快取 core/allowed_targets.py(心跳下發、持久化、從未收到=一律拒) 獨立掃描與網路除錯的目標都走它,離線沿用快取
八支 connector core/task_executor_connectors/* 獨立掃描直接呼叫同一支 connector,不另寫執行路徑
本機帳密儲存 core/credential_store.py(Fernet、secrets.key) 獨立掃描用本機帳密,與派工同一份
本機稽核+心跳回傳 core/console/audit.py、core/console/heartbeat.py(游標+ack) 所有新操作走同一條稽核路;離線時自然累積、復線補傳
Agent SQLite upload_files 一張表(FR-066 D2 起單檔) 新增「本機執行紀錄」「待傳佇列」「本機操作者」三張表,同一個檔
FE 主控台入口 compliance-manager-fe/console/(Vite 第二入口,三個 view) 加 view 不加入口
主系統 Agent 管理頁 FE RemoteAgentManage.vue、jedi-remote-agent remote_agents(已有 name、base_url、capabilities、在線判定=心跳間隔×3) 加「據點」欄、「離線操作者」設定、「自主執行紀錄」分頁

1.4 不做什麼(本稿先劃線,決策者可改)

不做 原因
Agent 自己建帳號、自己管密碼政策、自己管角色表 身分來源仍是主系統;Agent 只有一個固定的本機管理員(安裝時設)與主系統下發的離線操作者名單,沒有「新增使用者」功能
Agent 起 Postgres 本機資料單機、單進程、幾千筆以內,SQLite 甜蜜點;FR-066 D2 拔掉 PG 的理由一條沒變;業界邊緣節點(Nessus scanner、Wazuh agent)都用嵌入式 DB(D11 已裁)
多 Agent 之間同步設定或帳號 會經雲端,與 FR-157 D9 一致不做
獨立掃描放寬授權目標清冊 這條是防跳板的核心;離線不是放寬的理由(§6)
包 nmap 全埠掃描、封包擷取、任意命令列、SSH 跳轉到別台 包了就是駭客工具(§5.4、§6)
Agent 本體離線升級 FR-156 D4 已裁不做;弱點資料離線包走 FR-165
多因子(TOTP) 決策者裁「第二期的這次就做、太龐大再議」——本項屬太龐大:要客戶每人裝驗證器、要備援碼流程、要主系統端綁定頁,是一張完整 FR 的量;且離線登入已有三層+清冊門,多因子的邊際效益低。列為明確不做,決策者可反對
自主執行紀錄掛成任務證據 牽動任務抽屜與證據生命週期(FR-163/164 剛改完);本案先把紀錄與報告做出來,掛證據等決策者看過紀錄頁再決定形狀
Agent 自簽憑證換客戶憑證 FR-156 D9 範圍,不在本案重複
「據點」欄位或「區域」實體 Agent 名稱本來就是管理員取的、一台 Agent 通常就是一個點;自主執行紀錄與清單照 Agent 名稱篩(D8 已裁)

2. 分工概述(30 秒版)

整案:Agent 主控台多三個分頁(掃描工具、網路除錯、執行紀錄)與一頁登入,主系統連得上時照舊跳轉進去,連不上時用主系統事先授權的本機帳號登入;掃描與除錯直接在 Agent 上按、結果先存 Agent,連回主系統後自動補傳到「Agent 自主執行紀錄」。

子需求 做什麼 產出 完成怎麼判定(決策者檢查法)
A 登入與離線身分 直接打 Agent 網址有登入頁;主系統授權「誰能離線登入哪台」,在線跳轉時順手設本機密碼;離線用帳號+本機密碼;救援碼保留;鎖定與有效期 登入頁+主系統授權設定+Agent 本機操作者表 把 Agent 機的網路對主系統切斷(拔 VPN 或改 hosts),瀏覽器直接打 https://<agent>:8444/console 看到登入頁,用帳號+本機密碼登得進去,畫面上方有「主系統連線中斷」橫幅;沒被授權的帳號登不進去;連續打錯五次鎖十五分鐘
B 獨立掃描 主控台「掃描工具」頁:選工具→從清冊挑目標→填參數→執行;結果存 Agent 本機紀錄;復線回傳主系統 掃描工具頁+本機執行紀錄 離線狀態下對清冊內一台主機跑 OpenSCAP,看到進度與完成、能下載報告;對清冊外 IP 執行被擋且訊息寫「不在授權目標內」;接回主系統後在 Agent 管理頁「自主執行紀錄」看到這筆並能下載同一份報告
C 網路除錯 主控台「網路除錯」頁:挑目標、勾工具(每項標噪音等級,預設只勾安靜的)、執行、看結果、匯出或回傳 網路除錯頁+診斷報告 對一台拔了網路線的設備跑 ping+traceroute,結果顯示在哪一跳斷;traceroute 旁有「可能觸發 IDS 告警」標示且預設不勾;報告匯出 PDF/文字,接回主系統後在自主執行紀錄看得到
D 離線續傳 任務結果與報告回報失敗進 Agent 待傳佇列,心跳成功後依序重送;主系統端收重複回報不炸;任務畫面顯示「Agent 離線,等待回傳」 待傳佇列+主系統端狀態顯示 主系統派一支檢測給 Agent 後立刻切斷 Agent 對主系統的網路,等掃描跑完,主系統任務顯示「Agent 離線、等待回傳」不是失敗;接回網路兩個心跳內任務變成功、報告與證據都在
E 主系統端配合 離線操作者授權頁;「自主執行紀錄」分頁(可下載報告、可篩 Agent/工具/人);Agent 清單顯示待傳件數與「離線操作者 0 位」警示 主系統三處改版 授權頁能勾人並選範圍;自主執行紀錄能依 Agent 名稱篩到「高雄機房 Agent-01」的掃描;清單對沒勾人的 Agent 顯示紅字
F 文件與出貨 SPEC、手冊(含「哪些除錯動作可能觸發客戶告警」一節)、安裝文件(CIDR 白名單) 文件改成現況 手冊「Agent 獨立作業」一章有登入三條路、離線能做什麼不能做什麼、除錯工具噪音表

資安守門(§6)不是獨立一棒,是 A~E 每棒的驗收條件;收口時 ddd-compliance-reviewer 與資安規則檔一起掃。依賴:FR-157 主控台殼(已上到 F2)、FR-156 E1(已 Done)。決策者功課:D1 登入模式、D5 除錯工具清單要拍板。


3. 直接進門與登入

3.1 現況的門怎麼開、哪裡斷

FR-157 D1 裁「Agent 不開登入表單」,前提是主系統一定連得到。本案的前提剛好相反:主系統連不到是常態之一。FR-157 的三條路在離線時:

路 離線時
主系統簽入場券跳轉 斷(主系統本身連不到)
機上 console-login 救援碼 可用,但要能 SSH 上 Agent 機且有 sudo;現場網管多半沒有
直接打網址 只看到指路頁

所以「離線登入」要新開一條路,而這條路的核心問題不是表單怎麼畫,是 Agent 離線時憑什麼認得你。

3.2 業界常見的五種模式(D1)

模式 業界例子 離線能用 帳密存哪 主系統撤銷後多久失效 優 劣
A 主系統跳轉(現況) Okta/AD 的 SSO 跳轉 ✗ 不存 即時 零新攻擊面、不用記網址 主系統斷就進不去
B Agent 自己的本機帳號 Nessus、Wazuh、防火牆與交換器的管理頁 ✓ Agent(雜湊) 不會——主系統不知道這些帳號存在 最單純、離線完全獨立 第二套帳號表:誰有帳號主系統不知道、離職沒人刪、密碼政策各管各的
C **主系統授權的本機帳號(快取式) Windows 網域帳號的「快取登入」(筆電離開公司網路仍能登)、1Password/Bitwarden 離線解鎖 ✓ Agent 只存「主系統授權過的人」名單+各人另設的本機密碼**雜湊;名單隨心跳同步 在線時下一次心跳即時;離線時靠有效期自動到期(D2) 名單只在主系統一處管;離線能用;授權到期自動失效;不把主系統密碼雜湊送出去 使用者要在線時先設一次本機密碼(跳轉進來時順手設);離線期間撤銷不了
D 接客戶的 AD/LDAP Nessus、GitLab、Jenkins 的 LDAP 登入 ✓(AD 通常與 Agent 同內網) 不存 AD 停用帳號即生效 客戶 IT 最習慣;離職流程走 AD 就好 要設定(LDAP 網址、bind 帳號、群組對應);小客戶沒 AD;Agent 多一個對外連線與一組帳密要存
E 一次性救援碼(現況 CLI) break-glass、設備的 console 口 ✓ 不存 一次性 零設定 要登機器下 sudo;不適合日常

還有兩個常被問到的,本稿先排除:硬體金鑰/客戶端憑證(瀏覽器匯入憑證才能進——落地版客戶會抱怨麻煩,且撤銷要管 CRL)、Agent 自己接 OIDC 到雲端 IdP(離線時一樣斷,等於 A)。

多因子(TOTP 驗證碼 App):B/C/D 都能疊。業界獨立管理台(Nessus、pfSense)多半有,但要客戶每個人裝驗證器 App。本稿建議列第二期選配,不綁第一版。

3.3 定案:三層身分(D1 已裁)

決策者點出的關鍵:「主系統沒先給、根本沒設過」100% 會發生。這種情境下 Agent 只有兩種選擇——只認安裝時設的那組密碼,或自己放寬讓任何主系統帳號試(離線時驗不了主系統密碼,等於讓人猜,就是跳板的開端)。沒有第三條路;業界所有有管理頁的設備(Nessus scanner、FortiGate、UniFi)都走第一種。所以登入分三層,從「最後保障」到「日常」:

層 帳號 怎麼來的 能做什麼 什麼時候用 過期/撤銷
① 本機管理員 local-admin(固定一個) 安裝時強制設密碼,不設不給裝完;既有客戶升級時互動終端照問、無人值守不強制改紅字提醒(D13);12 碼以上 全部分頁;**但掃描範圍照樣過清冊(§6.1 門 2),未同步時只准主系統連線診斷(D12) 斷線、剛裝好、名單裡沒人、其他路都不通 永不過期、不受離線停用**;改密碼在主控台內或機上 guidant-agent-ctl reset-admin-password(sudo)
② 主系統授權的離線操作者 主系統帳號 管理員在主系統勾人+範圍+到期日,心跳同步名單到 Agent;本人在線時進主控台設一組本機密碼(不是主系統密碼,只存雜湊在這台) 名單上寫的範圍 日常離線操作(各據點網管) 到期日自動失效;離線 30 天停用;在線時撤銷下次心跳生效
③ 主系統跳轉 主系統帳號 入場券(FR-157 現況) 票上的範圍 在線時的首選 即時

救援:機上 sudo guidant-agent-ctl console-login 一次性網址(現況已有),給「本機管理員密碼也忘了」的情況。忘密碼的恢復路徑是「有機器 sudo 權限的人」,與所有設備忘密碼要接 console 口同理。

%%{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 I as 安裝者
    participant U as 使用者瀏覽器
    participant M as 主系統
    participant AG as Agent 8444
    Note over I,AG: 安裝時
    I->>AG: install.sh 強制設 local-admin 密碼(只存雜湊)
    Note over M,AG: 在線時(平常)
    M->>AG: 心跳回應帶「離線操作者名單」:誰、範圍、到期日(沒有主系統密碼)
    U->>M: Agent 管理頁按「開啟主控台」
    M-->>U: 五分鐘入場券
    U->>AG: 帶票進門
    AG-->>U: 發 session;在名單且未設離線密碼 → 提示設定
    U->>AG: 設離線密碼(只存雜湊)
    Note over U,AG: 離線時
    U->>AG: 直接打 https://agent:8444/console
    AG-->>U: 登入頁(橫幅:主系統連線中斷)
    U->>AG: 主系統帳號+離線密碼,或 local-admin+管理員密碼
    AG->>AG: 名單內?未到期?密碼對?沒被鎖?(local-admin 只驗後兩項)
    AG-->>U: 發 session;掃描範圍仍照清冊,未同步只准診斷
    Note over U,AG: 什麼都不行時
    U->>AG: 機上 sudo console-login 印一次性網址
圖 1 — 三層登入:在線走跳轉(順手設離線密碼),離線用離線密碼或本機管理員,什麼都不行用救援碼

設計點:

  • 名單由主系統下發,Agent 只做快取,名單只有帳號、顯示名、範圍、到期日,沒有主系統密碼雜湊。Agent 端沒有任何「新增使用者」功能,①只有一個固定帳號——Agent 永遠不會長出主系統看不見的身分。
  • 離線密碼是另一組:主系統密碼雜湊不該離開主系統(FR-157 的動機);一人授權多台各設一組可接受(與 FR-157 D9 同理);在線跳轉進任一台都能重設該台密碼。
  • 登入頁版面照主系統 Login.vue(左 44% 表單、右品牌面板、底線式分頁),改 Agent 模式:分頁依連線狀態變——在線=主系統登入(首選,跳回主系統驗證)/AD/離線密碼;離線=AD/離線密碼/本機管理員;未同步=只有本機管理員可按。右面板主角是「這台 Agent 卡」(名稱、IP、版本、角色、連線狀態)加一句狀態提示,不放 hero 標語;拿掉記住我、忘記密碼、captcha。FE console 的 Login.vue 直接從主站複製版面與樣式再刪分流邏輯。
  • 主控台殼改左側邊選單,結構照主系統、身分靠四個訊號分(決策者 2026-10-26 裁:「結構一樣、身分明顯不一樣」)。FR-157 的頂部 TabView 在三頁時合理,七頁不夠放。側欄 14rem、頂欄 4rem、麵包屑、側欄底部使用者區,照主站 AppSidebar/AppTopbar 複製版面(console 不載 router 與 Pinia,選單項寫死在 console 自己的清單)。分組:總覽/作業(掃描工具、網路除錯、執行紀錄)/設定(檢測工具、身分來源、檔案)/維運(服務與重啟);未同步時作業與設定項反灰。四個環境訊號:①側欄用主站現成的 teal 主題色(--menu-bg: #00695C),主站側欄跟亮暗走、Agent 固定 teal;②側欄頂「AGENT」字樣+這台的名稱與狀態點(主站同位置是租戶/專案);③內容區頂部常駐狀態列「AGENT · 高雄機房 Agent-01 · 主系統連線正常/中斷/未同步」,不是只有異常才出現;④瀏覽器分頁標題「Guidant AI Agent · 高雄機房 Agent-01」加 favicon 角標。同一套產品的 prod/staging 就是這樣分,不靠版面不同辨識。
  • 總覽=dashboard,不另開 Dashboard 頁:要處理的事放最上面(主系統連線、待回傳積壓、磁碟低於門檻、清冊過舊、帳密儲存損壞、有掃描被拒絕),其他正常收成一行;網路一行四個點(網卡、閘道、DNS、主系統);正在執行;最近五筆;這台的靜態資訊放最下。主系統端 Agent 清單本身就是跨台 dashboard,不另做「Agent 總覽儀表板」;跨據點統計圖列第二期報表。
  • 「沒設過」不能是靜默狀態:主系統 Agent 清單對「離線操作者 0 位」顯示紅字警示;安裝完成畫面與手冊要求把 local-admin 密碼交給現場負責人保管。
  • local-admin 不是萬能帳號:它只過門 1;門 2(清冊)與門 3(工具清單)不看身分(§6.1)。攻擊者拿到 local-admin 能做的是對清冊內已授權設備跑既有工具,不能掃任意 IP、不能下指令;未同步過的 Agent 連掃描都跑不了。

D1 離線登入模式(已裁 2026-10-26)

三層:安裝時強制設的本機管理員 local-admin(永不過期、不受離線停用、仍過清冊)+主系統授權的離線操作者(C 模式,日常)+入場券跳轉(在線首選);救援碼保留。LDAP/AD 列第二期選配;純本機多帳號不做;多因子第二期。

D2 有效期、鎖定與離線停用(已裁 2026-10-26)

離線操作者授權預設 90 天、可選 30/180/不限;離線超過 30 天(最後一次心跳成功起算)離線操作者登入與獨立掃描一併停用(清冊可能早過期),只剩 local-admin 可登、只准網路除錯與主系統連線診斷;主系統派的任務若復線後才領到照常跑。登入失敗 5 次鎖 15 分鐘並寫稽核;密碼最短 12 碼;local-admin 永不過期。

3.4 登入後能做什麼:在線、離線、未同步三種狀態

「未同步」=這台 Agent 從未收過授權清冊或離線操作者名單(剛裝好還沒註冊、或註冊後第一次心跳還沒成功)。決策者裁:未同步就預設沒完成設定,拒絕用工具,只准 local-admin 登進來查連線(D12)。

功能 在線 離線(30 天內) 離線超過 30 天 未同步
登入 跳轉(首選)/離線密碼/local-admin 離線密碼/local-admin 只有 local-admin 只有 local-admin
總覽、檔案、工具帳密(FR-157 既有) ✓ ✓ ✓ 總覽只顯示註冊狀態與診斷入口
獨立掃描(§4) ✓ ✓(清冊用快取) ✗ 停用 ✗(從未收到清冊=一律拒,既有行為)
網路除錯(§5) ✓ ✓ ✓ 只准主系統連線診斷(心跳端點、憑證、時鐘、閘道、DNS)
看主系統派的任務進度 ✓ ✓ ✓ —
執行紀錄回傳主系統 即時 進待傳佇列 進待傳佇列 —
清冊、名單、工具定義更新 心跳即時 用快取;畫面標版本與日期 用快取 無
弱點資料更新 心跳得知自取 不更新 不更新 無

D12 未同步狀態(已裁 2026-10-26)

沒收過清冊或名單=未完成設定:只有 local-admin 能登,只准主系統連線診斷,畫面寫「尚未完成與主系統的同步,請先連上主系統」。這是防跳板的核心——沒被主系統認可的 Agent 不管誰登入都掃不了任何東西。

3.5 第四層:接客戶 AD/LDAP(D17、D18、D19 已裁)

決策者判斷老闆會要。實查現況與缺漏在 §13.8;這裡講設計。

三選一(各區可能不同 AD——總部一個網域、新加坡子公司自己的、日本點用 Google Workspace 沒有 AD)。主控台「檢測工具」旁同形狀多一張「身分來源」卡:

選項 適合誰 設定存哪 Agent 回報
跟主系統同步(預設) 總部與主系統同一個 AD 主系統心跳下發 ldap_config,Agent 不存設定;服務帳密 Fernet 存 credential_store ldap_source=synced
這台自己設 分公司有自己的 AD Agent 本機,形狀同工具帳密(FR-157 已做:Fernet 儲存+測試連線+釘住指紋) ldap_source=local +伺服器位址,主系統清單看得到哪台接了哪個目錄
不接 這個點沒有目錄服務 — ldap_source=none

守住的點:LDAP 只取代「密碼怎麼驗」那一步,不決定「誰能登」——名單仍是主系統下發的離線操作者;名單內的人可選用 AD 密碼登 Agent、不必另設離線密碼;名單不在、AD 密碼對也不給進。AD 也連不到時退回離線密碼或 local-admin,登入頁一個帳號欄,Agent 自己判斷。

憑證(D18):客戶多半沒有 CA 憑證可貼,「驗證憑證」勾選框是工程師思維。改成 首次信任、之後釘住(SSH known_hosts 模式,專案 SSH 連線已是 RejectPolicy+known_hosts):第一次連自動記下伺服器憑證指紋、畫面顯示「已信任 xxx 的憑證(到期 2027-03)」;之後指紋變了就擋並紅字「伺服器憑證變了,若非你們更換憑證請聯絡網管」,管理員按「重新信任」才放行。拿掉勾選框,只留「貼上 CA」當進階選項。加密方式預設 STARTTLS,連不上自動退 389 並黃字「目前是明文連線」。使用者零操作、零憑證知識,中間人攻擊擋得住。此條推翻 CM-2239(當時裁預設不勾的理由正是「客戶沒憑證」,釘住模式同時滿足兩邊)。主系統 LDAP 設定頁與 Agent 端都改。

抽套件(D19):ldap_adapter.py 兩百多行混了兩種東西——目錄連線(約 180 行,只靠 ldap3)與主系統帳號對應(約 30 行,查 user_auth_providers/users 表,繼承 BaseRepository)。Agent 沒那兩張表,裝整支 jedi-iam 要拖 Flask-RESTful 全套且 jedi-common 版本線衝突(Agent 釘 0.0.31、jedi-iam 要 ≥1.3.4——jedi-masking 抽成零相依套件的理由相同)。

jedi-ldap(新,零相依只靠 ldap3)
  LdapDirectory(config).verify_user(username, pw) → DirectoryUser(uid, name, email, groups)
  LdapDirectory(config).test_connect() → 結構化結果(連不到/憑證變了/帳號格式/密碼錯)
  LdapConfig dataclass;首次信任釘住(指紋算與比對;存哪由呼叫端給 store)
  加密預設 STARTTLS;不丟 Flask 例外
jedi-iam.LdapAdapter  → 瘦成 30 行:呼叫 jedi-ldap 拿 DirectoryUser,再查綁定表;失敗計數補 LDAP 路徑
evidence-agent        → 裝 jedi-ldap,不裝 jedi-iam

復用性(決策者:「要可以復用抽出來才有意義」):判準是不綁任何一張表、不綁 Flask、只靠 ldap3。現在就有三個使用者——主系統登入(jedi-iam 瘦身後)、Agent 離線登入(本案)、未來任何「用客戶 AD 驗身分」的地方(FR-132 存取管理若做 AD 群組對應角色、License Center 若接客戶目錄)。C11 標的「跳脫寫在兩處」「鎖定不計入」「加密預設無」三個洞抽出來後只修一處。

D17 Agent LDAP 來源三選一(已裁 2026-10-26)

同步(預設)/自設/不接;名單仍由主系統決定,LDAP 只驗密碼;自設的回報主系統清單可見。

D18 LDAP 憑證改首次信任釘住、加密預設 STARTTLS(已裁 2026-10-26,推翻 CM-2239)

拿掉「驗證憑證」勾選框;第一次連自動記指紋、變了就擋並要人「重新信任」;「貼上 CA」留進階。主系統設定頁與 Agent 端同步改。

D19 抽 jedi-ldap 零相依套件(已裁 2026-10-26)

目錄連線段抽出;jedi-iam 瘦身呼叫它;Agent 裝它不裝 jedi-iam。套件異動清單見 §14.3 P0-1~P0-3。


4. 獨立掃描(不經任務)

4.1 老闆說的「像第三方元件掃描的模式」是什麼

FR-134/156 的第三方元件掃描是主系統側選單一個獨立模組:使用者挑 Agent、上傳源碼或 SBOM、按掃描,不必先有稽核任務,結果自己一張表(compliance.source_scans)、有自己的報表頁,之後要當證據再掛到任務。本案要把這種「工具自己能按」的形狀搬到 Agent 上,而且對八支工具通用。

4.2 畫面長什麼樣(雛形幕 3)

主控台「掃描工具」分頁:

  1. 工具卡:八支工具一人一卡,顯示「已設定帳密/未設定」「這台能跑」(capabilities);未設定的卡按下去先導去帳密分頁。
  2. 目標:從授權清冊挑(設備名+IP,清冊版本與更新時間標在旁邊);允許手打 IP 或網段,但必須落在授權網段內,否則當場擋(D3)。
  3. 參數:照工具目錄的 param_schema 長表單(與主系統派工表單同一份定義,心跳下發、Agent 快取——FR-157 D3 同一機制)。
  4. 執行:按下去進本機執行紀錄,進度與取消比照派工(同一支 connector、同一個 cancel_event)。
  5. 完成:摘要(發現幾項)、報告下載、「回傳主系統」狀態三種:已回傳(附時間)/待回傳/回傳失敗(附原因,例如主系統回 413)。在線時可按「立即重試」不等下一次心跳;離線時按鈕反灰、連回後自動補傳,急著要報告用「匯出 PDF」帶走。主系統端「手動匯入 Agent 報告」列第二期。畫面上來源一律寫「Agent 手動」(不寫「本機」,使用者分不出什麼本機)。

4.3 Agent 內部怎麼跑

不另寫執行路徑:手動執行=自己造一張「本機任務」,走 task_executor._run_one 同一條(ack 與 result 的「雲端」改成本機紀錄表)。差異只有三處:任務來源標 local、沒有 job_execution_uid、結果回報對象是本機紀錄+待傳佇列。

兩支工具的範圍(§13.1 實查):Nmap 的執行主機走 params.hosts,_run_one 呼叫 connector 前對 params 整包 allowed_targets.check(),執行主機本來就過清冊,手動模式沿用同一條不必另做。ZAP 的網址是 FR-129 刻意留的缺口(allowed_targets.py 檔頭明寫網址不在授權範圍);手動模式若照現況開 ZAP 等於開一條對任意網址發掃描的路。決策者裁「所有掃描工具都要能在 Agent 獨立執行」,所以不是拔掉 ZAP,是把網址型目標納進清冊規則:Agent 端 allowed_targets.py(203 行)把 ZAP 的網址欄位加進比對欄位,urlsplit 取 hostname → getaddrinfo 解成 IP → 過同一張清冊;解不到或不在網段內就拒。主系統清冊不用改(網址最後仍落到 IP)。主系統派工的 ZAP 也一併受這條管(現況不過清冊)。並發上限:同一台 Agent 同時最多 2 個掃描、1 個診斷,超過排隊;執行不綁 session,session 過期掃描繼續。

本機紀錄表(Agent SQLite 新增 local_runs):uid、kind(scan/diag)、tool、targets、params(不含帳密)、started_by(本機操作者帳號)、status、summary、report_file_uid(走既有 upload_files)、created_at、finished_at、synced_at、cloud_uid。

4.3b 八支工具都在 Agent 執行,差別只在入口與結果落點

決策者點出:第三方元件掃描「也是 call Agent 執行,應該整個在 Agent 上執行,只是看使用者要用哪一台」。實查現況確實如此——FR-156 之後它的執行就全在 Agent(主系統只剩開單、簽直傳票、收結果出報表),不存在「移過來」的問題;本案要補的是Agent 自己的入口。這與其他七支工具是同一個形狀:

主系統入口(在線) Agent 入口(本案新增,離線也能) 執行在哪 結果回主系統落哪
OpenVAS/OpenSCAP/Nmap/CINC/SonarQube/SSH 金鑰 派稽核任務 掃描工具頁 Agent agent_local_runs(主系統沒有這些工具的獨立報表頁,派工結果本來是任務證據)
第三方元件掃描 側選單獨立模組(選 Agent→直傳源碼→掃) 掃描工具頁(源碼直接丟這台 8444) Agent source_scans 既有收件路徑(主系統已有它的結果表與報表頁,台北在既有的報表頁就看得到高雄手動掃的那次)
ZAP 派稽核任務 掃描工具頁(網址解析成 IP 過清冊) Agent agent_local_runs

給 runner:八支都在 Agent 執行、都有兩個入口;差別只在結果回傳落哪張表。第三方元件掃描手動模式不是第二套,是在 source_scans 多一個「來源=Agent 手動」的值。

4.4 結果回主系統要落在哪(D4)

選項 做法 優 劣
a. 主系統新表「Agent 自主執行紀錄」+Agent 管理頁一個分頁(建議) 心跳或復線時 Agent 把 local_runs 推上去,主系統存 compliance.agent_local_runs(含報告檔參照,報告本體仍在 Agent);畫面在 Agent 管理頁「自主執行紀錄」分頁,可篩 Agent/工具/人/日期、下載報告 與任務體系分開、不污染證據;多台 Agent 彙整一頁看完 要掛成任務證據要再做一步
b. 直接塞進 detection_executions(任務型檢測結果表) 補 job_execution_uid 可空 報表頁現成 那張表與任務一對一,到處假設有任務;空值會讓既有頁面炸
c. 只留 Agent 本機、不回傳 最省 — 老闆明說要回傳

「掛成證據」:有了 a 之後,第二期在任務抽屜加「從 Agent 自主執行紀錄挑一份當證據」,複製參照即可。第一版不做。

D3 獨立掃描的目標範圍(已裁 2026-10-26,同日修正)

與派工同一條規矩——只能掃授權清冊(設備清冊 IP+授權網段)內的目標,離線沿用快取清冊,不因離線放寬;手打目標必須落在授權網段內;要掃新設備到主系統清冊加、等下一次心跳。八支工具全部可在 Agent 手動執行(決策者:所有掃描工具都要能在 Agent 獨立執行);ZAP 的網址解析成 IP 後過同一張清冊,Agent 端 allowed_targets.py 補這段。

D4 獨立掃描結果落點(已裁 2026-10-26)

a:主系統新表 compliance.agent_local_runs(jedi-remote-agent)+Agent 管理頁「自主執行紀錄」分頁,報告本體留 Agent、主系統只存參照;掛成任務證據列第二期。


5. 網路連線除錯

5.1 要回答的問題

「Agent 跟某幾台設備連不到,原因是什麼?」拆開來是四個層次:這台 Agent 自己的網路正不正常(介面、閘道、DNS)→ 到目標的路通不通(ping、traceroute)→ 目標的服務開不開(連接埠、TLS、HTTP)→ 跟主系統之間通不通(心跳端點、憑證、時鐘)。工具就照這四層挑。

5.2 兩區:這台 Agent 的網路(儀表板)+對目標做診斷(勾選)

決策者裁:「看自己這台」做成 UI,不是勾選項。網路除錯分頁分上下兩區。

上半區「這台 Agent 的網路」:進頁自動載入、不用按、不對外送任何封包、無噪音。右上「重新整理」「複製成文字」(貼給網管)。

卡片 顯示什麼 來源 容器形態
網卡 每張一卡:IP/遮罩/MAC/速率/up-down(綠灰點) ip addr、ethtool(速率) ethtool 要 network_mode: host 或掛 /sys/class/net,讀不到標「此部署形態不可用」
路由表 目的/閘道/介面,預設路由標示 ip route ✓
DNS 用哪幾台、順序 /etc/resolv.conf、resolvectl ✓
鄰居表 閘道 MAC 學到沒(沒有=紅字「二層不通」) ip neigh(只讀) ✓
這台在聽的埠 8443/8444 兩列,綠=在聽 ss -tuln 只列 Agent 自己的 ✓
時鐘 本機時間、與主系統偏移秒數(既有 clock_skew)、NTP 狀態 timedatectl 容器內 NTP 狀態不可用
代理 有無、指向哪 環境變數 ✓
網路服務 NetworkManager/systemd-networkd 狀態 nmcli/networkctl 原生可讀,容器標不可用
主系統連線 最後心跳、狀態(正常/連不到/連得到但被拒)、清冊版本 既有 heartbeat_status、allowed_targets ✓

下半區「對目標做診斷」:選目標 → 勾項目 → 執行。每項標噪音等級(安靜=正常應用程式天天在做;中=某些 IDS 規則會記;高=多數監控當掃描告警)。

項目 回答什麼 噪音 預設 實作與逾時
ping 通不通、延遲、掉包 安靜 ✓ ping -c 4,上限 10 次;10s
DNS 解析 名稱解成什麼、哪台 DNS 回、多久 安靜 ✓ getaddrinfo+指定 DNS;5s
指定埠測試 埠開不開;逾時(封包被丟)/拒絕(到了但沒服務)分得出 安靜 ✓ connect_ex,一次最多 5 個指定埠(D20),不提供範圍;每埠 3s
TLS 憑證檢查 誰發的、到期、鏈完整;刻意要連自簽目標:用預設驗證 context,把 SSLCertVerificationError 當結果,程式不得出現 CERT_NONE 安靜 ✓ ssl;10s
HTTP(S) 回應 回幾號、轉址、耗時;只取標頭 安靜 ✓ httpx;不跟隨跨 host 轉址、最多 5 跳、大小上限;10s
SSH 握手 TCP 連上+拿到 SSH 版本 banner,不登入(OpenSCAP/Nmap/CINC 都靠 SSH,先分「網路不通」還是「帳密錯」) 安靜 ✓ socket 讀 banner;10s
MTU 測試 VPN 下「ping 通但大檔卡」 安靜 ✓ ping -s 1472 -M do;10s
主系統連線診斷 固定序列:DNS → 閘道 ping → 主系統 ping → 443 → TLS(用既有 describe_tls_failure)→ 心跳端點 HTTP → 時鐘偏移 → 代理 安靜 ✓ 既有 cloud_trust、clock_skew;總 60s
traceroute 在哪一跳斷 中 ✗ 要勾,執行前確認 traceroute,ICMP/UDP 可選;60s;需 CAP_NET_RAW,沒有時該項標「此環境不可用」
持續路徑監測(mtr 式) 一段時間內每跳掉包 高 ✗ 要勾並確認 在 traceroute 上疊迴圈與統計;上限 60s

規則:一次只查一台、不並發;同一目標每分鐘最多 3 輪、每 session 最多 20 個目標;上次勾的組合記本機。

噪音以總量計,不只看單項(D20):決策者問「全勾不就一堆,不算攻擊嗎」。答:單項各只做一次正常連線,但八項全勾對同一台二十秒內二十幾次連線,敏感的 IDS 可能記為探測。所以按「開始診斷」前先算這次總共幾次連線(勾幾項加總:ping 4、DNS 1、埠 N、TLS 1、HTTP 1、SSH 1、MTU 1、主系統序列 5、traceroute 30、持續監測 60)與約幾秒,顯示噪音條;總連線數超過 10、或含中高項目,執行前跳確認框列出每個原因。連線之間固定隔 1 秒、不連發。不採「只能勾一項」——判讀卡要多項結果合起來才講得出「斷在第幾跳」。門檻、每項噪音等級、埠上限、間隔都寫死在程式,不能被參數改。

5.3 結果怎麼呈現:結論在上、證據在下(雛形幕 4)

決策者問「會知道斷在哪嗎」。結果區三層:

第一層:路徑圖。每一跳一個節點、綠勾紅叉,紅叉那跳就是斷點;只跑 ping 沒跑 traceroute 時兩端之間標「未探測(勾 traceroute 可看每一跳)」。

第二層:判讀卡,固定三句——結論/依據/建議動作。判讀規則是確定性的,不做 AI 推測:

症狀組合 結論
閘道都 ping 不到 問題在本機網段或這台 Agent 的網卡
閘道通、目標 DNS 解不到 DNS 問題,不是路不通
閘道通、路徑中途斷 斷在那一跳之後
路徑全通、埠「逾時」 封包到了目標附近被防火牆丟掉
路徑全通、埠「拒絕」 到了目標,該服務沒開
埠通、TLS 失敗 憑證或 TLS 設定問題
埠通、SSH 握手失敗 SSH 服務設定,非網路
小封包通、MTU 測試失敗 VPN/隧道 MTU 問題
主系統:DNS 解不到 「這台的 DNS 解不到主系統名稱,請確認內網 DNS」
主系統:443 逾時 「路通但 443 被擋,請放行 Agent → 主系統 443」
主系統:TLS 失敗 「憑證驗不過:自簽未裝 CA,或時鐘差太多」
主系統:心跳 401/403 「連得到但身分被拒:註冊 token 錯或已被停用」(既有心跳迴圈已分 401/403/404)
主系統:時鐘偏移 > 5 分 「時鐘差 N 秒,JWT 與憑證驗證會失敗,請對時」

第三層:原始輸出,摺疊,等寬字,給網管複製。

每次執行產一份診斷報告:可匯出文字/PDF(html.escape 走既有 WeasyPrint 路徑)、可「回傳主系統」(走 outbox,落自主執行紀錄 kind=diag)。報告含內網拓撲,下載過 session scope;回傳主系統後下載過能力點。

5.4 安裝後連不上主系統(雛形幕 7)

決策者問「一開始安裝就連不通,怎麼知道本體在哪」。未同步狀態下 local-admin 登入 → 總覽紅橫幅「尚未同步」→ 按「診斷主系統連線」→ 跑 §5.2 的固定序列 → 路徑圖+判讀。這條序列同時是手冊「安裝後連不上」一節的排查順序,UI 與手冊同一份邏輯。

D5 網路除錯工具(已裁 2026-10-26)

上半區本機儀表板(資訊與連線狀態分開、無噪音、自動載入);下半區十項診斷,安靜的預設勾,traceroute 與持續路徑監測要勾並確認;埠測試只給指定埠;全部進第一版,容器形態讀不到的標「此部署形態不可用」。噪音總量門檻見 D20。

D6 網路除錯的目標範圍(已裁 2026-10-26)

四類:授權清冊內設備(已同步才有)、主系統網址(註冊設定就有)、本機預設閘道、本機 DNS(讀本機就有)。手打 IP 必須落在授權網段。未同步狀態只准後三項(對應 D12)。清冊外一律拒,連 local-admin 也拒。traceroute 經過的中間節點是被動出現的,允許。


6. 資安守門:開了門之後怎麼不被當跳板

6.1 三道門:「誰能登入」與「能掃誰」是兩件事

決策者擔心的是「有登入口=有跳板」。答案是掃描範圍由主系統決定、與登入身分無關,三道門各管各的:

%%{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'}}}%%
flowchart LR
  A[任何人] --> G1
  subgraph G1[門 1 · 誰能登入]
    direction TB
    L1[local-admin 安裝時設]
    L2[離線操作者 主系統下發名單]
    L3[入場券 主系統簽]
  end
  G1 --> G2
  subgraph G2[門 2 · 能對誰動手]
    direction TB
    C1[授權目標清冊 主系統下發 Agent 只快取]
    C2[從未收到清冊 = 一律拒]
    C3[Nmap 執行主機 ZAP 網址解析 IP 也過]
  end
  G2 --> G3
  subgraph G3[門 3 · 能用什麼]
    direction TB
    T1[八支工具 只接表單定義參數]
    T2[除錯清單 不包埠範圍 nmap 封包擷取]
    T3[沒有命令列 subprocess 傳陣列不經 shell]
  end
  G3 --> R[對清冊內設備跑既有工具 僅此而已]
圖 2 — 三道門:登入只過門 1;門 2、門 3 不看你是誰,local-admin 也一樣被擋

推論:攻擊者拿到 local-admin 密碼,能做的是對清冊內那幾十台已授權設備跑 OpenSCAP;不能掃 192.168.77.5、不能掃任意網段、不能下指令。未同步過的 Agent(D12)連門 2 都過不了,什麼都掃不了。守衛測試固定一條:用 local-admin session 打清冊外目標必須 403。

6.2 逐威脅配鎖

本案把 Agent 從「只聽主系統」變成「現場也能操作」,攻擊面變大是事實;下面是每一道新門配的鎖。這些是 A~E 每棒的驗收條件,不是另一棒。

威脅 鎖 落在哪
外人從網路連到 8444 猜密碼 登入失敗 5 次鎖 15 分鐘(D2);本機密碼最短 12 碼;session 30 分鐘;可選 CIDR 白名單只准內網某段連(D9);稽核記來源 IP A
local-admin 密碼被現場多人共用、稽核分不出誰 已知限制:手冊要求平常走離線操作者、local-admin 只當救援;稽核記來源 IP;主系統對「離線操作者 0 位」紅字警示 A、E
Agent 換租戶重註冊、舊租戶資料留在機上 重註冊時清名單、清冊、執行紀錄、待傳佇列;佇列有未回傳先問「有 N 筆未回傳,確定丟棄?」;退役提供 guidant-agent-ctl wipe A、D
離職或撤權的人離線期間還能登 授權有效期(D2);離線超過 30 天停用;在線時心跳即時同步名單 A
被偷走或拔線搬走的 Agent 永遠獨立運作 離線 30 天停用本機登入與獨立掃描;救援碼只能看狀態 A
拿 Agent 掃清冊外的主機(跳板) 獨立掃描與除錯都走 allowed_targets.check(),從未收到清冊=一律拒(既有,D12);手打目標限授權網段(D3/D6);Nmap 執行主機與 ZAP 網址解析 IP 也過清冊 B、C
注入指令 工具只接表單定義的參數(param_schema),目標只接 IP/hostname/CIDR 格式驗證,一律 subprocess 傳陣列不經 shell;不提供任何自由命令框 B、C
拿除錯頁做掃描 不包埠範圍、nmap、arp-scan、封包擷取;traceroute 要人勾並確認;單目標序列執行;速率上限 C
Nmap 手動模式被拿來掃任意網段 手動模式與派工同一支 connector:仍只能 SSH 到已設定的執行主機、目標仍過清冊 B
操作沒人知道 所有登入、執行、匯出寫本機稽核並回主系統(FR-157 D8 既有通道);離線累積復線補傳;報告含執行者 A~E
診斷報告外洩內網拓撲 報告下載需 session+範圍;回傳主系統走既有 mTLS 資料面;本機存放在資料目錄 0600 C、D
待傳佇列被塞爆磁碟 佇列只存指標與摘要(報告本體走 upload_files);佇列上限 10,000 筆,超過最舊先丟並寫稽核 D
主系統端被重送轟炸 回報端點冪等(同 uid 同狀態回 200 不重寫);每心跳最多帶 50 筆 D
本機密碼雜湊被拿走 argon2id;存 SQLite 的資料目錄 0600;Agent 本身已有防竄改(FR-064) A

守衛測試(runner 每卡 commit 前跑):① 除錯與獨立掃描的所有入口都呼叫 allowed_targets.check()(grep 斷言);② 沒有任何 shell=True;③ 工具清單裡沒有 nmap/tcpdump/iperf 字樣;④ 回報端點對重複 uid 回 200;⑤ local-admin session 打清冊外目標 403;⑥ 未同步狀態下所有掃描與除錯入口(主系統連線診斷除外)403;⑦ Agent 端 grep 不到任何「新增使用者」路由。

D9 主控台 8444 對誰開(已裁 2026-10-26)

現況綁 0.0.0.0,維持預設不限。安裝時問一次「允許哪些網段連到主控台(留空=不限)」寫本機設定 AGENT_CONSOLE_ALLOW_CIDR,這是底線、未同步時就靠它;主系統 Agent 管理頁可再設,心跳下發後取交集(主系統只能收緊、不能放寬)。逃生門(§13.2 #10、#11):白名單永遠隱含放行 loopback;主系統端擋「與安裝設定無交集」;Agent 收到空交集忽略主系統那份並寫稽核;CLI console-allow-cidr reset。

D10 明確不包的東西(已裁 2026-10-26)

寫進 design 並加守衛測試:任意命令列、SSH 跳轉到別台、埠範圍掃描、nmap 當除錯工具、ARP 掃描、封包擷取(tcpdump:偷聽整條線能看到別人帳密、EDR 必告警、擷取檔是外洩點,業界管理台皆不內建)、頻寬測試(iperf)、從 Agent 刪主系統證據、Agent 端新增使用者。需要封包層級分析時由客戶網管在網路設備端擷取,手冊寫一句。


7. 離線續傳

7.1 現況哪裡會掉

%%{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 M as 主系統
    participant AG as Agent
    participant Q as Agent 待傳佇列(SQLite)
    M->>AG: 心跳回應帶 pending_tasks
    AG->>M: ack(pending→dispatched)
    Note over M,AG: 此時斷線
    AG->>AG: 掃描照跑(執行緒不受影響)
    AG->>AG: 報告存本機 upload_files(本來就在這台)
    AG--xM: POST result 失敗
    AG->>Q: 入列:task_result{uid, status, summary, result_ref}
    AG->>Q: 入列:稽核事件、local_runs 摘要
    Note over M,AG: 主系統畫面:「Agent 離線中,掃描結果等待回傳」
    Note over M,AG: 復線
    AG->>M: 心跳成功
    AG->>Q: 依序取出
    AG->>M: POST result(冪等:已是終態回 200)
    AG->>M: POST local-runs(批次 50 筆)
    AG->>Q: 成功才刪;失敗留著下次再送
    M->>M: 任務→succeeded、證據照既有流程產生
圖 2 — 斷線期間任務怎麼走:執行不停,回報進待傳佇列,復線依序補送

實查結果:

  • 執行不會停:dispatch_tasks 每筆開 daemon thread,與心跳無關;報告 _upload_report 存 Agent 自己的儲存,不經主系統。這兩段已經是對的。
  • 會掉的只有兩處:① _result() 的 POST 失敗只記 log(註解「留待 FR-056.4 觀察」);② 主控台稽核事件走心跳游標,離線期間累積在本機但沒有上限控管。
  • 主系統端:任務停在 dispatched,Agent 在線判定=心跳間隔×3 沒心跳就算離線(is_online)。畫面目前只會看到「執行中」轉圈。

7.2 做法(D7)

  • Agent 待傳佇列:SQLite 新表 outbox(id、kind:task_result/local_run/audit、payload、idempotency_key、attempts、next_retry_at、created_at)。_result() 失敗即入列;心跳成功後 drain() 依 created_at 序送,每次心跳最多 50 筆,失敗退避(1、2、4、8 分鐘,上限 1 小時);送成功才刪,重啟中途可能重送,靠主系統終態冪等吸收。上限 10,000 筆,丟棄順序:先稽核事件、再 local_runs 摘要、任務結果永不丟(一台一天不會超過幾十筆)。
  • 主系統端冪等:實查 agent_task_service.receive_result 只對 cancelled 短路,succeeded 再收一次會再跑 write_success 與 listener(證據再產一次)。改成「任何終態(succeeded/failed/cancelled)=回 200 記 log 不重做」;新端點 POST /agents/local-runs(批次 50 筆)以 (agent_id, local_uid) 去重,agent_id 從通行證取、body 帶的無效。
  • 主系統端顯示:任務卡片與檢測執行頁,當 agent_tasks.status in (dispatched, running) 且該 Agent is_online=false → 顯示「Agent 離線中,結果等待回傳(最後心跳 10/24 14:31)」,不計入失敗、不觸發逾時失敗;Agent 清單加「待傳 N 筆」欄(心跳帶 outbox_size)。
  • 逾時與收尾(D15):實查主系統沒有任何派工逾時機制(dispatched_at 只是記錄欄位,jedi-remote-agent 與 jedi-detection 都沒拿它判過期)。所以離線不會被誤判失敗,但永久離線(機器報廢)的任務會永遠「執行中」無人收尾。補:主系統既有背景排程加一條「dispatched/running 且該 Agent 離線超過 7 天 → failed,原因 agent_unreachable」,system_context("agent_task_offline_reaper") 具名、看到 0 筆不動;Agent 復線後回傳已 failed 的單走終態冪等(記錄不重做)。
  • 離線判定加遲滯(斷斷續續的線路):Agent 端連續 3 次心跳失敗才切「離線」、連續 2 次成功才切「在線」,避免畫面跳動與佇列反覆送一半;佇列每筆獨立冪等,送一半重來不重複。
  • 「連得到但被拒」與「連不到」分開:主系統整台重裝、憑證對不上時心跳是 401/403 不是逾時;總覽與診斷頁顯示「連得到主系統但身分被拒(憑證或註冊問題)」,與「連不到」是兩種狀態、兩種處置。
  • 離線期間任務被主系統取消:Agent 不知道、跑完回傳;主系統收到已取消任務的結果記錄但不產證據、不炸。
  • Agent 重開機(斷電):記憶體 session 全失效、正在跑的掃描中斷——執行紀錄標「中斷」;待傳佇列在 SQLite 不丟;開機後自動重連補傳。
  • 磁碟空間:低於門檻(既有 disk_space.py)拒絕新掃描、總覽紅字;佇列本體只存指標與摘要。

D7 離線續傳機制(已裁 2026-10-26)

Agent 端 outbox+心跳後 drain(丟棄順序任務結果永不丟);主系統端回報端點改任何終態冪等、local-runs 批次端點、已刪除或停用的 Agent 回傳拒收;「Agent 離線等待回傳」狀態顯示;離線判定遲滯。不做:Agent 之間代送、主系統主動連 Agent 拉結果、復線自動推報告副本。

D15 永久離線任務的收尾(已裁 2026-10-26,§13.2 #3)

主系統派工單 Agent 離線超過 7 天自動 failed(原因 agent_unreachable),既有背景排程具名 system_context 跑;7 天內照 D7 等。

7.3 本機狀態放哪(D11 已裁:同一個 SQLite,不起 Postgres)

實查 Agent 本機現在存的東西:檔案索引 upload_files(SQLite 單檔,FR-066 D2 從 Postgres 換來)、工具帳密(Fernet 加密檔)、清冊與工具表單定義(JSON 檔)、主控台稽核(JSONL+游標)、救援碼(一碼一檔)。本案要加:離線操作者名單、local-admin 與離線密碼雜湊、本機執行紀錄 local_runs、待傳佇列 outbox。全部是單機、單進程、幾千筆以內、無並發寫入的資料。

起 Postgres 換到什麼 代價
正規的表與交易 SQLite 單一寫入者下交易語意相同
多進程並發 Agent 沒這需求
「看起來比較正式」 多一個容器要起停、備份、密碼輪換、升級;原生安裝版要 bundle 整套 PG;客戶升級多一個會壞的點;FR-066 D2 拔掉 PG 的理由一條沒變

業界邊緣節點(Nessus scanner、Wazuh agent、FortiGate)都用嵌入式 DB,沒有一家為本機帳號起 Postgres。做法:四張新表進既有的 SQLite 檔;清冊與工具定義的 JSON 順手收進去;稽核 JSONL 與帳密加密檔維持(帳密有獨立金鑰檔的理由,FR-157 D11)。反悔條件:Agent 要跑多 worker 同時寫、或本機資料要給第三方報表工具直接查。

D11 本機狀態存放(已裁 2026-10-26)

不起 Postgres;離線操作者、本機密碼雜湊、執行紀錄、待傳佇列四張表進既有 SQLite 單檔,清冊與工具定義 JSON 順手收進同一檔。


8. 主系統端配合

8.1 多據點怎麼表達(D8 已裁:用 Agent 名稱,不加據點欄)

老闆的情境是「台北主系統,高雄、台南、日本、新加坡各一台」。決策者裁:Agent 名稱本來就是管理員取的,「高雄機房 Agent-01」一看就知道在哪,一台 Agent 通常就是一個點,不另加「據點」欄位。自主執行紀錄與 Agent 清單照 Agent 名稱篩;手冊寫命名建議「地點+用途+編號」。

D8 多據點(已裁 2026-10-26)

不做據點欄位、不做區域實體;靠 Agent 名稱。

8.2 離線操作者授權頁(雛形幕 6)

Agent 管理頁每台 Agent 多一個「離線操作者」設定:勾人(租戶內使用者)、每人選範圍(Agent 設定/Agent 檔案/獨立掃描/網路除錯四個能力點的子集,不能超過這個人在主系統本來就有的能力點)、授權到期日(預設 90 天)。存 remote_agent_offline_operators(agent_id、user_id、scope、expires_at、local_password_set_at、revoked_at)。心跳回應帶給 Agent;撤銷即從名單拿掉,Agent 下次心跳刪快取。

新增兩個能力點:detection.remote-agent.run-local-scan(獨立掃描)、detection.remote-agent.net-diag(網路除錯),照 FR-157 D10 形狀;入場券的 scope 也帶這兩個。

Agent 清單對「離線操作者 0 位」顯示紅字警示(提醒要嘛勾人、要嘛確認現場有 local-admin 密碼);名單內的人若還沒設離線密碼,授權頁標「離線密碼:未設定」;有撤銷但該台離線還沒同步到的,標「有待同步的撤銷」。離職流程手冊寫明:把人從離線操作者移除,離線中的那台等它上線才生效。

D13 安裝與升級流程(已裁 2026-10-26;§13.2 #2 修正升級段)

新裝:install.sh 強制問 local-admin 密碼(12 碼以上),不設不給裝完;安裝完成畫面大字要求交給現場負責人保管。升級:upgrade 可能無人值守,非互動終端([ -t 0 ] 為假)不強制;啟動後主控台總覽紅字「尚未設定本機管理員密碼」、心跳回報 local_admin_set=false 讓主系統清單同樣紅字,在線跳轉進去的 IT 管理員(configure scope)可設;互動終端升級時照新裝問。新增 CLI:reset-admin-password、wipe(先清 SQLite 再刪檔案目錄)、console-allow-cidr reset;re-enroll 既有子命令擴充「清名單、清冊、執行紀錄、outbox,有未回傳先問」。

8.5 重啟 Agent(D14)

兩種部署形態都有外部拉起機制(systemd Restart=on-failure、compose restart: unless-stopped),主控台重啟=Agent 自己優雅結束(取消執行緒、關 DB、exit 0)由外部重拉,不是 Agent 去呼叫 systemctl 或 docker API(要特權、又多一條能被濫用的路)。

入口 情境 連線是誰發的
主系統 Agent 管理頁 → 開啟主控台 → 維運 → 重啟 在線時台北管理員遠端處理 管理員瀏覽器 → Agent(入場券)
直接打 Agent 網址 → 登入 → 維運 → 重啟 離線時現場人員 現場瀏覽器 → Agent
主系統 Agent 清單「要求重啟」→ 心跳帶旗標 → Agent 下次心跳執行 Agent 在線但 8444 被防火牆擋、只有 8443 心跳通 仍是 Agent 來拉,不違反發起方原則

流程:確認框列出「正在跑的掃描 N 個會中斷並標 cancelled;待回傳佇列不受影響;約 10 秒後恢復」→ 寫本機稽核、進 outbox → 回「已送出」→ 前端倒數 15 秒打 /api/me(401)→ 顯示「Agent 已重啟,請重新登入」。旗標分兩種(§13.2 #8):restart_when_idle(預設,等沒任務)、restart_now;主系統按鈕預設前者、勾「立即」才後者。總覽加 uptime_sec 與上次重啟原因(手動/崩潰重拉/主系統要求)。

同區塊:服務狀態(uptime)、下載支援診斷包(Agent 側新做,不是 FR-071 的主系統包:log 尾段、去敏設定、本機儀表板快照、最近診斷結果)。權限:local-admin 與 configure scope。

D14 重啟 Agent(已裁 2026-10-26)

主控台維運區「重啟」按鈕(跳轉與離線登入兩條入口共用)+主系統「要求重啟」心跳旗標(when_idle/now);Agent 優雅結束由 systemd/compose 重拉;加 uptime、支援診斷包。

8.3 自主執行紀錄分頁

Agent 管理頁「自主執行紀錄」:一列一次執行——Agent、種類(掃描/除錯)、工具、目標、執行者、時間、狀態、報告下載(經 Agent 資料面取檔,Agent 離線時標「報告在離線的 Agent 上」)。篩 Agent/工具/人/日期。

8.4 心跳新欄位

Agent → 主系統:outbox_size、local_login_enabled(離線超過 30 天停用時回 false)、local_admin_set、console_allow_cidr_installed(安裝時設的,供主系統驗交集)、uptime_sec、last_restart_reason。主系統 → Agent:offline_operators[](含撤銷→Agent 踢 session)、console_allow_cidr(只能收緊)、restart(when_idle/now/空)。全部照既有 observer 形狀掛進 _heartbeat_loop。


9. 決策彙整

編號 題目 建議 替代 狀態
D1 離線登入模式 三層:安裝時本機管理員 local-admin+主系統授權離線操作者+入場券跳轉;救援碼保留;LDAP 第二期 純本機多帳號 已裁
D2 有效期與鎖定 授權預設 90 天可選;離線 30 天停用離線操作者與獨立掃描;5 次鎖 15 分;密碼 12 碼;local-admin 永不過期 不限期 已裁
D3 獨立掃描目標範圍 仍限授權清冊,離線用快取不放寬;手打限授權網段;八支全開,ZAP 網址解析後過清冊 離線放寬/拔 ZAP 已裁
D4 獨立掃描結果落點 主系統新表 agent_local_runs+管理頁分頁;掛證據第二期 塞 detection_executions 已裁
D5 除錯工具 本機儀表板九卡+十項診斷全進第一版;安靜預設勾,traceroute/持續監測要勾並確認 分期 已裁
D6 除錯目標範圍 清冊+主系統+閘道+DNS;手打限授權網段;未同步只准後三項 自由輸入 已裁
D7 離線續傳 Agent outbox(任務結果永不丟)+心跳 drain;主系統任何終態冪等+「離線等待回傳」顯示+遲滯 主系統主動拉 已裁
D8 多據點 不加據點欄,靠 Agent 名稱;手冊寫命名建議 據點欄/區域實體 已裁
D9 8444 對誰開 安裝時設底線 CIDR、主系統只能收緊取交集;隱含 loopback;交集非空驗證;CLI reset 預設只內網 已裁
D10 明確不包的東西 命令列、SSH 跳轉、埠範圍、nmap 探測、ARP 掃描、tcpdump、iperf、刪證據、Agent 端新增使用者——守衛測試 — 已裁
D11 本機狀態存放 不起 Postgres;四張新表進既有 SQLite 起 Postgres 已裁
D12 未同步狀態 沒收過清冊或名單=只有 local-admin 可登、只准主系統連線診斷 放寬 已裁
D13 安裝與升級 新裝強制設 local-admin 密碼;升級非互動不強制改紅字提醒;加 reset-admin-password/wipe/console-allow-cidr reset;re-enroll 擴充清本機 升級也強制 已裁
D14 重啟 Agent 主控台維運「重啟」(兩入口共用)+主系統心跳旗標 when_idle/now;優雅結束由 systemd/compose 重拉;uptime、支援診斷包 Agent 呼叫 systemctl 已裁
D15 永久離線任務收尾 主系統派工 Agent 離線 7 天自動 failed(agent_unreachable),具名 system_context 排程 永遠等 已裁
D16 Agent 本機 DB 版本機制 最小 schema_version+有序 migration list(§13.4) 每表 checkfirst/alembic 已裁
D17 Agent LDAP 來源 三選一:同步(預設)/自設/不接;名單仍主系統決定 只同步 已裁
D18 LDAP 憑證與加密預設 首次信任釘住、拿掉勾選框、預設 STARTTLS;推翻 CM-2239 維持勾選框 已裁
D19 LDAP 實作怎麼共用 抽 jedi-ldap 零相依套件,jedi-iam 瘦身呼叫 複製一份進 Agent/Agent 裝 jedi-iam 已裁
D20 除錯噪音怎麼算 以這次總連線數計:執行前算給使用者看;超過 10 次或含中高項目跳確認框列原因;埠一次上限 5、連線間隔 1 秒;門檻寫死 只能勾一項執行 已裁
D21 主控台檔案頁權限 維持 FR-157 D7 租戶層(有「Agent 檔案」=看得到這台全部檔案);離線操作者授權頁與手冊寫明這一句;專案層另開 FR 本案提前做專案層 已裁
D22 安裝題目搬進主控台 只搬證據檔儲存位置進「服務與重啟」頁,且主控台只能選內建 SeaweedFS(客戶不能把證據指到別處);自有 SeaweedFS/MinIO/本機磁碟只能用 CLI configure-storage;重新註冊與升級留 CLI 主控台四種型別全開/全搬/不搬 已裁
D23 離線操作者的權限從哪來 跟主系統同一份:離線名單只管「誰能離線登入、到期日」,不另勾範圍;每次心跳依該使用者當下的 Agent 能力點算出範圍下發(restart 照 D14 不下放)。主系統改角色,下一跳名單自動跟著變。主系統沒有任何 Agent 能力點的人不能加入名單(離線進去也什麼都做不了) 名單逐人勾範圍(上限為本人能力點) 已裁

10. 拆棒草案

排序以「決策者何時看得到畫面」為準:先讓登入頁與離線橫幅出現,再長功能。同一批彼此不改同一檔;登記型共用檔(能力點 migration、ROUTE_TABLE、心跳 payload 組裝、FE console/ 路由)分批。

批 棒 做什麼 repo 看得到什麼
0 J1 jedi-remote-agent 套件異動 兩張表(remote_agent_offline_operators、agent_local_runs,RLS)、心跳下發 offline_operators/console_allow_cidr/restart、心跳收 outbox_size/local_admin_set/uptime、local-runs 批次端點、receive_result 終態冪等、三個能力點、離線 7 天 reaper、事件碼。異動前列清單等決策者點頭;dev 走 path dependency jedi-python-package 套件測試過;主專案接線後心跳 payload 多欄位
0 G1 Agent 本機 DB 版本機制 D16:core/local_db/、schema_version、v1=既有 upload_files、v2=四張表 evidence-agent 既有客戶升級後 SQLite 多四張表、資料不丟
1 A1 主系統離線操作者授權(BE) core/plugins/remote_agent.py 接線、能力點 migration、_ADMIN_CAPABILITIES、CIDR 交集驗證 BE curl 能建授權、心跳回應帶名單
1 A1-FE 主系統離線操作者授權頁 新開子元件 OfflineOperatorsDialog.vue,不往 RemoteAgentManage.vue(766 行)加;「0 位」紅字 FE 主站 幕 6 授權表
1 B1 Agent 本機執行紀錄與手動掃描 API local_runs 表用、_run_one 的 ack/result 改可注入 sink、清冊 check、並發上限、取消;新檔 core/console/local_runs.py evidence-agent curl 起一支本機掃描並查進度
1 C1 網路除錯執行器 新套件 core/netdiag/:九卡儀表板、十項診斷、參數驗證、逾時、速率、判讀規則、報告;容器形態偵測 evidence-agent curl 跑 ping+traceroute 拿報告含判讀
2 A2 Agent 登入與本機密碼 名單快取 observer、local-admin/離線密碼 argon2id(單調時鐘鎖定)、POST /console/local-session(四種失敗同一回應)、有效期、離線 30 天停用、未同步狀態、revoke_by_sub、在線提示設密碼、heartbeat_status.last_error_kind;新檔 core/console/local_auth.py evidence-agent curl 離線密碼登入拿 session
2 A2-FE 登入頁與三態 Login.vue 取代 Blocked.vue、phase='login'、三態橫幅、設離線密碼對話框 FE console 直接打網址看到登入頁、離線登得進去
2 B2 掃描工具分頁 views/Scan.vue:工具卡、清冊目標挑選、tool_schemas 表單、進度、取消 FE console 幕 3
2 C2 網路除錯分頁 views/NetDiag.vue:儀表板九卡、目標、勾選與噪音、確認、路徑圖、判讀卡、匯出 FE console 幕 4、幕 7
2 D1 Agent outbox outbox 表、_result 失敗入列、心跳後 drain、退避、丟棄順序、離線判定遲滯 evidence-agent 斷線掃完、復線結果到主系統
2 E2 Agent 維運區 重啟(優雅結束)、心跳旗標 observer、uptime、支援診斷包;新檔 core/console/ops.py evidence-agent curl 重啟後 Agent 回來
3 D2 主系統離線顯示(BE) 任務頁「Agent 離線等待回傳」資料、清單待傳欄、「要求重啟」端點 BE 幕 6 任務橫幅 API
3 D2-FE 主系統離線顯示 任務頁橫幅、清單欄、「要求重啟」按鈕(子元件) FE 主站 幕 6
3 E1 自主執行紀錄(BE) local-runs 接線、報告下載走資料面 JWT、nickname enrich BE API
3 E1-FE 自主執行紀錄分頁 新 view AgentLocalRunsView.vue,篩 Agent/工具/人/日期 FE 主站 幕 6 紀錄表
3 E3 執行紀錄分頁與維運分頁 views/Runs.vue、views/Ops.vue FE console 幕 5、維運區
3 A3 安裝、升級與 CLI 新裝強制問密碼、升級非互動不強制、reset-admin-password/wipe/console-allow-cidr reset、re-enroll 擴充、upgrade 前問執行中任務、CIDR 安裝問句;子命令進 scripts/install/lib/(install.sh 2,800 行) evidence-agent 裝機不設密碼裝不完
4 F1 文件與出貨 SPEC、手冊(獨立作業一章、除錯噪音表、安裝後連不上排查、local-admin 保管)、安裝文件、資安例外登記兩條、守衛測試收口 BE docs+evidence-agent 手冊

批 0 兩張是地基(套件與 DB 機制),批 1 起才能並行;批 1 內 A1/B1/C1 改不同檔;批 2 的 A2 與 D1 都動 enroll.py 的 observer 掛點(登記型)→ A2 先、D1 排批 2 尾或改批 3,開卡時定。

跨 repo 的棒(A1、D2、E1)各拆成「BE+套件」與「FE 主站」兩張卡,一卡一 repo。


11. 風險與陷阱

風險 說明 對策
離線期間撤銷不了 離線操作者模式的本質 有效期+離線 30 天停用;主系統標「有待同步的撤銷」;手冊寫明
local-admin 密碼沒人記得 裝機的人離職 安裝完成畫面要求交給現場負責人;機上 sudo reset-admin-password;主系統「離線操作者 0 位」紅字
清冊內設備換 IP 新 IP 不在清冊掃不了 診斷頁 DNS 項看得出;手冊「到主系統更新清冊」
本機密碼變成第二套密碼 使用者忘了、跨台不同 在線跳轉時可重設;救援碼可重設;多台各設一次已是 FR-157 既定
清冊快取過期 離線一個月後清冊早變了 畫面標清冊版本與日期;離線 30 天停用
除錯工具在某些客戶環境本身就觸發告警 連 ping 都有客戶擋 噪音標示+預設只勾安靜+手冊「先跟網管講」一節
traceroute 需要 raw socket 容器內要 CAP_NET_RAW;原生安裝需 setcap 安裝文件補;沒有權限時該項顯示「此環境不可用」而不是失敗
待傳佇列累積很大 離線一個月的稽核事件 佇列上限、報告本體不進佇列、每心跳 50 筆分批
主系統端回報端點改冪等會影響既有流程 既有「同 uid 第二次回報」行為可能被某處依賴 改之前 grep 所有呼叫端;守衛測試
jedi-remote-agent 異動量大 兩張表、六個心跳欄位、三個端點、一條 reaper 批 0 先做、套件測試齊、主專案接線後實打心跳
資安規則兩條例外(SR-08.2、SR-04.9) reviewer 會標 design 明列例外登記+docs/security-report/requirements/ 補註記
無人值守升級 不能強制互動 D13 分新裝/升級
自簽憑證警告 直接打網址比跳轉更常撞到 FR-156 D9 已列第二期「客戶自備憑證」;本案手冊再提一次
FE console/ 入口長大 現在三個 view,本案加四個 體積上限盯著;共用元件抽出

13. 全案重新檢視:死結、漏掉的情境、既有機制、規範對照

決策者要求在定案前把整案重走一遍:會不會有死結、有沒有情境沒想到、有沒有既有機制沒注意到、有沒有違反開發與資安規範。本節是逐項實查(開檔看程式、不憑印象)後的結果,實查推翻本稿前面說法的地方標 🔁,前面各節已同步改正。

13.1 既有機制盤點:哪些本來就有、本案直接接上

這張表的目的是「不重複造輪子」。每一列都是開檔確認過的。

需求 既有機制(檔案) 本案怎麼用 🔁 本稿前面說錯的
主控台 session core/console/session.py:記憶體 SessionStore、滑動 30 分鐘、secrets.token_urlsafe(32) 離線登入、local-admin 登入發的 session 走同一個 store,不另做 —
路由守門 core/console/routes.py:require_session(scope):無 session 401、scope 不在票上 403 新路由全掛它;新 scope run-local-scan、net-diag 加進 agent_auth.CONSOLE_SCOPES —
本機管理員身分 core/console/local_login.py 已有 LOCAL_ADMIN_SUB="local-admin"、LOCAL_ADMIN_DISPLAY_NAME;_create_local_session 發全 scope session local-admin 不是新身分,是既有救援碼身分加一組密碼;D1 的①沿用這個 sub,密碼登入與救援碼登入進同一個 _create_local_session 前面把 local-admin 寫成「新增帳號」,實際是既有 sub 加密碼路徑
CSP 與安全標頭 routes.py 頁面帶 nonce CSP、API 帶 nosniff 登入頁、新分頁照用,不另設 —
本機稽核與回傳 core/console/audit.py:JSONL、游標嚴格遞增、REPORTED_ACTIONS 白名單、_REPORT_KEYS 欄位白名單、10MB 輪替;core/console/heartbeat.py:_console_events() 隨心跳帶、ack 游標 新動作(local_login、local_scan_started、net_diag_run、restart_requested、report_exported)加進 REPORTED_ACTIONS;主系統端對應事件碼加進 jedi-remote-agent 的事件表 —
授權目標清冊 core/allowed_targets.py:心跳下發、持久化 allowed_targets.json、check(params) 從 params 的 hosts/targets 等欄位抽目標比對、從未收到=一律拒(degraded_reason=cloud_too_old);主系統端 core/plugins/remote_agent.py:AllowedTargetsProvider 實作 IAllowedTargetsProvider 獨立掃描與除錯都呼叫同一支 check();未同步狀態就是 received=False 🔁 Nmap 執行主機已經過清冊:task_executor._run_one 在呼叫 connector 前對 params 整包 check(),Nmap 的 params.hosts(執行主機)本來就在被比對的欄位裡。本稿 §4.3 說「執行主機不過清冊」是錯的,已改
ZAP 網址 allowed_targets.py:30 檔頭明寫「ZAP 的網址不在授權範圍」,_target_networks 會 urlsplit 取 hostname 但 ZAP 的欄位不在 _CONNECT_FIELDS 這是既有的刻意缺口(FR-129 當時裁網址型目標不管)。本案手動模式若開放 ZAP,等於開一條「對任意網址發掃描」的路 → D3 補一條:手動模式第一版不開 ZAP,等 FR-129 把網址型目標納清冊再開 前面說「ZAP 網址解析後 IP 也過清冊」是要改 allowed_targets.py 的既有決策,屬 FR-129 範圍,不在本案偷改
任務執行骨架 core/task_executor.py:_run_one:ack → 清冊 check → 帳密解析 → connector.run → 上傳報告 → result;_RUNNING_TASKS 取消登記 手動執行造一張本機任務走同一條;_ack/_result 改成可注入的 sink(雲端 sink/本機 sink),不複製整支 —
心跳迴圈 core/enroll.py:_heartbeat_loop:每跳依序 skew_monitor、allowed_targets、error_tracking、vulndb_sync、tool_schemas、console_heartbeat.observe(data)、dispatch_tasks;失敗只 log、磁碟檢查不依賴雲端 新增 offline_operators.observe(data)、outbox.drain()(成功後)、restart_flag.observe(data) 三個 observer,形狀照既有 —
心跳失敗判定 同上:401/404 自動重註冊、403 不重註冊、其他只 warning 「連得到但被拒」已經分得出(401/403/404 各走不同分支),本案只是把這個狀態露到主控台總覽(heartbeat_status 加 last_error_kind) —
時鐘偏移、磁碟 core/clock_skew.py、core/disk_space.py:DiskSpaceMonitor 本機儀表板直接讀;磁碟門檻沿用 AGENT_DISK_MIN_FREE_PERCENT —
雲端 TLS 診斷 core/cloud_trust.py:describe_tls_failure 已會把 TLS 失敗翻成白話提示 主系統連線診斷的 TLS 那一步直接用它 —
本機帳密 core/credential_store.py(Fernet、secrets/secrets.key 0600) 手動掃描帳密從這裡拿,與派工同一份(credential_resolver) —
工具表單定義 core/tool_schemas.py 心跳下發快取 tool-schemas.json 掃描工具頁的參數表單從這裡長 —
檔案索引 Agent SQLite upload_files(core/app_factory.py 以 checkfirst=True 自建單表) 報告本體照走 file_upload_service,local_runs.report_file_uid 指向它 🔁 Agent 沒有 migration 機制(FR-066 D7 明寫「無 DB migration 負擔」);本案加四張表要補一個最小的「建表+版本欄」機制,見 13.4
維運 CLI install.sh 子命令:status/start/stop/restart/logs/fingerprint/console-login/configure-storage/re-enroll/upgrade/uninstall reset-admin-password 與 wipe 加進同一個 case;re-enroll 已存在,重註冊清本機狀態的鉤子掛在它裡面 前面寫「重註冊時清本機狀態」沒指出掛點,實際是擴充 cmd_re_enroll
入場券 jedi-remote-agent agent_ticket_service(purpose=console_login、scope、jti 一次性) 兩個新 scope 走同一張票 —
主系統能力點 FR-132 已有 detection.remote-agent.{read,create,update,delete},FR-157 加 configure、browse-files 加 run-local-scan、net-diag、restart,照 FR-157 A1 的 migration 形狀 —
LDAP 登入 jedi-iam ldap_adapter.py(295 行):兩段式 bind、AD NTLM、跳脫、TLS 驗證;主系統登入頁有密碼/LDAP 切換;LDAP 設定頁 LdapConfigForm.vue;帳號要先由管理員「綁定」到 LDAP 身分 目錄連線段抽成 jedi-ldap(D19),Agent 與 jedi-iam 共用 🔁 不能直接用:adapter 同時繼承 BaseRepository(查 user_auth_providers/users 表),Agent 沒這兩張表;整支 jedi-iam 拖 Flask-RESTful 全套且釘 jedi-common>=1.3.4 與 Agent 的 0.0.31 衝突
主系統 Agent 管理頁 FE src/views/remote-agent/RemoteAgentManage.vue 766 行 🔴 已逼近 800 行上限:離線操作者授權、自主執行紀錄一律新開子元件或新 view,不往裡加 前面拆棒沒提這件事
主控台 FE compliance-manager-fe/console/:App.vue 用 phase(loading/blocked/ok)切頁、api.js 401 觸發 onSessionLost、Blocked.vue 登入頁取代 Blocked.vue 成為 phase='login';api.js 不動 —
支援診斷包 FR-071 是主系統端的診斷包,Agent 沒有對應物 本案「下載支援診斷包」是 Agent 側新做,不能說沿用 FR-071;第一版內容限 Agent log 尾段、去敏設定、本機儀表板、最近診斷結果 前面說「FR-071 有腳本」誤導
主系統端派工逾時 🔁 沒有。agent_tasks.dispatched_at 只是記錄欄位,jedi-remote-agent 與 jedi-detection 都沒有任何以它判過期的程式;source_scans 另有 expire_stale_uploads 但那是等上傳的狀態 離線續傳不需要「暫停逾時」(本來就沒有逾時);但反過來,永遠回不來的任務現在無人收尾,本案要補一條(13.2 死結 3) 前面寫「離線期間暫停逾時」是對不存在的機制做假設
結果回報冪等 agent_task_service.receive_result:歸屬比對 → 已 cancelled 短路回 200 → 其他狀態直接寫 🔁 只對 cancelled 冪等。succeeded 任務再收一次 succeeded 會再走一次 write_success 與 listener(證據再產一次)。本案要把短路條件擴成「任何終態=回 200 不重做」 前面寫「冪等端點」當成已有,實際要改

13.2 死結推演

逐條想「這個情境會不會卡住、誰來解」。

# 情境 會不會死結 解法(已寫進對應 D 項)
1 剛裝好就斷線,沒人設過離線密碼 不會:local-admin 安裝時強制設(D13) 若裝機的人也忘了:機上 sudo reset-admin-password;再沒有=沒有機器管理權,本來就不該進得去
2 既有客戶升級,升級腳本要求設 local-admin,但升級是無人值守的(upgrade 子命令被排程跑) 會:強制互動會讓無人值守升級卡住 D13 修正:升級時若非互動終端([ -t 0 ] 為假)不強制,改為啟動後主控台總覽紅字「尚未設定本機管理員密碼」、心跳回報 local_admin_set=false 讓主系統清單也紅字;在線跳轉進去的人可設。只有「新裝」才強制
3 主系統派工後 Agent 永久離線(機器報廢),任務永遠 dispatched 會:主系統沒有逾時機制,任務永遠「執行中」 新增 D15:主系統派工單加「Agent 離線超過 N 天(預設 7)自動標 failed,原因 agent_unreachable」,由既有背景排程跑;Agent 復線後回傳已 failed 的單走 13.1 的終態冪等(記錄但不重做)。這與 D7「離線不算失敗」不衝突:離線 7 天內等,超過才收尾
4 離線操作者名單從未同步(未同步狀態),local-admin 登入後要「設離線密碼」給別人 不會:D1 明定 Agent 端沒有新增使用者;local-admin 只能改自己的密碼 —
5 離線 30 天停用掃描後,主系統派的任務復線領到 不會:D2 明定主系統派的照跑(停用的是手動) —
6 Agent 重啟中(維運按重啟)同時心跳正在 drain outbox 可能重送:drain 送出去、主系統收到、Agent 退出前沒來得及刪佇列 → 重啟後再送一次 outbox 每筆帶 idempotency_key,主系統端終態冪等(13.1);Agent 端 drain 採「送成功才刪」本就允許重送,靠冪等吸收
7 重啟按鈕按下、session 失效、使用者不知道有沒有重啟成功 不會死結但體驗差 前端倒數 15 秒後打 /console/api/me(會 401)→ 顯示「Agent 已重啟,請重新登入」;總覽加 uptime_sec,重登後看得到剛重啟
8 主系統經心跳下「請重啟」旗標,但 Agent 正在跑一個 4 小時的 OpenVAS 會中斷任務 旗標分兩種:restart_when_idle(預設,等沒任務再重啟)、restart_now;主系統按鈕預設前者,勾「立即」才後者;中斷的任務走既有 cancel 流程標 cancelled,不是 failed
9 離線期間,離線操作者在主系統被撤銷,同時他正在 Agent 上跑掃描 不會死結:session 到 30 分鐘過期;撤銷同步到 Agent 時 SessionStore 把該 sub 的 session 全部 revoke(要加 revoke_by_sub) —
10 8444 CIDR 白名單設錯把自己鎖在外面 會:連 local-admin 都連不到 機上 CLI console-login 印的網址不受 CIDR 限制嗎?不對,CIDR 是連線層擋。解法:CIDR 白名單永遠隱含放行 loopback,手冊寫「鎖住時 SSH 到機器用 curl 或 port-forward 從 127.0.0.1 進」;另加 CLI console-allow-cidr reset
11 主系統 CIDR 與安裝時 CIDR 取交集後為空 會:誰都連不到 主系統端下發前驗證交集非空(Agent 心跳回報安裝時 CIDR,主系統 UI 擋「與安裝設定無交集」);Agent 端收到空交集時忽略主系統那份並寫稽核
12 outbox 滿 10,000 筆最舊先丟,丟掉的是任務結果 會丟資料 丟棄順序改為:先丟稽核事件、再丟 local_runs 摘要、任務結果永不丟(任務結果一台 Agent 一天不會超過幾十筆,不可能撐滿)
13 離線操作者離線密碼忘了,Agent 又離線 不會死結:local-admin 可登;但 local-admin 不能幫他重設(只能改自己的) 可接受:他等復線後從主系統跳轉進來重設。手冊寫明
14 兩個人同時用 local-admin 登入 不會:session 各自一個;稽核分不出誰(已知限制) —
15 心跳帶回 offline_operators 名單時 Agent 磁碟滿、SQLite 寫不進 不會卡心跳(observer 照既有「拿不到只略過」原則),但名單停在舊版 總覽磁碟紅字已涵蓋;手冊
16 未同步狀態,local-admin 跑「主系統連線診斷」要對主系統網址 DNS 解析,但 Agent 連 DNS 都沒設 不會死結:診斷結果就是「DNS 未設定」,這正是它要回答的 —
17 手動掃描跑到一半 Agent 升級(current symlink 切換、重啟) 任務中斷 upgrade 子命令已有停服務步驟;本案在 cmd_upgrade 前加「有執行中任務先問」(非互動時直接中斷並標 cancelled)
18 主系統與 Agent 對「離線」的判定不一致(主系統 ×3 心跳、Agent 連續 3 次失敗) 不會死結,但畫面可能一邊說在線一邊說離線 兩邊都是「3 個心跳間隔」,只差相位;可接受,手冊說明

13.3 前面沒想到的情境(本輪新增)

情境 影響 併入
無人值守升級不能強制互動 D13 要分新裝/升級 D13 修正(13.2 #2)
主系統從未有逾時,永久離線的 Agent 讓任務永遠執行中 要補收尾機制 新 D15
結果回報只對 cancelled 冪等 重送會重做證據 D7 修正:終態冪等
手動模式開 ZAP 等於繞過清冊 清冊設計刻意不管網址 D3:把網址型目標納清冊(hostname 解析成 IP 比對),八支全開
Agent 沒有 migration 機制 四張新表怎麼進既有客戶的 SQLite 新 D16(13.4)
RemoteAgentManage.vue 766 行 再加就破 800 拆棒 E1/A1 明定新開子元件
重啟與長任務衝突 要分 when_idle/now D14 修正
CIDR 把自己鎖外面 要有逃生門 D9 修正:隱含 loopback+CLI reset+交集非空驗證
outbox 丟棄順序 不能丟任務結果 D7 修正
撤銷要踢掉現有 session SessionStore 加 revoke_by_sub A2
local-admin 密碼也要能從主系統跳轉時改 在線時 IT 管理員進主控台可改 local-admin 密碼(要 configure scope) A2
診斷報告含內網拓撲、回傳主系統後誰能看 自主執行紀錄分頁的報告下載要過 net-diag 或 read 能力點;報告本體在 Agent,下載走資料面 JWT(既有 data_plane_auth) E1
手動掃描的帳密在 Agent 本機,credential_store 解不開(金鑰壞)時 既有 _configured_tools 已處理成「全部未設定」;手動掃描頁照此顯示「帳密儲存損壞,請重設」 B2
Agent 時鐘偏移大,local-admin 密碼的鎖定計時與授權到期判定都會歪 鎖定用單調時鐘(time.monotonic);到期日判定用系統時鐘但總覽紅字提醒偏移 A2
同一瀏覽器同時開主系統跳轉 session 與離線密碼 session 同一個 cookie 名,後者覆蓋前者 可接受;登入頁若已有有效 session 直接進總覽

13.4 Agent 本機 DB 的版本機制(新 D16)

現況 app_factory.py 只對 upload_files 一張表 create(checkfirst=True),再加 _heal_upload_files_columns 補欄位——這是「沒有 migration 的 migration」。本案四張新表若也照這個做法,每張表各寫一支 heal,下一個 FR 再加就第三套。

選項 做法 優 劣
a. 最小版本表(建議) SQLite 內加 schema_version(version INTEGER) 一列;core/local_db/migrations.py 一個有序 list,每項 (version, sql);啟動時從當前版本往後套、每步一個交易;首版把既有 upload_files 當 v1、本案四張表 v2 之後任何 FR 加表只加一項;與主系統 schema_migrations 概念對齊但不共用(主系統那套是 PostgreSQL) 要寫一次
b. 每表各自 checkfirst 照現況 零新機制 加欄位要各寫 heal;沒有版本概念,回滾不可能
c. 引入 alembic 正規 重;Nuitka 打包多一個套件;單檔 SQLite 不值得

D16 Agent 本機 DB 版本機制(已裁 2026-10-26)

a:最小 schema_version+有序 migration list,30 行;本案四張表當 v2;既有 _heal_upload_files_columns 是 v1 的一部分不動。決策者問「不留技術債選哪種」——不是 alembic(Nuitka 多帶套件、單檔用不到),是把現況的「每表各自補丁」收斂成一張清單。

13.5 開發規範對照

規範 本案怎麼守 要注意的點
DDD 分層(主系統端) 新表 agent_local_runs、remote_agent_offline_operators 走 jedi-remote-agent 套件(domain entity/repo interface/infra impl/app service),主專案只在 core/plugins/remote_agent.py 接線;route 不碰 session 兩張表都有 tenant_id → RLS 四種指令(13.6)
套件異動規範 jedi-remote-agent 要加:兩張表、offline_operators 心跳下發、local-runs 批次端點、restart 旗標、終態冪等、離線逾時收尾。異動前列清單等決策者點頭;dev 走 poetry path dependency,發版等令 這是本案最大的套件異動,拆棒時 jedi 的卡要先行
檔案體積 RemoteAgentManage.vue 766、task_executor.py 300 餘、routes.py(console)200 餘、install.sh 2,800 餘 FE 新開子元件;Agent 新路由進 core/console/ 新檔(local_auth.py、local_runs.py、netdiag.py、ops.py);install.sh 已嚴重超標,新子命令進 scripts/install/lib/(若無則本案開第一支)並在 case 一行呼叫
下拉值進 system_menus 本案沒有新下拉(據點已裁不做;噪音等級是程式列舉、屬例外) —
新 env var 先查既有 AGENT_CONSOLE_ALLOW_CIDR 不存在,但命名照既有 AGENT_CONSOLE_ASSETS_DIR 族;AGENT_DISK_MIN_FREE_PERCENT 沿用 .env sample 與 deployment-env.md(agent 版)同步補
註解只寫為什麼與陷阱 本案新檔案多,runner 要守 reviewer 掃
測試政策 本案改到 common/authz/(能力點)、migration、jedi 套件 → 當卡就跑 reviewer;守衛測試 7 條(§6.2)屬「核心共用邏輯」要寫 突變測試紀律適用
審計欄位 agent_local_runs.started_by 是主系統帳號 login_name → 回應要 enrich nickname(enrich_audit_nickname_objs) E1
錯誤碼 Agent 端 common/code/agent_error_code.py 族(AGENT_CONSOLE_4xxNNN);主系統端 jedi-remote-agent 的 error code 檔;FE error-code.json 同步 登記型共用檔分批

13.6 資安規範對照(.claude/rules/security/ 八支逐條)

規則 SR 本案落點 怎麼守
不需登入的入口回應不帶祕密、成敗一致 SR-01.1 Agent 登入頁 POST /console/local-session 帳號不存在、密碼錯、被鎖、到期四種都回同一個 401 同一個 body;鎖定狀態不在回應透露,只進稽核
身分檢查前只做便宜的事 SR-04.1 同上 body 長度上限 1KB 在 before_request 擋;argon2 驗證前先查帳號在不在名單(在記憶體)
機器身分只認簽章、回報只動自己名下的單 SR-01.6、01.7 POST /agents/local-runs 批次端點、重啟旗標 走既有 AUTH_AGENT 通行證;local_runs.agent_id 從證取,body 帶的無效;重複 (agent_id, local_uid) 去重
有能力點的功能 route 掛 @require_capability SR-02.6 主系統三個新端點(授權頁 CRUD、自主執行紀錄、要求重啟) _ADMIN_CAPABILITIES 登記;FE v-can
帶資源編號先 resolve 再查關係 SR-02.4 授權頁對 agent_uid、使用者 user_id agent 屬本租戶、user 屬本租戶、scope 不超過該 user 既有能力點(三個都核)
新表掛 RLS 四種指令 SR-02.1 agent_local_runs、remote_agent_offline_operators FOR ALL 一條;test_rls_all_tenant_tables.py 自動管
整合憑證加密、讀取只回已設定 SR-06.1 離線密碼、local-admin 密碼 argon2id 雜湊(不是加密,更嚴);任何 API 不回雜湊;主系統端只存「已設定/未設定」布林
祕密寫出去那一刻就遮 SR-06.2 登入請求 log mark_password 接 Agent 的 request log;密碼欄位名進遮罩清單
解密後帳密只留記憶體 SR-06.4 手動掃描帶帳密給 connector 走既有 credential_resolver,不進 local_runs.params
對外呼叫帶逾時 SR-07.3 除錯十項工具、主系統連線診斷 每項固定逾時(ping 10s、traceroute 60s、埠 3s、HTTP 10s、TLS 10s、SSH 10s、持續監測上限 60s);總逾時 120s
代連目標來自已登記資料、不收自由文字 FR-129 D7 除錯目標、手動掃描目標 API 收 device_uid 或 target 字串,後者必須過 allowed_targets.is_allowed();主系統網址/閘道/DNS 三個固定例外由 Agent 自己算、不收 body;守衛 test_no_freeform_connect_target.py 的 Agent 版
目標由呼叫者填的動作:限量、回應只回成敗、稽核記完整目標 SR-08.2、09.1 🔴 與本案衝突:除錯頁要逐項回結果(通不通、哪一跳斷),不是只回成敗 判斷:SR-08.2 防的是「拿本系統當內網掃描器」,前提是目標自由填。本案目標限清冊+三例外、單目標序列、速率上限,回詳細結果是功能本身。寫進 design「資安規則例外登記」並在 docs/security-report/requirements/ 補一條 SR 註記;reviewer 看到要能查到理由
錯誤回應只回代碼 SR-05.2 全部新端點 ErrorCode 族
操作日誌是旁路 SR-08.1 audit.record 已是「寫不進只 log」 —
外部回來的一切是外部輸入 SR-04.6 traceroute/ping 的 stdout 解析、local-runs 批次 body 長度上限、固定格式 regex 解析、不 eval;批次 body 50 筆上限、每筆欄位白名單 schema
交給外部程式執行先看內容、subprocess 不經 shell SR-04.8、04.10 ping/traceroute 呼叫 參數陣列、目標先過 ipaddress/hostname regex、shell=False;守衛 grep
回應 schema 白名單 SR-05.2 local_runs 回主系統、自主執行紀錄回 FE 逐欄明列
使用者輸入在出口跳脫 SR-05.1 診斷報告匯出 PDF/文字含目標名、執行者 PDF 走既有 WeasyPrint 路徑 html.escape;文字匯出無格式
推到第三方最小化 SR-05.4 不適用(本案不推外部) —
socket 不適用 — —
TLS 驗憑證、不得關驗證 SR-03.2 除錯的 TLS 檢查刻意要連自簽憑證的目標(它就是要報告「憑證驗不過」) 用 ssl.create_default_context() 連,捕捉 SSLCertVerificationError 當結果而不是關驗證;程式裡不得出現 CERT_NONE;守衛 grep
可選加密預設加密 SR-03.3 不適用 —
伺服器端抓外部網址走 safe_http_fetch SR-04.9 HTTP(S) 回應檢查 🔴 衝突:safe_http_fetch 拒內網,而除錯對象就是內網。判斷:它防的是 SSRF(騙伺服器連攻擊者指定位址),本案目標已過清冊。用 httpx 直連但套同樣的「不跟隨跨 host 轉址、最多 5 跳、大小上限、只取標頭」;例外登記同上
檔案存取先問歸屬 SR-02.8 診斷報告與手動掃描報告下載 Agent 端過 session scope;主系統端過能力點+agent_local_runs 歸屬
背景工作入列前守門 SR-02.9 離線逾時收尾排程(D15) system_context("agent_task_offline_reaper") 具名;看到 0 筆不動
刪除先刪紀錄 SR-08.3 wipe CLI 先清 SQLite 再刪檔案目錄
解析前先量大小 SR-04.2 local-runs 批次、診斷 stdout 上限常數放 Agent common/

兩條例外登記(SR-08.2 逐項回結果、SR-04.9 內網直連)是本案資安面最需要決策者知道的:規則寫給「主系統代人連任意目標」的情境,本案「Agent 對清冊內目標做診斷」是功能本體。例外不是繞過,是把「目標受限」這個前提寫清楚讓規則成立。

決策者問「變例外會不會造成漏洞」——不會,前提是三個條件都守住(已裁照此登記):①目標只能是清冊內設備+主系統+閘道+DNS(探得到的範圍=主系統本來就授權 Agent 掃的範圍,沒有新增可達性);②單目標序列、每分鐘 3 輪(掃 42 台要 14 分鐘以上,不是掃描器的形狀);③結果寫稽核含完整目標。對比:主系統派 Nmap 回來的報告本來就逐台列埠與服務,比除錯頁詳細得多,而那是正常功能,因為目標過清冊;除錯頁的風險上限不會超過它。唯一新增的是三個固定例外,而那是 Agent 本來天天在連的對象。守衛測試「local-admin 打清冊外目標必須 403」就是確保前提永遠成立,它被改掉例外就失效,寫進登記。

13.8 既有 LDAP 功能的缺漏(決策者要求順便檢查)

登入本體做對三件事(資安連線分析 C11 已驗):OpenLDAP 兩段式 bind(先用服務帳號查 DN,再拿 DN+使用者密碼真的登一次目錄;AD 走 NTLM 一步到位);帳號欄特殊字元跳脫;加密連線可驗憑證可貼 CA。重要設計特性:LDAP 使用者不自動建帳號,要先有本地帳號再由管理員綁定(LOGIN_LDAP_ACCOUNT_NOT_BIND)——對 Agent 是好消息,「誰能用 LDAP 登」仍是主系統的使用者表。

# 缺口 嚴重度 現況(實查) 本案落點
1 LDAP 登入失敗不計鎖定 🟠 高 本地密碼錯 3 次鎖 15 分;LDAP adapter 第二段 bind 失敗直接拋 401,不碰 login_failed_count。拿登入頁對客戶目錄猜密碼沒上限,還可能把員工 AD 帳號反鎖 P0-2
2 加密方式預設「無」 🟠 高 設定頁下拉預設空值=明文 389;選「無」連警告都不記 P0-1(套件預設 STARTTLS)、P0-7(FE)
3 驗憑證預設不勾(CM-2239) 🟡 中 前端新建 verify_cert: false,新客戶接上去就是不驗 D18 釘住,P0-1/P0-2/P0-7
4 服務帳號密碼明文落庫 🟡 中 THIRD_PARTY_LOGIN 的 secret 讀取有遮、落庫明文 P0-3
5 測試連線可帶任意主機 🟡 中 POST /ldap/connect-test 帶畫面設定連任何主機,沒有「換主機要重填密碼」 P0-2
6 AD 停用不同步 🟡 中 綁定後不再問目錄「這個人還在嗎」;AD 停用但本地帳號還在且走密碼登入照樣進;走 LDAP 登入會被目錄擋(這點對) 手冊寫離職流程;Agent 離線時更問不到,故「到期日+離線 30 天停用」在 LDAP 模式仍保留

13.7 本輪修正彙整(對前面各節的影響)

項 修正 已改到哪一節
D3 八支全開,ZAP 網址型目標納清冊 §4.3、§9
D7 冪等擴成「任何終態」;outbox 丟棄順序任務結果永不丟;刪「暫停逾時」 §7.2、§9
D9 隱含放行 loopback;交集非空驗證;CLI console-allow-cidr reset §6.2、§9
D13 升級非互動不強制、改紅字提醒;新裝才強制 §8.2、§9
D14 重啟分 when_idle/now §8.5、§9
D15 新 主系統派工離線 7 天自動收尾 §7.2、§9
D16 新 Agent 本機 DB 版本機制 13.4、§9
§4.3 Nmap 執行主機已過清冊(刪錯誤敘述) §4.3
拆棒 FE 主站新開子元件;Agent 新路由新檔;install.sh 子命令進 lib;jedi 卡先行 §10

14. 全量工作清單(最嚴謹版,給 PM 分階段與剔除用)

決策者要求:「先用最嚴謹的設計模式跟能力來看有多少工要做,再來跟 PM 討論有哪些可以分階段或剔除。切階段、切內容要分清楚,不能因為戰線太長造成東西沒辦法銜接。」本節是這張底表。每一工項都寫到「砍掉會斷什麼」,PM 砍的時候知道後果。

14.1 四個原則(切階段的判準)

  1. 每個階段結束都是可出貨狀態:階段 N 做完,客戶升級不會壞、沒做的功能只是「看不到」,不是「按了壞掉」。這是戰線拉長也不會斷的保證。
  2. 地基先、畫面後,但地基只做該階段畫面需要的部分:不為第三階段的畫面先鋪第一階段的表。
  3. 登記型共用檔分批(能力點 contract、事件碼、心跳 payload 組裝、FE 路由、Agent observer 掛點、SQLite 版本清單),同批兩張卡動同一個登記檔必衝突。
  4. 套件先於宿主,宿主先於畫面:jedi-* 改了要發版(或 dev 走 path dependency)宿主才接得上;宿主端點好了 FE 才有東西打。

14.2 五個階段

階段 做什麼 做完之後客戶多了什麼 停在這裡不往下做,會怎樣
P0 底層先改好:LDAP 連線的程式抽成獨立套件、Agent 本機資料庫加版本管理、主系統加兩張新表和心跳要帶的欄位、補主系統 LDAP 登入的三個資安漏洞 使用者看不到新東西。主系統的 LDAP 登入會變嚴:連錯會鎖、憑證換了會擋 不會壞。升級後跟現在一樣
P1 登入:Agent 有登入頁、三種身分、主系統可以勾誰能離線登入、安裝時設本機管理員密碼、限制哪些網段能連主控台 直接打 Agent 網址就能登入。管理員在主系統勾離線操作者 登進去只有原本的三頁(總覽、檢測工具、檔案)。等於多了一個登入方式,其他沒變
P2 離線能做事:在 Agent 上手動掃描、網路除錯、執行紀錄、重啟、儲存設定 主系統連不到也能掃、能查線路、能重啟 掃出來的結果只在那台 Agent 上,台北看不到
P3 把結果送回主系統:斷線時先存著、連回自動補送,主系統多一頁看各區的手動執行,任務頁顯示「等 Agent 回傳」,離線超過 7 天的任務自動收掉,主系統可以要求 Agent 重啟 連回主系統後結果自動補上。台北看得到高雄、台南、新加坡各自做了什麼。任務不會永遠卡在執行中 不會壞。結果留在 Agent 上,想看要到現場
P4 Agent 接客戶的 AD、手冊、規格文件、守衛測試、資安例外登記 分公司可以用自己的 AD 帳號登 Agent 不會壞。離線密碼和本機管理員照用

哪些能砍:P0 不能砍,砍了後面每張卡都要往已經太大的檔案裡硬塞。P1 是老闆要的最低限度。P2 的掃描、除錯、重啟三塊可以各砍各的。P3 可以整段延後,但「離線 7 天自動收掉任務」建議留,它修的是現在就有的問題。P4 的 AD 可以延後,文件不能砍。

14.3 全量工項表

欄位:ID(階段-序號)/做什麼/repo/改哪些既有檔(行數=現況,超過 800 標 🔴)/新開哪些檔/依賴/可砍?砍了斷什麼。

P0 地基(7 項)

ID 做什麼 repo 改既有檔 新開檔 依賴 可砍?
P0-1 抽 jedi-ldap:從 ldap_adapter.py(295 行)抽目錄連線段成零相依套件(只靠 ldap3):LdapDirectory(config)、verify_user()、test_connect()、DirectoryUser DTO、LdapConfig dataclass;加首次信任釘住(指紋存呼叫端給的 store,套件只算與比對);加密預設 STARTTLS、退 389 要明示;失敗回傳結構化原因(連不到/憑證變了/密碼錯/帳號格式),不丟 Flask 例外 jedi-python-package(新) — 整支新套件+tests 無 ❌ 不可砍。砍了 Agent 接 LDAP 只能複製一份(兩套真相)或裝整支 jedi-iam(版本線衝突)
P0-2 jedi-iam 瘦身:LdapAdapter 改呼叫 jedi-ldap,只留帳號綁定段(~30 行);失敗計數補 LDAP 路徑(C11 D5);LoginConfigDTO.LDAP_VERIFY_CERT 退役改釘住指紋欄 LDAP_TRUSTED_FINGERPRINT;/ldap/connect-test 換主機要重填密碼(SR-06.3) jedi-iam ldap_adapter.py 295、login_config_assembler.py 111、login_service.py 147、user_auth_provider_service.py 199、login_error_code.py — P0-1 ❌ 不可砍(P0-1 沒接上就是兩份)
P0-3 主系統 LDAP 設定存釘住指紋:system-core 的 THIRD_PARTY_LOGIN 設定組加 trusted_fingerprint、trusted_at;服務帳號密碼改 Fernet(C11 「明文落庫」) jedi-system-core+BE migration system-core 設定 schema、scripts/sql/ 一支 — P0-2 ⚠️ 指紋欄不可砍(P0-2 要存);Fernet 可延後但建議一起
P0-4 Agent 本機 DB 版本機制(D16):core/local_db/:schema_version 表、有序 migration list、啟動套用;v1=既有 upload_files(含 _heal_upload_files_columns)、v2=outbox、local_runs、offline_operators、local_credentials(local-admin/離線密碼雜湊/LDAP 服務帳密指標) evidence-agent core/app_factory.py 208 core/local_db/__init__.py、migrations.py 無 ❌ 不可砍(四張表沒地方放)
P0-5 jedi-remote-agent 表與 port:remote_agent_offline_operators(agent_id、user_id、scope JSONB、expires_at、local_password_set_at、revoked_at;RLS)、agent_local_runs(agent_id、local_uid、kind、tool、targets JSONB、started_by、status、summary JSONB、report_ref、started_at、finished_at;RLS;唯一 (agent_id, local_uid));remote_agents 加 console_allow_cidr、outbox_size、local_admin_set、uptime_sec、restart_requested、ldap_source;三個能力點進 plugin/contract.py(run-local-scan、net-diag、restart);domain entity/repo interface/infra impl/app service 照套件形狀 jedi-remote-agent plugin/contract.py、infra/remote_agent/model/remote_agent.py 87、remote_agent_domain_service.py 171、套件 migration domain/offline_operator/*、domain/local_run/*、infra/.../offline_operator*、infra/.../local_run*、app/service/offline_operator_service.py、app/service/local_run_service.py 無 ❌ 不可砍(P1 授權頁、P3 紀錄頁都要)
P0-6 jedi-remote-agent 心跳契約擴充:回應多 offline_operators[]、console_allow_cidr、restart、ldap_config(三選一同步時);收 outbox_size、local_admin_set、uptime_sec、last_restart_reason、console_allow_cidr_installed、sync_state、ldap_source;照 heartbeat_console.py 的白名單收法;receive_result 改任何終態冪等;新端點 POST /agents/local-runs(批次 50、(agent_id, local_uid) 去重、agent_id 從通行證取);POST /remote-agents/{uid}/restart(AUTH_ADMIN);離線 7 天 reaper run_daily_tick();主控台稽核事件碼加 local_login、local_scan_started、net_diag_run、restart_requested、report_exported jedi-remote-agent agent_enrollment_service.py 608、agent_task_service.py 170、api/routing.py 142、heartbeat_console.py app/service/heartbeat_offline.py(新欄位收發)、app/service/agent_offline_reaper.py、api/routes/local_run_route.py、api/routes/agent_restart_route.py P0-5 ⚠️ 可拆:ldap_config 下發與 restart 可延到 P4/P2;終態冪等與 reaper 建議留(修既有缺口)
P0-7 三個 LDAP 洞的 FE 面:LdapConfigForm.vue(279 行)拿掉「驗證憑證」勾選、加「已信任憑證 xxx(到期)」顯示與「重新信任」按鈕、加密下拉預設 STARTTLS 並把「無」移末加「僅限測試」;錯誤碼 error-code.json 同步 FE 主站 LdapConfigForm.vue 279、error-code.json、api.js — P0-2 ⚠️ 可延後,但主系統 LDAP 設定頁與套件行為會不一致(勾選框還在但沒作用)

P1 進得去(8 項)

ID 做什麼 repo 改既有檔 新開檔 依賴 可砍?
P1-1 主系統接線:core/plugins/remote_agent.py(289 行)接 P0-5/P0-6 的 port;_ADMIN_CAPABILITIES 登記三端點;能力點 migration(照 FR-157 A1);CIDR 交集非空驗證;「離線操作者 0 位」查詢給清單 BE core/plugins/remote_agent.py 289、core/host_capabilities.py 275、scripts/sql/ 一支 core/plugins/_agent_offline_operators.py P0-5、P0-6 ❌
P1-2 離線操作者授權頁:新子元件 OfflineOperatorsDialog.vue(勾人、範圍不超過其既有能力點、到期日、離線密碼狀態、撤銷、「有待同步的撤銷」;「Agent 檔案」欄標「勾了=看得到這台全部檔案」,D21);清單加「離線操作者」欄與 0 位紅字;不往 RemoteAgentManage.vue(766 行)加 FE 主站 RemoteAgentManage.vue 766(只加一行掛子元件+一欄)、api.js、i18n src/views/remote-agent/OfflineOperatorsDialog.vue P1-1 ⚠️ 可砍→離線只剩 local-admin;老闆「各區自己用」的多人情境做不到
P1-3 Agent 名單快取與離線身分:offline_operators.observe(data) 寫 SQLite;撤銷→SessionStore.revoke_by_sub;local-admin 密碼(沿用 LOCAL_ADMIN_SUB)與離線密碼 argon2id 存 local_credentials;鎖定計數單調時鐘;有效期;離線 30 天停用(含掃描旗標);未同步狀態判定(allowed_targets.received 且名單收過);heartbeat_status.last_error_kind(401/403/404 分開) evidence-agent core/enroll.py 411(加一行 observer)、core/console/session.py 75、core/heartbeat_status.py、core/console/heartbeat.py 76(回報欄位) core/console/local_auth.py(登入邏輯)、core/offline_operators.py(快取 observer)、core/local_credentials.py P0-4、P0-6 ❌
P1-4 Agent 登入 API:POST /console/local-session(body ≤1KB、四種失敗同一 401、通過發既有 session);POST /console/api/me/password(改自己的離線密碼或 local-admin 密碼);GET /console/state(在線/離線/未同步/停用,登入頁用,無需 session);CSP 照既有;稽核 local_login evidence-agent core/console/routes.py 212(只掛 blueprint 一行) core/console/local_auth_routes.py P1-3 ❌
P1-5 CIDR 白名單:安裝時 AGENT_CONSOLE_ALLOW_CIDR 底線;心跳收主系統 CIDR 取交集(空則忽略+稽核);隱含 loopback;browser_plane_server.py 連入檢查;CLI console-allow-cidr reset evidence-agent core/browser_plane_server.py 218、config/config.py 162、scripts/install/install.sh 🔴3426(子命令進 lib) core/console_allow_cidr.py、scripts/install/lib/console_allow_cidr.sh P0-6 ⚠️ 可砍→8444 維持現況不限;手冊寫「靠客戶防火牆」
P1-6 安裝與升級流程(D13):新裝強制問 local-admin 密碼;升級非互動不強制;完成畫面保管提示;reset-admin-password、wipe、re-enroll 擴充清本機;upgrade 前問執行中任務 evidence-agent scripts/install/install.sh 🔴3426、deploy/install.sh 🔴2894(兩份 install.sh,docker 與原生各一,都要改) scripts/install/lib/local_admin.sh、lib/wipe.sh P1-3 ❌(沒密碼就沒有 local-admin)
P1-7 FE console 登入頁、殼改側欄、三態:Login.vue 從主站 Login.vue 複製版面刪分流邏輯取代 Blocked.vue;App.vue 的 TabView 換成側欄+頂欄+狀態列(照主站 AppSidebar/AppTopbar 版面,選單寫死清單、teal 主題色、分頁標題帶 Agent 名);phase='login';GET /console/state 驅動狀態列與側欄反灰;設離線密碼對話框;「我的離線密碼」;local-admin 改密碼 FE console App.vue 149、api.js 74、main.js 51、console.scss 91、i18n agent-console.json console/Login.vue、console/Shell.vue(側欄+頂欄+狀態列)、console/SetOfflinePasswordDialog.vue、console/state.js(小 store) P1-4 ❌
P1-8 總覽改 dashboard:警示區(連線、待回傳、磁碟、清冊過舊、帳密損壞、被拒絕)、網路一行、正在執行、最近五筆、靜態資訊;overview.py 加 alerts 組裝與最近執行查詢 evidence-agent+FE console core/console/overview.py 33、console/views/Overview.vue 141 console/components/AlertList.vue P1-3、P2-1(最近執行) ⚠️ 可砍→總覽維持 FR-157 現況,有事要自己翻各頁

P2 能做事(9 項)

ID 做什麼 repo 改既有檔 新開檔 依賴 可砍?
P2-1 Agent 手動執行骨架:_run_one 的 ack/result/report-sink 抽成可注入介面(雲端 sink/本機 sink),不複製;local_runs 表寫入(source 值:cloud_task/agent_manual,畫面字「主系統任務」「Agent 手動」);並發上限(2 掃描 1 診斷);取消沿用 _RUNNING_TASKS;離線 30 天與未同步擋;ZAP 網址型目標納清冊(allowed_targets.py 加網址欄位、hostname 解析成 IP 比對) evidence-agent core/task_executor.py 369(抽 sink,淨減)、core/allowed_targets.py 203 core/task_sinks.py、core/local_runs.py P0-4、P1-3 ❌(P2-2、P2-5 都靠它)
P2-2 手動掃描 API:GET /console/api/scan/tools(八支全部、帳密狀態、capabilities)、GET /console/api/scan/targets(清冊)、GET /console/api/scan/schema/{tool}(既有 tool_schemas)、POST /console/api/scan/runs(清冊 check、帳密 credential_resolver、入 local_runs)、GET /console/api/scan/runs/{uid}、POST .../cancel;scope run-local-scan;稽核。第三方元件掃描:源碼上傳走這台既有 PUT /scan-input(本機 session 代替主系統簽的票)、執行走既有 sbom_scan connector、結果回傳走 source_scans 既有收件路徑並標來源=Agent 手動,不進 agent_local_runs(§4.3b) evidence-agent core/console/routes.py 212(掛 blueprint)、core/scan_input.py(接受本機 session) core/console/scan_routes.py P2-1 ⚠️ 可砍→老闆的「離線手動掃描」沒了,是核心需求
P2-3 掃描工具分頁:Scan.vue:工具卡、清冊目標挑選(搜尋+手打驗網段)、tool_schemas 表單(重用主站 DetectionConfigField)、進度、取消、結果連到紀錄 FE console App.vue 149(加 tab) console/views/Scan.vue、console/scanApi.js P2-2 同 P2-2
P2-4 網路除錯執行器:新套件 core/netdiag/:九卡儀表板讀取(ip/ss/resolvectl/ethtool/nmcli,容器形態偵測回「不可用」)、十項診斷(每項固定逾時、subprocess 陣列、目標先過 allowed_targets.is_allowed() 或三固定例外、速率限制、單目標序列、連線間隔 1 秒、埠上限 5)、limits.py 多一支 estimate(selection) 回總連線數/秒數/噪音等級(D20,門檻 10 寫死)、主系統固定序列(重用 cloud_trust.describe_tls_failure、clock_skew)、判讀規則引擎(確定性表)、報告產生(JSON+文字+PDF 走 WeasyPrint) evidence-agent 無 core/netdiag/__init__.py、dashboard.py、probes/{ping,dns,port,tls,http,ssh,mtu,traceroute,mtr,cloud}.py、verdict.py、report.py、limits.py P1-3 ⚠️ 可拆:儀表板(P2-4a)、十項診斷(P2-4b)、判讀(P2-4c)、PDF(P2-4d)各自可砍;砍 P2-4c 結果只剩原始輸出
P2-5 網路除錯 API:GET /console/api/netdiag/dashboard、GET .../targets、POST .../runs(進 local_runs kind=diag)、GET .../runs/{uid}、GET .../runs/{uid}/report.{txt,pdf};scope net-diag;未同步只放 cloud 序列;稽核 evidence-agent core/console/routes.py 212 core/console/netdiag_routes.py P2-1、P2-4 同 P2-4
P2-6 網路除錯分頁:NetDiag.vue:儀表板(資訊區+連線狀態燈分開;重新整理、複製成文字)、目標、勾選與噪音標示、噪音計算條(打 estimate 即時顯示總連線數)、超門檻確認框列原因(D20)、路徑圖元件、判讀卡、摺疊原始輸出、匯出、回傳 FE console App.vue 149 console/views/NetDiag.vue、console/components/PathFigure.vue、console/components/VerdictCard.vue、console/netdiagApi.js P2-5 同 P2-4
P2-7 執行紀錄分頁:Runs.vue:主系統派的+Agent 手動同一表、來源欄、拒絕也列、回傳三狀態(已回傳附時間/待回傳/回傳失敗附原因)、在線「立即重試」(打 POST /console/api/runs/{uid}/resend,走 outbox 立即 drain 一筆)、報告下載 FE console+evidence-agent App.vue console/views/Runs.vue、core/console/runs_routes.py P2-1 ⚠️ 可砍→結果只能從掃描頁與除錯頁各自看
P2-8 維運區(D14 Agent 側):重啟(優雅結束:取消執行緒、關 DB、exit 0)、確認框列中斷任務、uptime 與上次重啟原因、支援診斷包(log 尾段、去敏設定、儀表板快照、最近診斷);證據檔儲存位置表單(D22):四種型別(內建 SeaweedFS/自有 SeaweedFS/MinIO 相容 S3/本機磁碟)、欄位與 CLI configure-storage 同一組、測試連線、儲存後 Agent 自己重載不重啟、「換位置不搬舊檔」警語;祕密金鑰走既有 credential_store;表單儲存層與工具帳密共用(抽 core/console/secret_form.py,P4-1 同一支);scope configure evidence-agent+FE console main_app.py 31(結束鉤子)、App.vue、core/console/tools.py 213(抽共用表單) core/console/ops.py、core/console/ops_routes.py、core/console/storage_routes.py、core/support_bundle.py、core/console/secret_form.py、console/views/Ops.vue、console/views/StorageForm.vue P1-3 ⚠️ 可砍→重啟靠 SSH 下 restart;診斷包可單獨砍;儲存表單可砍→照舊走 CLI
P2-9 心跳旗標重啟(D14 主系統側 Agent 端):restart.observe(data):when_idle 等無任務、now 立即;中斷任務標 cancelled evidence-agent core/enroll.py 411(一行) core/restart_flag.py P0-6、P2-8 ⚠️ 可砍→「在線但 8444 被擋」的情境無法遠端重啟

P3 接回主系統(7 項)

ID 做什麼 repo 改既有檔 新開檔 依賴 可砍?
P3-1 Agent outbox:outbox 表;_result()、local_runs 摘要、稽核事件失敗入列;心跳成功後 drain()(50 筆、退避、送成功才刪);丟棄順序;離線判定遲滯(3 敗/2 成);磁碟門檻 evidence-agent core/enroll.py 411(一行)、core/task_executor.py(_result 改走 sink) core/outbox.py P0-4、P2-1 ⚠️ 可砍→斷線期間的任務結果永遠丟(現況就是如此);是老闆「續傳」需求本體
P3-2 主系統收自主執行紀錄:local-runs 端點接線、agent_local_runs 落庫、報告下載走資料面 JWT(既有 data_plane_auth)、started_by nickname enrich BE core/plugins/remote_agent.py core/plugins/_agent_local_runs.py P0-6、P3-1 ⚠️ 可砍→台北看不到各區自主執行;老闆「報告回傳總部」做不到
P3-3 自主執行紀錄頁:新 view AgentLocalRunsView.vue(篩 Agent/種類/工具/人/日期、報告下載、離線標「報告在離線的 Agent 上」);ui_routes+route_capabilities 登記(FR-132 守衛) FE 主站+BE migration api.js、i18n、scripts/sql/ 一支 src/views/remote-agent/AgentLocalRunsView.vue P3-2 同 P3-2
P3-4 主系統離線顯示:任務頁「Agent 離線中,等待回傳(最後心跳)」橫幅資料與元件;清單「待傳」欄;不計入失敗 BE+FE 主站 jedi-detection 任務狀態組裝(detection_orchestration_service.py 🔴2375——只讀不加,狀態欄位在 jedi-remote-agent 的 _enrich_online 補)、FE 任務頁元件 FE AgentOfflineBanner.vue P0-6 ⚠️ 可砍→畫面轉圈不說明;體驗問題非功能問題
P3-5 7 天 reaper 接線(D15):core/scheduler.py(714)加一條 @_job_scoped("agent_task_offline_reaper") 呼叫 P0-6 的 run_daily_tick();system_context 具名;看到 0 筆不動 BE core/scheduler.py 714(加 20 行;已逼近上限,抽 core/scheduler_agent.py) core/scheduler_agent.py P0-6 ⚠️ 建議留:它修的是既有「永遠執行中」缺口,與本案無關也該做
P3-6 主系統要求重啟:POST /remote-agents/{uid}/restart 接線+_ADMIN_CAPABILITIES;FE 清單「要求重啟」按鈕(子元件 RestartAgentDialog.vue,when_idle/now) BE+FE 主站 core/plugins/remote_agent.py、RemoteAgentManage.vue 766(一行) src/views/remote-agent/RestartAgentDialog.vue P0-6、P2-9 ⚠️ 可砍,同 P2-9
P3-7 守衛測試(Agent 側):七條(§6.2):所有入口過 allowed_targets、無 shell=True、工具清單無 nmap/tcpdump/iperf、local-admin 打清冊外 403、未同步全 403、無新增使用者路由、終態冪等 evidence-agent+BE test — test/test_fr166_guards.py(Agent)、test/security/code/test_agent_console_guards.py(BE) P2、P3 ❌(CLAUDE.md 核心共用邏輯要寫)

P4 LDAP 與收尾(6 項)

ID 做什麼 repo 改既有檔 新開檔 依賴 可砍?
P4-1 Agent LDAP 三選一(同步/自設/不接):裝 jedi-ldap(加進 Nuitka --include-package 清單);「身分來源」卡(形狀同工具帳密:Fernet 存服務帳密、測試連線、釘住指紋存 local_credentials);同步模式收心跳 ldap_config;登入時多一分支(名單內+AD 密碼對=通過;名單不在=拒);心跳回報 ldap_source evidence-agent core/console/local_auth.py(P1-3 新檔)、core/console/tools.py 213(身分來源卡與工具帳密同形,抽共用表單儲存成 core/console/secret_form.py 再各自薄層)、scripts/build/build_agent_release.sh core/ldap_source.py P0-1、P1-3 ⚠️ 可整項延後→離線靠離線密碼與 local-admin;老闆「分公司用自己 AD」做不到
P4-2 主系統 LDAP 下發:心跳 ldap_config 從 THIRD_PARTY_LOGIN 組(服務帳密 Fernet 密文原樣下發、Agent 用自己的 key 轉存——或下發明文走 mTLS 後 Agent 立即 Fernet,二擇一在 design 定);Agent 清單顯示「身分來源:同步/自設 x.x.x.x/不接」 BE+FE 主站 core/plugins/remote_agent.py、RemoteAgentManage.vue(一欄) core/plugins/_agent_ldap_config.py P0-3、P0-6、P4-1 同 P4-1
P4-3 FE console 身分來源卡 FE console console/views/Tools.vue 467 console/views/IdentitySource.vue(或併入 Tools 作第二區,看 Tools 體積) P4-1 同 P4-1
P4-4 SPEC:docs/spec-site/current/ Agent 主控台頁改版(登入三層、四分頁)、Agent 管理頁(離線操作者、自主執行紀錄、要求重啟)、LDAP 設定頁(釘住);writing-feature-specs skill BE docs 既有 spec 頁 — 全部 ❌
P4-5 手冊與安裝文件:「Agent 獨立作業」一章(登入三條路、離線能做什麼、除錯噪音表與總量門檻(D20)、安裝後連不上排查序列、local-admin 保管、離職流程、CIDR 逃生門、多據點命名建議、「Agent 檔案權限是整台」(D21)、儲存位置改在主控台設(D22));安裝需求(8444、CAP_NET_RAW、容器形態不可用項);資安例外登記兩條+docs/security-report/requirements/ 註記 BE docs+evidence-agent deploy/README.md deploy/README.md 手冊章節 全部 ❌
P4-6 e2e:主系統登入 LDAP(鎖定、釘住)、Agent 管理頁三個新動作;Agent 主控台不在 e2e 範圍(另一台機器) test repo — features P0~P3 ⚠️ 可延後到下一輪 e2e 補

14.4 數量總覽

階段 工項 卡(一卡一 repo 拆後) 不可砍 新套件 動到的超標檔
P0 7 9 5 jedi-ldap —
P1 8 11 6 — install.sh ×2(🔴 子命令進 lib)
P2 9 13 2 — —
P3 7 10 1 — scheduler.py 714(抽)、detection_orchestration_service.py 🔴2375(只讀不加)
P4 6 8 2 — —
合計 37 約 51 16 1 4

最小可出貨組合(PM 砍到底的下限):P0 全部(9 卡)+P1 不可砍 6 項+P2-1/P2-2/P2-3(手動掃描)+P3-1/P3-2/P3-5+P4-4/P4-5。約 28 卡。砍掉的是:CIDR、網路除錯整塊、維運與重啟、自主執行紀錄頁、離線顯示、LDAP、e2e。

老闆原始五點對應:① 直接登入=P1;② 離線身分=P0-1~3+P1;③ 獨立掃描=P2-1~3;④ 網路除錯=P2-4~6;⑤ 續傳=P3-1~2;資安=P3-7+P4-5 例外登記。P4 LDAP 是決策者追加(老闆可能會要)。

14.5 銜接保證:每階段結束的狀態

階段結束 升級既有客戶後 沒做的功能
P0 SQLite 多四張空表;主系統 LDAP 登入變嚴(鎖定、釘住:第一次登入自動信任現有伺服器憑證,使用者無感);心跳多幾個欄位舊 Agent 忽略 全部看不到
P1 直接打 8444 有登入頁;裝機要設 local-admin(升級不強制、紅字提醒) 登入後只有 FR-157 三頁
P2 多三個分頁;結果留 Agent 主系統看不到自主執行
P3 復線自動補傳;主系統多紀錄頁與離線橫幅 —
P4 分公司可接自己的 AD —

每階段都是「加法」:沒有任何一階段會把前一階段做的東西改形狀。這是戰線拉長也不斷的根據。


12. 這份文件的定位

你想知道 看哪份
做出來長什麼樣 雛形(右上切在線/離線)
為什麼要做、各選項與建議 本稿
主控台殼、入場券、救援碼怎麼做的 FR-157 design §5.1、FR-156 design E1
派工骨架與 connector FR-056 design、evidence-agent core/task_executor.py
全量工作清單、哪些可砍、最小可出貨組合 本稿 §14
定案與開卡 design.md(D16 裁完後落)