R2a 檢查結果:任務下發與狀態機+控制面 route 與守門殼(jedi-remote-agent)

R2a 檢查結果:任務下發與狀態機+控制面 route 與守門殼(jedi-remote-agent)

檢查日期 2026-09-16|對應卡片 CM-1593|檢查範圍 30 個檔案

§1

🔴 一句話結論

五條發現全部通過檢查——三條嚴重、一條中等、一條輕微——但一條新問題都沒有。 五條分別對應先前已經開好的兩張修正卡(CM-1595/CM-1596),這一棒的價值不在「又找到什麼」,而在把 CM-1595 那條攻擊路徑的最後幾塊拼圖補齊了:授權過期進入唯讀模式時這些端點照樣打得到、雲端會主動去抓攻擊者指定的檔案存成稽核證據、「你是哪一家客戶」除了代理程式編號與機器編號之外還可以用任務編號來選、以及管理頁面把機器指紋回傳出去等於送出冒充用的第二把鑰匙。

白話講整件事:客戶機房的代理程式(agent)跑完掃描要回報結果,接收回報的那幾個端點完全不檢查來人是誰;更麻煩的是,伺服器決定「這次動作算在哪一家客戶頭上」的依據,就是請求自己送上來的那個編號——攻擊者填哪一家,伺服器就切成那一家。

這是 jedi-remote-agent 第一次真正跑完三個檢查員投票的一棒。R1 那次面板全滅於額度上限(21 票 0 張投出),R1b 雖然跑完但驗證章被渲染退件;本棒 15 票全數投出、五條全 3:0 通過、驗證章蓋 verified、掃描當下工作區乾淨。所以這次的「這些問題是真的嗎」是三棒以來最扎實的一次。

§2

這一棒在檢查什麼

客戶機房裡裝了一個代理程式,雲端把掃描工單派給它、它跑完回報結果,這一整條「下發—接收—狀態轉換」的鏈子就是本棒的主題,再加上 2026-09-11 套件拆分時搬進來的那層對外 HTTP 介面與守門殼。具體三件事:

  1. 回報結果的那幾個端點有沒有檢查來人是誰(api/routes/ 與路由表 api/routing.py)。
  2. 任務的狀態機——從「待派」走到「成功/失敗」這條路上,有沒有任何一步確認「回報的這台,是不是當初被派工的那台」(domain/agent_task/、app/service/agent_task_service.py)。
  3. 守門殼有沒有裝好——套件自己不決定誰能進,它把守門這件事交給宿主(主專案)傳一個函式進來;本棒要確認的是「宿主如果忘了傳,會靜靜地變成沒人守門嗎」(plugin/assembly.py、plugin/contract.py)。

掃描目標 ~/Projects/Jedicogy/module/jedi-python-package/jedi-remote-agent,revision 3cc966f8(branch feature/review,工作區乾淨、無未提交改動),mode scan,effort low,focus attack-surface。範圍是 30 個受版控檔案,啟動前已核對;這 30 檔從 3cc966f8 到現在的 HEAD 零漂移,所以報告裡的行號現在打開還是對的。

刻意排除在本棒之外的,是代理程式的身分與註冊那一整條鏈(agent_enroll*、remote_agent_service.py、common/agent_auth/、domain/remote_agent/、infra/remote_agent/、migrations/)——那些在 R1 與 R1b 掃過了。

§3

Coverage

30 個檔以單一元件讀完。低強度跑法不做元件盤點、不做威脅建模、不跑額外的廣度掃描,completenessCheckOutcome 為 not-applicable(指定範圍的掃描本就不適用)。這份報告完全不能拿來說 jedi-remote-agent 其他地方沒事,身分與註冊那半邊要看 R1/R1b。

派出兩位研究員、兩位都回報。驗證跑一輪,7 個原始候選去重後 5 個,每一條都拿到完整的三票,15 票全投出,沒有候選遺失、沒有候選未被審、沒有候選被交到下一輪。沒有任何候選被駁回。面板調整了一條的嚴重度:F5 研究員原報中等,面板定為輕微(三票分別是輕微、輕微、中等)。

有一條發現的位置在範圍外:F5 的問題函式住在 app/service/remote_agent_service.py,那支屬於 R1 的範圍、不在本棒的 30 檔裡;研究員是從範圍內的路由檔追進去的,所以報出來但標為越界。

這份報告沒有逐檔紀錄:coverage.research 是 null,工具沒留下「哪位研究員把哪個檔讀到什麼程度」的帳。下方「卡片重點逐項」段由首腦逐項開檔補查,並標明哪些是工具報的、哪些是人工查的。

工具沒有實際執行任何程式碼:沒跑測試、沒發請求、沒示範攻擊。五條發現全部是讀原始碼推出來的。

§4

掃到什麼(總覽)

# 嚴重度 這是什麼問題(白話) 出事會怎樣 要先有什麼才打得到 位置 修正卡
F1 🔴 嚴重 「我收到工作了」與「我做完了」這兩條回報管道,一個身分檢查都沒有 不需任何帳號,把別人的掃描標成「成功」,並讓雲端去抓攻擊者指定的檔案,當成合規稽核的證據存進案子裡 連得到 API+知道一個還沒結束的任務編號(無認證的心跳端點就會回給你) api/routes/remote_agent_route.py:185(收到)、:192(做完) ✅ 已修(CM-2052;原重複 R1 對應卡 CM-1595 作廢)
F2 🔴 嚴重 「報到」(心跳)這條管道也不檢查身分,而且回應裡夾帶已經解密的明文帳號密碼 拿走客戶自己機器的帳密(SonarQube 權杖、SSH/WinRM 帳密),同時把真代理程式的工作佇列搶過來 連得到 API+知道一個代理程式編號或一個機器編號(VM 樣板複製出來的機器編號會一模一樣) api/routes/remote_agent_route.py:179 ✅ 已修(CM-2052;原重複 R1 對應卡 CM-1595 作廢)
F3 🔴 嚴重 「這次動作算在哪一家客戶頭上」,是用請求自己送來的任務編號查出來的,查的時候還刻意繞過客戶隔離 A 客戶的合法代理程式,拿 B 客戶的任務編號就能用 B 的身分寫結果與證據;資料庫的隔離機制從頭到尾看不出異常 拿得到一個別家客戶的任務編號;多客戶部署才成立 app/service/agent_task_service.py:134 ✅ 已修(CM-2052;原重複 R1 對應卡 CM-1595 作廢)
F4 🟡 中等 同一條路的授權面:沒有檢查「這張工單是不是派給你的」 就算之後補上了身分驗證,任何一張合法的代理程式憑證,仍然可以替別台回報 有一張合法的代理程式憑證(今天連憑證都不用)+知道一個任務編號 app/service/agent_task_service.py:134 ✅ 已修(CM-2052;原重複 R1 對應卡 CM-1595 作廢)
F5 ⚪ 輕微 管理頁「測試連線」填什麼網址就打什麼,沒有任何位址限制 拿我們的伺服器當跳板探測內網,從回傳的狀態碼分辨「服務在但拒絕」與「完全不通」 需要租戶管理員權限(這是它只算輕微的原因) app/service/remote_agent_service.py:66(⚠️ 越界) ✅ 已修(CM-2370,1.21.1;原卡 CM-1596 作廢)

五條全部是既有問題的再次確認,淨新增 0。 跨 arc 總表 §3.1 不加項。

§5

Findings

F1 — 回報工作的兩個端點沒有身分檢查,可以塞任意結果(嚴重,3:0)

這是什麼問題。 代理程式跑完掃描後會呼叫兩個端點:/agents/tasks/<編號>/ack(我收到這份工作了)與 /agents/tasks/<編號>/result(我做完了,結果在這)。這兩條在路由表裡標著 needs_admin=False,而掛載的那段程式只對標 True 的項目加守門——所以這兩個端點出貨時一個守門裝飾器都沒有,服務層也沒有任何一行確認來人是誰。

出事會怎樣。 客戶用來應付外部稽核的證據會被汙染。攻擊者把任務標成「成功」時可以附一個 result_ref.upload_uid(結果檔的編號),宿主的收尾程式(on_task_succeeded)會主動照這個編號去把那個檔案抓回來,存成這次檢測工單的稽核證據。也可以反過來標成「失敗」,讓真正跑出問題的掃描結果整份消失。真的代理程式稍後回報時,會因為「任務已在終態、這個狀態轉換不合法」被擋掉——假的先到就贏。

要先有什麼才打得到。

  • 連得到 /api/1.0/agents/tasks/<編號>/ack 或 /result。授權(license)過期進入唯讀模式也照樣打得到——唯讀中介層的白名單明文豁免了整個 /agents/ 前綴(common/middleware/license_readonly_mw.py:76)。
  • 知道一個任務編號。這個不難拿——下面 F2 那個同樣沒有身分檢查的心跳端點,回應裡的 pending_tasks 每一筆就帶著任務編號。
  • 該任務還沒結束(待派/已派/執行中)。

在哪裡。 jedi_remote_agent/api/routes/remote_agent_route.py:192(AgentTaskResultRoute.post)與 :185(AgentTaskAckRoute.post)。路由表宣告在同套件 api/routing.py:39-40(兩條都是 needs_admin=False),只對 True 套守門的那行在 :95。

怎麼修。 讓這兩個端點跟其他資料面呼叫一樣要求證明身分——由入口驗過再傳下來的用戶端憑證,或套件自己就會簽的短效通行證(RS256 JWT)——並且在驗過身分之後,比對「這張工單記的代理程式」是不是就是來人,不符就拒絕。

驗證。 3/3 三位檢查員一致確認成立。

首腦核對註記與判定。 屬實,重複 R1 的 F2+F5,修正卡 CM-1595 已開,不另計新項。

但這次有兩塊增量價值,已經 append 進 CM-1595:

  1. 授權唯讀模式也豁免——R1 只說「連得到 API 就打得到」,這次追到中介層的白名單(license_readonly_mw.py:76)明文放行 /agents/,代表授權過期的環境不但沒有變安全,這條路還一樣開著。
  2. 宿主會主動去 fetch 攻擊者指定的東西——R1 只寫到「可以偽造證據」,這次把 result_ref.upload_uid → 宿主 listener 去抓 blob → 存成稽核證據這一整段追完了。這是攻擊者從「能寫一筆假紀錄」升級成「能把自己準備的任意檔案塞進客戶的稽核案卷」的關鍵一步。

F2 — 心跳端點沒有身分檢查,回應夾帶解密後的明文帳密(嚴重,3:0)

這是什麼問題。 代理程式每隔一段時間會「報到」一次(心跳),順便領取待辦工作。這個端點在路由表裡同樣標著 needs_admin=False(api/routing.py:38),所以沒有掛任何守門;伺服器唯一用來判斷「你是哪一台」的依據,就是請求內容裡自己寫的 agent_uid 或 device_uuid。

device_uuid 這個尤其糟——它來自機器本身的識別碼(machine-id/product_uuid),同一份 VM 樣板複製出來的機器,這個值會一模一樣。

出事會怎樣。 回應會把該客戶待辦的掃描工單整包送出,裡面的 credentials 欄位是宿主的付載提供器已經解密成明文的內容——SonarQube 的存取權杖、客戶自己主機的 SSH 與 WinRM 帳號密碼。拿到的人可以直接登入客戶自己的機器。同一次呼叫還會覆寫那台代理程式的「最後上線時間」「版本」「能力清單」「裝置指紋」,等於把真代理程式的工作佇列整條搶過來。

要先有什麼才打得到。

  • 連得到 /api/1.0/agents/heartbeat。落地版的對外入口 nginx 用一條 location /api/1.0 代理全部路徑,這條也在內。
  • 知道一個代理程式編號,或一個機器編號。前者是任何登入使用者在代理程式管理頁就看得到的;後者在 VM 樣板複製的環境是共用的。
  • 要拿到帳密還需要該代理程式當下至少有一筆待辦工作;單純冒充則什麼都不用。

在哪裡。 jedi_remote_agent/api/routes/remote_agent_route.py:179(AgentHeartbeatRoute.post),路由表在 api/routing.py:38。

怎麼修。 在應用層要求一個可驗證的代理程式身分憑據,不要相信請求自報的編號:驗入口傳下來的用戶端憑證(這需要 nginx 開 ssl_verify_client on,出貨的三個 nginx 設定檔一行都沒有),或要求套件本來就會簽的短效通行證,並且拒絕任何「宣稱的身分」與「要被更新的那一列」對不上的心跳。

驗證。 3/3 三位檢查員一致確認成立。

首腦核對註記與判定。 屬實,重複 R1 的 F1,併 CM-1595,不另計。

這是同一件事的第三次獨立確認:R1 的研究員報過一次、首腦 2026-09-16 比對 fc1cadb 這筆修改的前後又核過一次、本棒面板 3:0 再確認一次。三次都指向同一段程式碼、同一個結論——這條沒有翻案空間。

F3 — 「算在哪一家客戶頭上」由請求自己送來的任務編號決定,跨客戶成立(嚴重,3:0)

這是什麼問題。 這是本棒最值得看的一條。收到回報之後,伺服器要決定「這次動作算在哪一家客戶頭上」,做法是:拿請求路徑上的那個任務編號,在一個刻意繞過客戶隔離的提權範圍裡(elevated_readonly_scope())去查整張 agent_tasks 表,找到那一列之後,取它的 tenant_id,然後把整個執行環境切換成那一家客戶的機器身分(tenant_context)。

整段程式沒有任何一行比對「呼叫者有沒有權碰這張單」或「呼叫者是不是這家客戶的」——送上來的那把鑰匙同時決定了「要開哪扇門」和「用誰的身分開」。

出事會怎樣。 客戶之間的隔離在這條路上等於不存在。一個持有 A 客戶合法憑證的代理程式,只要拿到 B 客戶的一個任務編號,就能用 B 客戶的身分寫入終態與證據。資料庫的隔離機制(RLS,就是「每個客戶只能看自己資料」的那套)從頭到尾不會報錯——因為連線本身就被設定成 B 客戶了,它看到的一切都「合法」。

這一點也說明了:就算之後把 nginx 的用戶端憑證驗證開起來,這條也還在——憑證只證明「你是一台合法登記過的代理程式」,不證明「這張工單是你的」。

要先有什麼才打得到。

  • 打得到 ack 或 result 端點(今天不需任何憑證,見 F1)。
  • 知道一個屬於別台代理程式或別家客戶的任務編號。
  • 多客戶部署才有跨客戶效果;單一客戶的部署只會看到「冒充別台」這一半。

在哪裡。 jedi_remote_agent/app/service/agent_task_service.py:134,AgentTaskService._task_tenant_context。

怎麼修。 把順序倒過來:先從憑證或已簽名的通行證解析出「呼叫者是哪一台代理程式」,再要求 task.agent_id == 呼叫者.id 且 task.tenant_id == 呼叫者.tenant_id,通過了才進 tenant_context。 那個繞過隔離的提權查詢要收窄到「查呼叫者自己是誰」,絕對不能用來查一個由呼叫者指名的物件。

驗證。 3/3 三位檢查員一致確認成立。

首腦核對註記與判定。 屬實。這正是首腦 2026-09-16 推翻 CM-1595 降級時說的那件事——當時是人工比對推出來的,這次工具的三個檢查員各自獨立讀完程式碼、3:0 坐實。

與 R1 的 F3(心跳那條路的跨客戶問題)是同一個根因、不同端點,所以併 CM-1595。卡尾已補上一句總結,方便修的人一次看全:三支端點(心跳/ack/result)的做法全部都是「用呼叫者送來的鍵去選客戶身分」,而這把鍵有三種形態——代理程式編號、機器編號、任務編號。修的時候三種都要堵,只堵一種等於沒堵。

F4 — 同一條路的授權面:沒有檢查這張工單是不是派給你的(中等,3:0)

這是什麼問題。 位置與 F3 是同一行,看的角度不同。F3 講的是「客戶隔離被繞過去了」,F4 講的是更基本的一件事:這裡根本沒有「歸屬檢查」——沒有任何一步問「這張工單當初是派給誰的、你是不是他」。

出事會怎樣。 意義在於它是「補了認證之後還剩下的洞」。假設之後有人把 nginx 的用戶端憑證驗證開起來、端點也掛上守門,那時候任何一張合法的代理程式憑證(例如某個客戶機房裡被入侵的那一台)仍然可以替別台回報、把別人的檢測執行歷史與證據寫壞。

要先有什麼才打得到。 今天:什麼都不用(端點無認證)。補上認證之後:一張任何合法的代理程式憑證,加上一個任務編號。

在哪裡。 jedi_remote_agent/app/service/agent_task_service.py:134,同 F3。

怎麼修。 把驗過的身分一路傳到 domain service 去做 task.agent_id == 呼叫者.id 的斷言。

驗證。 3/3 三位檢查員一致確認成立。

首腦核對註記與判定。 屬實,重複 R1 的 F5,併 CM-1595。

🔴 與同日 R2b(CM-1601)的 F4 是同一條路的兩端:R2b 是從狀態機那一側(agent_task_domain_service.update_status)看到的,本棒是從任務服務這一側看到的。兩邊合起來得到一個對修法很重要的結論——只在 route 掛一個守門裝飾器不夠。因為 update_status 這一層自己不檢查歸屬,只要之後有第二條呼叫路徑進來(例如未來新增的批次補登、維運工具、資料修復腳本),就會再一次繞過。身分必須一路傳到 domain service 做斷言,或者乾脆把「歸屬」變成 update_status 的必填參數,讓任何呼叫端都不可能轉換一張不屬於它的工單。

F5 — 測試連線可以被當成內網探測器(輕微,面板由中等降下,⚠️ 越界)

這是什麼問題。 管理頁上有個「測試連線」功能,會拿使用者填的網址直接去打一次(httpx.get),不檢查協定、不檢查主機、不檢查位址範圍,然後把對方回的狀態碼與最多 200 字的錯誤訊息回給呼叫者。

出事會怎樣。 攻擊者可以拿我們的伺服器當跳板去探測內部網路——從回應分辨「這個服務在,只是拒絕我」與「這裡根本沒東西」,逐一掃過位址範圍就能畫出內網有哪些服務、開了哪些埠。打得到的目標包含容器網路裡的資料庫、Redis、授權服務,雲端部署還包含中繼資料端點。

要先有什麼才打得到。

  • 一個已登入、且持有 remote-agent-manage.create 權限的租戶管理員。這就是它只算輕微的原因——不是任何人都打得到。
  • 授權(license)過期進入唯讀模式也照樣打得到:這支在唯讀中介層的白名單裡(common/middleware/license_readonly_mw.py:116)。
  • 代理程式認證設定是關閉狀態(mode=none),或填的網址不是 https——這兩種情況都會走到那個沒有任何限制的裸 httpx.get。

在哪裡。 jedi_remote_agent/app/service/remote_agent_service.py:66,RemoteAgentService.check_health。入口是本棒範圍內的路由 api/routes/remote_agent_route.py:76。

怎麼修。 送出前先驗網址:只准 http(s)、把主機名稱解析出來後拒絕本機位址、link-local 與私有網段(解析之後要再檢查一次,避免解析結果被換掉),或者乾脆只准打已經登記在該客戶名下的代理程式位址。回傳改成一個「通不通」的布林值,不要把上游的狀態碼與原始錯誤訊息照原樣吐回去。

驗證。 3/3 三位檢查員一致確認成立。研究員原報中等,面板降為輕微(三票分別是輕微、輕微、中等)。

首腦核對註記與判定。 ⚠️ 越界發現——這個函式住在 remote_agent_service.py,屬於 R1 的範圍、不在本棒 30 檔內,研究員是從範圍內的路由追進去的。重複 R1 的 F4,修正卡 CM-1596 已開,不另計。

同意面板降為輕微(需要管理員權限,與 R1 當時把它排在 F1/F2 之後是同一個理由)。新增一點已 append 進 CM-1596:這支在授權唯讀模式的白名單裡(license_readonly_mw.py:116),所以授權過期的環境不會因此少一個攻擊面。

§6

卡片重點逐項

🔴 低強度只跑兩位研究員。卡片列的七個重點,工具實質碰到的是②、⑥,加上①的一部分(路由表那兩條 needs_admin=False)。其餘四項由首腦回頭開檔人工查證。

① 守門殼會不會靜靜地變成沒人守門(工具碰到一部分,首腦補完)

結論:乾淨,而且是意外的乾淨——它會在啟動時就直接炸掉,不會靜靜放行。

套件自己不決定誰能進,它要求宿主(主專案)傳一個「守門函式」進來:plugin/assembly.py:126 的 mount_routes(bp, adapters.admin_required)。契約上 RemoteAgentAdapters.admin_required 的型別是必填的 Callable(plugin/contract.py:90),但 create_blueprint 裡沒有任何一行 assert 去檢查它到底有沒有被傳進來。

原本擔心的是:宿主漏傳(傳成 None)時,守門會靜靜地變成「不守」。實查結果相反——真的傳 None 的話,_guard(resource, None) 會在 decorator(getattr(...)) 那一行直接丟 TypeError: 'NoneType' object is not callable,而且是在掛載階段就炸,服務根本起不來。這叫「出錯就大聲」(fail loudly),是好的那一種。

另外查了 _guard 只包五個 HTTP method 會不會漏——本套件的 Resource 沒有第六種 method,不漏。

建議(非資安):照 jedi-asset 的做法在 create_blueprint 開頭補一個顯式 assert,讓錯誤訊息直接說「宿主沒有提供 admin_required」,而不是丟一個看不出原因的 TypeError。這是可維護性改善,不升為發現。

② 任務下發與回報端點的守門(工具已報=F1/F3/F4)

結論:成立。 詳見上方三條。這是本棒唯一被工具吃得最透的一項。

③ 狀態機有沒有搶著寫的問題(工具未報,人工查證)

結論:時間窗口確實存在,但後果是「第二個請求被拒(409)」,不是兩份資料互相蓋掉。不升為發現。

update_status 的做法是三步:查這張單在不在 → 驗這個狀態轉換合不合法 → 寫進去。中間沒有加鎖(with_for_update 全套件 grep 零命中)。所以兩個回報同時進來時,理論上兩個都可能讀到「還沒結束」然後都往下走。

但實際跑起來的結果是:先寫進去的那個成功標成 succeeded,後到的那個在第二步就會發現「已經是終態了」,拋出衝突錯誤(409)——不會出現兩份結果互相覆蓋。真正會不會走到這個窗口,還取決於資料庫的交易隔離級別,本次未實測。

處置:記為跨 arc 總表 §3.2 的「非資安小坑」候選,暫不登記,寫在這裡備查。

④ 工單裡的 agent_id 是不是可以指到別家的代理程式(工具未報,人工查證)

結論:不成立。

create_task(agent_id=plan["agent"].id) 裡那台代理程式是從 _pick_agent(tenant_id) 挑出來的,它往下呼叫 get_dispatchable_agents(tenant_id, …)(jedi-detection 的 :1355),查詢本身就帶客戶條件。另一側 list_dispatchable_for_agent 的查詢也同時帶 agent_id 與 tenant_id 兩個條件。兩邊都不是破口。

⑤ 掃描工單的內容會不會被寫進 log(工具未報,人工查證)

結論:乾淨。

把 30 個檔裡所有 logger.* 的呼叫全部 grep 過一遍,沒有任何一處把工單內容(payload/params)、帳號密碼或存取權杖印出來。稽核事件那側(common/audit.py)也只帶編號與指紋,不帶內容。

⑥ 那個繞過客戶隔離的提權查詢本身是不是攻擊面(工具已報=F3)

結論:是,而且它就是 F3 的核心。 提權查詢不是「順便做的效能優化」,它是這條攻擊路徑成立的必要條件——因為它繞過隔離,攻擊者指名的任務編號才查得到、才切得過去。詳見 F3 的修法:提權查詢要收窄到「查呼叫者自己是誰」,不能用來查呼叫者指名的物件。

⑦ 回傳給管理頁的欄位有沒有多給(工具未報,人工查證)

結論:回傳裝置指紋等於送出冒充用的第二把鑰匙——是 F2 的放大器。

api/serializers/remote_agent.py 裡,管理面的回應會帶出 device_fingerprint(:28)、hardware_info(:32)與 base_url(:25)。這些需要 remote-agent-manage.create 權限才看得到,但 F2 那條攻擊只要拿到「代理程式編號或機器編號」其中之一就能冒充——而機器編號(device_uuid)正是這裡回傳出去的東西。所以管理頁把指紋顯示出來,等於替 F2 多備了一把鑰匙。

(下拉選單用的那個精簡版 schema 刻意不回 base_url(:9),這一點做得對。)

處置:併進 CM-1595 的修法要求——心跳端點不得再接受「機器編號」作為身分的替代依據,把這把鑰匙作廢,回傳指紋就不再是問題。

§7

這份結果可信到什麼程度

分兩層看。

「這五條是真的嗎」→ 可信,而且是三棒以來最扎實的一次。 15 票全數投出、五條全部 3:0 一致通過、驗證章蓋 verified、沒有任何候選被退件或延到下一輪。首腦逐條開檔核對過行號。其中 F2 與 F3 更是第三次獨立確認(R1 研究員、首腦 09-16 人工比對、本棒面板各一次)。

「只有這五條嗎」→ 中等。 好的一面:兩位研究員都完整回報、面板完整跑完,這是 jedi-remote-agent 第一次真正把三個檢查員的流程從頭跑到尾的一棒(R1 面板全滅、R1b 驗證章被渲染退件)。不能宣稱完整的一面:低強度跑法沒有元件盤點、沒有威脅建模、工具沒留逐檔紀錄(coverage.research 是 null),卡片列的七個重點工具只碰到三個,其餘四項是首腦人工補查的結論、不是工具給的。

淨新增 0。 五條全部併進既有的 CM-1595/CM-1596,跨 arc 總表 §3.1 不加項。但本棒把 CM-1595 那條攻擊鏈補完整了四點:授權唯讀模式也豁免、宿主會主動去抓攻擊者指定的檔案、任務編號是第三種可以用來選客戶身分的鍵、管理頁回傳指紋是冒充的第二把鑰匙。

§8

執行概況

項目 數字
檢查範圍 30 個受版控檔案(任務下發鏈 + 2026-09-11 拆套件時搬進來的控制面 HTTP 介面與 plugin 殼)
檢查強度 最低(low),focus 設在攻擊面
候選問題 → 去除重複 7 → 5
投票數 15(5 條候選 × 3 位檢查員),全數投出,沒有漏投、沒有中斷、沒有被延到下一輪
五條投票結果 全部 3:0 通過
被駁回候選 0
被降低嚴重度的 1(F5:研究員報中等 → 面板定輕微,三票為輕微/輕微/中等)
研究員派出 / 回收 2 / 2
驗證章狀態 verified——無退件,是本套件三棒以來第一次完整通過
掃描耗時 7,082 秒(約 1 小時 58 分)
掃描當下的程式碼版本 commit 3cc966f8,branch feature/review,工作區乾淨;30 檔自 3cc966f8 到現在的 HEAD 零漂移
首腦人工補查項目 卡片七個重點中的①(守門殼 assert)、③(狀態機無鎖)、④(agent_id soft-ref)、⑤(log 有無印帳密)、⑦(serializer 回傳指紋)
對總表的影響 §1 加 R2a 一列;淨新增 0,§3.1 不加項;CM-1595 卡尾 append 四點新細節;CM-1596 卡尾 append 唯讀白名單一點;§3.2 候選(狀態機無鎖)暫不登記
§9

交付狀況

🔴 本棒 runner 四件全未交:沒有寫報告、沒有 commit、沒有回寫 Notion(CM-1593 仍停在「未開始」)、沒有回報。工具產物是首腦自己到掃描目錄撈出來的,五條發現的逐條開檔核對、卡片七個重點裡五項的人工查證、與同日 R2b 的接合判定,也全部由首腦完成。本報告由首腦補寫,這是全計畫第十次 runner 交付掛零。


原始工具產出:~/Projects/Jedicogy/module/jedi-python-package/jedi-remote-agent/CLAUDE-SECURITY-20260916-125508/ (CLAUDE-SECURITY-RESULTS.md 為工具原文,本報告為首腦整理版,含逐項人工查證。)