D3-4a 檢查結果:編排服務前半 — 派工/取消/刪除(jedi-detection)

D3-4a 檢查結果:編排服務前半 — 派工/取消/刪除(jedi-detection)

檢查日期 2026-09-19|對應卡片 CM-1875(D3 第 4 小棒前半)|檢查範圍 1 個檔案 2,498 行(本棒歸屬前 1,151 行)

§1

🔴 一句話結論

找到 1 條新的中風險:解密後的帳密被原封不動寫進工單資料表,而且這份副本根本沒人用。 系統把客戶的 SSH 密碼、掃描器管理員 token 解密之後,一邊交給代理程式、一邊又存了一份明文到 agent_tasks.params 這張表——代理程式拿到的其實是另一份(心跳組裝時當場重解的),存下來這份從頭到尾沒有任何程式讀過。這張表沒有任何清理或保存期限,所以那份明文會一直躺著。修法是刪掉那一行加一支清資料的 migration,不影響代理程式(已逐環核對過)。另外工具還報了兩條,都是前面棒次已經記過的:list_executions 缺參與者檢查=重複第 88 項、換工具繞過台數上限=重複第 105 項,本棒不重複計數。卡片重點①「誰能按」工具沒直接查,首腦人工核對九個寫入端點全部有守門、無缺口。

§2

這一棒在檢查什麼

「執行檢測」這件事的中控台就是這支檔案。客戶在畫面上按「開始掃描」「取消」「重跑這一台」「刪掉這筆失敗紀錄」,每一個按鈕背後都是這支檔案裡的一個方法。

這一棒看的是按下去之後發生什麼,兩件事:

  1. 誰能按。這些按鈕的破壞力差很多——「開始掃描」會對客戶的正式機器發動掃描,「刪除」會讓紀錄消失。每一支都該先確認「按的人是不是這個專案的人」,漏掉任何一支都是缺口。
  2. 帳密解密之後往哪裡流。掃描要登入被掃的機器,所以系統必須把加密存放的帳密解開。解開之後那份明文經過哪些地方、有沒有在不該留的地方留下來,是這一棒的重點。

掃描目標 /Users/chouraymond/Projects/Jedicogy/module/jedi-python-package/jedi-detection,revision 955e40964baa,mode scan,effort low,範圍就是編排服務 detection_orchestration_service.py 這一個檔案(2,498 行)。

行號分界說明:這支檔案由 D3-4a/D3-4b 兩小棒分掃,D3-4a 負責前 1,151 行(派工/取消/刪除),1,152 行之後歸 D3-4b。但工具的低強度模式是整檔一次讀完、不吃行號範圍,所以它報出來的東西可能落在後半段——本棒依卡片分界處理:落在前半的計入本棒,落在後半的標註清楚、留給 D3-4b 那棒判斷。

§3

Coverage

low 強度:一位研究員讀完這個檔案就提報候選,未做元件盤點、未做威脅建模、未跑額外密鑰專項掃描,completenessCheckOutcome 為 not-applicable(低強度本來就不跑盤點)。驗證跑了 1 輪,3 個候選去重後仍是 3 個,三條全數通過,沒有候選遺失、沒有嚴重度被降低、沒有候選被略過。

研究員為了追資料流另外讀了範圍外的幾處(主專案的路由層與心跳組裝、代理程式端的工單執行器、jedi-remote-agent 的工單 model、SQL migration、綁定處理器),那些只當佐證、沒有納入稽核。研究員沒有回報「哪些檔案沒讀完」的自述(coverage.research 為 null),所以工具端沒有做讀取完整度的交叉檢查。

工具沒有實際執行任何程式碼:沒跑測試、沒發請求、沒示範攻擊,所有判斷都是讀原始碼推出來的。首腦核對階段同樣只做讀取(開檔、grep),沒有改動任何檔案、沒有連任何資料庫。

工具碰到了卡片重點②「憑證解密後流向」,重點①「誰能按」一條候選都沒提,由首腦回頭開檔逐支核對補上,詳見下方「卡片重點逐項人工查證」段。

現況(2026-10-01):本棒各條後來的處理結果如下(過程紀錄保留,不改)。

  • F1(解密帳密另存明文進工單表)=M03 第 9 條,✅ 已修(FR-114.4-3)。
  • F2(查執行歷史缺成員檢查)=M03 第 12 條,✅ 已修(CM-2040)。
  • F3(換工具範圍不重驗)=M03 第 11 條,✅ 已修(CM-2058)。
§4

掃到什麼:總覽

編號 這是什麼問題 出事會怎樣 要先有什麼 在哪裡 嚴重度 計數
F1 解密後的帳密被寫進工單資料表存著,而且沒人讀它 拿到資料庫備份/唯讀帳號的人,直接得到客戶正式機器的可用帳密 有人按過一次執行 + 之後取得資料庫讀取權 detection_orchestration_service.py:480 MEDIUM 本棒新增
F2 查執行歷史沒檢查是不是專案成員 同租戶任何人看得到別專案的掃描歷史與報告檔編號,再拿編號下載完整報告 一個有效登入身分 + 知道別專案的任務編號 detection_orchestration_service.py:1667 MEDIUM 重複第 88 項
F3 換工具時掃描範圍不重驗,超大網段被逐台展開 後端記憶體被吃爆,API 服務變慢或倒掉 專案管理者身分 + 兩次修改 + 按執行 detection_orchestration_service.py:555 MEDIUM 重複第 105 項

淨新增 1 條(F1),登記跨 arc 總表。F2 落在後半段(1,667 行)本不屬本棒,且已是第 88 項;F3 的落點雖在前半(555 行),但缺口本體與修法在 D3-2 已完整記為第 105 項,本棒只是從另一端又看到同一件事。

§5

Findings

F1 — 解密後的帳密被寫進工單資料表存著,而且從頭到尾沒有任何程式讀它(MEDIUM,confidence high)本棒新增

這是什麼問題。 客戶要掃描自己的機器,系統得有登入用的帳密——SSH 的 root 密碼、Windows 遠端管理的帳密、OpenVAS/ZAP/SonarQube 的管理員 token。這些東西存進資料庫時是加密的,這是產品刻意做的保護(負責這件事的元件在 domain/ports.py 的註解裡自己寫著「缺了就是明文落庫」)。

按下「開始掃描」時,系統必須把它解開才能用。問題出在解開之後多做了一步::480 這一行把解出來的明文原封不動塞進派工參數,而派工參數整包會被寫進 agent_tasks 這張表的 params 欄位。那個欄位是普通的 JSON 欄位,沒有再加密。

於是同一份帳密有兩種存在形式:tenant_detection_tool_configs 裡那份是密文(受保護),agent_tasks.params 裡這份是明文(不受保護)。加密這道防線等於被繞過了。

最關鍵的一點:存下來這份沒有任何程式讀過。 這是首腦核對時查出來的,比工具原本講的更乾淨——詳見下方「修法成立性核對」。

出事會怎樣。 任何拿得到資料庫讀取權的人,一句 SQL 就得到客戶正式機器的可用帳密:

SELECT params->'_credentials' FROM compliance.agent_tasks;

「拿得到讀取權」的情境比想像中多:資料庫備份檔、pg_dump 出來的檔案被複製到筆電、給報表用的唯讀帳號、唯讀的複本資料庫、或是別處的一個唯讀型 SQL 注入漏洞。這些情境本來都只該洩漏業務資料,現在額外附送一整份可以直接登入客戶機房的帳密。

而且這份明文會一直留著。agent_tasks 這張表沒有任何清理機制(見下方查證),所以每一次執行掃描就多留一份,累積下去。

要先有什麼才打得到。

  • 有人按過至少一次「開始掃描」(或重跑),而且那個工具有設定帳密。實務上這是常態——目前六個工具全部宣告為需要帳密。
  • 攻擊者之後取得 compliance.agent_tasks 的讀取權:資料庫帳號、備份、dump 檔、複本,或唯讀型 SQL 注入。

第二個條件是「額外還要發生一件事」,所以是中風險不是高風險——但它的性質是把別的事故放大:原本只是「備份檔外流」,現在變成「備份檔外流+客戶機房被登入」。

在哪裡。

  • 缺口本體:jedi-detection/jedi_detection/app/service/detection_orchestration_service.py:480 —— params["_credentials"] = creds
  • 明文從哪來:同檔 :1397 _resolve_credentials() —— 解密 tenant_detection_tool_configs.credentials_encrypted
  • 寫進資料庫:同檔 :488-491 create_task(params=params)
  • 落地的欄位:compliance-manager-be/scripts/sql/packages/jedi_remote_agent/001-remote-agent-tables.sql:80 —— params JSONB NOT NULL DEFAULT '{}'::jsonb,沒有加密包裝

修法成立性核對(首腦補查,這是本條能不能修的關鍵)。

工具的建議是「刪掉 :480,因為心跳組裝時會另外現場注入帳密,存這份是多餘的」。這句話屬實,而且實際情況比它講的更乾淨。逐環查證如下:

  1. 代理程式領單時帳密確實另有來源。 代理程式定期「心跳」問雲端有沒有新工單,組工單內容的是主專案的 infra/remote_agent/adapter/detection_task_payload_provider.py。它的 build_pending_payloads()(:48)組出來的每張工單長這樣:

    {
        "uid": ...,
        "detection_tool_id": ...,
        "detection_tool_code": ...,
        "params": self._inject_source_file_limits(params),
        "credentials": self._resolve_tool_credentials(t.detection_tool_id, tenant_id),  # ← :68
    }

    credentials 是頂層獨立的鍵,由 _resolve_tool_credentials()(:109)當場去查 tenant_detection_tool_configs 重新解密產生——完全不看 params 裡有什麼。

  2. 代理程式讀的是哪一個鍵。 查代理程式本體(evidence-agent repo)core/task_executor.py:101:

    connector = get_connector(
        task.get("detection_tool_id"), params, task.get("credentials") or {}, ...
    )

    它讀的是 task["credentials"],也就是心跳當場解的那份。全 repo grep _credentials 的結果:代理程式端沒有任何一處讀取這個鍵,只有三處註解提到它(都是在講「_ 開頭是雲端注入的內部欄位」這個命名慣例)。

  3. 雲端這邊也沒人讀。 三個 repo 一起 grep,params["_credentials"] 的讀取端掛零:唯一的寫入在 :480,其餘全是註解或 docstring 提及。反而有一處是專門把它剝掉的——_visible_scan_params()(:2271)在回 API 之前會把所有 _ 開頭的欄位濾掉,註解寫明理由是「_credentials:租戶層憑證明文,派工時塞入」。換句話說,程式自己知道這裡有明文、自己在出口做了補救,但補救的是 API 回應,資料庫裡那份沒有被處理。

  4. 同一份檔案裡已經有正確的做法可以對照。 _resolve_source_file() 的註解(:796-800)在講另一個欄位(限額)為什麼不存進 JSON 時寫道:「改由心跳組裝時當場注入(與 _credentials 同時機)」——設計者本人已經認定帳密是「心跳當場注入」那一類,:480 這行存進去的是與該設計矛盾的殘留。

結論:刪掉 :480 之後,代理程式照樣拿得到帳密,修法成立。

agent_tasks 有沒有清理或保存期限 —— 沒有(首腦查證)。

查了兩處:

  • 排程工作清單(core/scheduler.py):全系統共九支定期工作,分別是完整性抽查、Drive 同步、webhook 續約、框架解析暫存清理、授權到期狀態機、防竄改同步、檢測執行逾時收斂、任務綁定孤兒清理、日誌分區維護。其中沒有任何一支碰 agent_tasks。名字最接近的 detection_execution_timeout 收斂的是 detection_executions(執行紀錄)的狀態,不刪工單、也不清參數。
  • 程式碼層:app/、common/、core/ 全域 grep agent_task 配上清理類關鍵字(purge/cleanup/delete/retention/expire),零命中。

所以那份明文沒有時限,只會累積。這讓 F1 的嚴重度撐在 MEDIUM 而不是更低——若有 30 天保存期限,風險窗口至少有界。

怎麼修。 兩步,兩步都要做:

  1. 拔掉源頭:刪掉 :480 的 params["_credentials"] = creds。這一行之後 creds 在 _dispatch_one 裡就沒有其他用途了,連帶可以把參數傳遞鏈上多餘的 creds 一併收掉(start_execution 仍要保留 _resolve_credentials 的呼叫——:219-226 用它做「沒設帳密就擋下來」的前置檢查,那個檢查要留)。

  2. 清存量:寫一支 migration 把既有資料裡的這個欄位拿掉。這一步不能省——光改程式只讓「以後不再新增」,資料庫裡已經躺著的那些明文原封不動。

    UPDATE compliance.agent_tasks
       SET params = params - '_credentials'
     WHERE params ? '_credentials';

    套用規範照 sql-migration skill:cmmgr 帳號、--single-transaction、收尾寫 schema_migrations。先只套 DEV,STG/POC 等決策者放行(環境異動鐵律)。

⚠️ 修完要實測一次完整掃描。這條路徑的驗證不能只看程式碼——_dispatch_one 改完之後,要實際派一張需要帳密的工單(例如 OpenSCAP)、讓代理程式領走、確認它真的登入成功。理由是本條的修法建立在「代理程式讀的是另一份」這個判斷上,判斷雖然逐環核對過,但沒有實跑驗證;掃描能不能登入成功是這個判斷唯一的硬證據。

驗證。 3/3 三位檢查員確認成立(可達性、影響、既有防護三個角度),嚴重度維持 MEDIUM,沒有被降級。三位各自獨立追同一條鏈:解密 → :480 塞入 → create_task → 落進沒加密的 JSON 欄位,並各自確認了中間沒有任何一處做過遮蔽或剝除。

首腦核對註記。 屬實,逐環開檔核對過::480 確實把明文塞進去、:1397 確實是解密來源、:488-491 確實寫進資料庫、SQL 定義確實是普通 JSONB 沒有加密。工具聲稱的「心跳會另外現場注入」經三個 repo 交叉核對確認屬實(見上),且進一步查出代理程式端沒有任何一處讀 _credentials——這比工具的說法更強,代表刪除的風險比工具評估的還低。agent_tasks 無清理機制亦經排程清單與程式碼雙重查證確認。

F2 — 查執行歷史沒有檢查是不是專案成員(MEDIUM,confidence high)重複第 88 項,不計新發現

這是什麼問題。 同一個檔案裡有九支方法會對「某個任務」做事,其中八支都會先問一句「按的人是不是這個專案的成員」,只有查執行歷史那支沒問。它只檢查了租戶(也就是「是不是同一家客戶」),所以同一家客戶底下、不同專案的人,拿著別的專案的任務編號就查得到。

出事會怎樣。 查得到的內容包括對方專案掃了哪些內網網段、用什麼工具、失敗訊息,以及報告檔的編號。報告檔編號等同下載憑證——下載端點只驗「有沒有登入」不驗「這份報告是不是你的」,所以拿到編號就能下載對方專案的完整弱點掃描報告。

在哪裡。 detection_orchestration_service.py:1667 list_executions。

計數處理。 這條是第 88 項,前面棒次已經記過,本棒不重複計入。另外它落在 1,152 行之後,依 D3-4a/D3-4b 的分界本來就不屬本棒範圍——工具因為整檔一次讀而撞到。D3-4b 那棒若再撞到,同樣標重複即可。

驗證。 3/3 檢查員確認成立。本棒首腦核對時順帶確認了「八支有、一支沒有」這個對比屬實(見下方重點①的逐支清單),與第 88 項的既有記載一致,沒有新增資訊。

F3 — 換工具時掃描範圍不重驗,超大網段被逐台展開(MEDIUM,confidence medium)重複第 105 項,不計新發現

這是什麼問題。 「掃哪些機器」這欄有台數上限,但上限只在存檔時檢查、而且只檢查「這次送上來的值」。先綁一個不受上限管的工具把 10.0.0.0/8 存進去,再只換工具不帶參數,那個超大網段就留在原地跟到新工具底下。按下執行時,派工端會把它逐台展開成 1,670 萬個位址,後端記憶體被吃爆。

在哪裡。 展開那一端在 detection_orchestration_service.py:555(本棒範圍內),寫入端的缺口在 detection_job_binding_handler.py:201-204(D3-2 範圍)。

計數處理。 這條是 第 105 項,D3-2 那棒已經從寫入端完整記過,連修法(寫入端重驗 + 展開端硬上限)與「展開端刻意不擋是設計判斷、硬上限要訂寬」的注意事項都寫清楚了。本棒只是從展開端這一側又看到同一件事,不重複計入,也沒有新增資訊。

驗證。 3/3 檢查員確認成立,confidence 記為 medium(攻擊路徑要兩步操作,是讀程式碼推出來的、沒有實跑)。

§6

卡片重點逐項人工查證

卡片重點有兩項:①「誰能按」②「憑證解密後流向」。工具碰到了②(就是 F1),①一條候選都沒提,首腦開檔補查。

① 誰能按:九支寫入端點的守門 — ✅ 沒問題(工具未報,人工查證)

本棒範圍的七支方法(加上範圍外的兩支一併核對)逐支開檔確認,全部都有專案成員檢查,沒有缺口:

方法 行號 成員檢查 額外守門
start_execution 開始掃描 :196 ✅ 任務狀態須為處理中、無執行中群組、帳密須已設定
cancel_execution 取消 :871 ✅ —
rerun_assignment 重跑單台 :925 ✅ —
cancel_assignment 取消單台 :1007 ✅ —
cancel_group 整組取消 :1032 ✅ —
delete_execution 刪單筆 :1097 ✅ 只准刪 failed;跨租戶回 404 不回 403
delete_group 整組刪 :1127 ✅ 全組皆 failed 才准;空群組也擋
start_assignment_now 立即開始(範圍外) :1470 ✅ —
list_executions 查歷史(範圍外) :1667 ❌ 缺口=F2/第 88 項

守門的實際判定邏輯。 assert_project_participant 走的是 FR-048 軸③(資源域守門,在 app service 層而非 route decorator,符合規範)。判定鏈是:任務 → 流程執行 → 反查專案 → 查使用者在該專案的角色;查無角色就擋(fail-closed)。任一角色都放行(manager/auditor/reviewer/viewer),所以它擋的是「不是這個專案的人」,不是「角色不夠大」。

路由層另有一道授權鎖。 這些端點在路由層都掛了 @require_license("plugin"),也就是客戶得買了檢測模組才能用。這是產品授權層,不是權限層——同一租戶內所有人的授權狀態相同,所以它擋不了「同租戶跨專案」,F2 的缺口不會因為有這道鎖而被補上。

兩個設計細節值得記。

  • 跨租戶一律回「查無此物」不回「你沒權限」(delete_execution :1090-1095、_require_group)。註解寫明理由是「避免成為探測管道」——回 403 等於告訴對方「這個編號是存在的」,攻擊者可以用它逐一試出有效編號。這個處理是對的。
  • 刪除限制得很嚴且理由充分:只能刪失敗的紀錄。成功的掛著報告與證據,刪了證據鏈就斷;執行中的要先取消;已取消的是使用者自己按的、屬有意義的軌跡。整組刪還多一層「含重跑歷史在內全部都是失敗」才准,避免夾著一筆成功的被整組刪掉。判定:限制方向正確,沒有把破壞力開得太大。

判定:重點①這一項沒有問題,唯一的缺口是已記在案的第 88 項。

② 憑證解密後流向 — ⚠️ 有一處缺口(就是 F1),其餘處理正確

完整的流向追蹤:

解密只發生在一處,_resolve_credentials()(:1397)。它的設計有兩點值得記:

  • 查詢時才解析,不在綁定時寫死。 註解點明使用者可能「先建任務、後設工具憑證」,綁定當下寫死會拿到空值。而且綁定表那個 tenant_config_id 欄位實務上一律是 NULL(寫入端從沒寫過它),所以主要靠 (租戶, 工具) 反查——那個組合有唯一約束,定位得了,且歷史 NULL 資料自動自癒、不必補資料 migration。判定:設計合理。
  • 沒設帳密就提前擋下來(:219-226)。註解的理由站得住:空帳密派下去代理程式必炸,而且錯在雲端卻只能從代理程式的 log 看到——提前擋並講清楚「要去設定工具憑證」,比讓它炸在遠端好查得多。零帳密工具有例外處理(否則一個合法狀態會永遠開始不了),且往嚴的方向失敗(查不到工具一律當作需要帳密)。判定:方向正確。

流向有三條,兩條正確、一條是缺口:

  1. → 代理程式(心跳組裝當場解):✅ 正確。走 mTLS 通道下發,代理程式用完即丟不落地,不存在本地。這是設計上的正規路徑。
  2. → API 回應:✅ 有擋。_visible_scan_params()(:2271)把所有 _ 開頭的欄位濾掉,且單筆與批次兩條讀取路徑共用同一份剝除實作——註解自己寫明「兩份剝除實作就是『這裡忘了剝』的溫床(憑證明文會落進 api_logs.response)」。這個共用是對的。
  3. → 資料庫 agent_tasks.params:❌ 缺口,就是 F1。第 2 點的剝除只保護 API 出口,保護不到資料庫本身。

一個容易誤判的地方要講清楚。 同一個欄位裡還有另一種機敏資料——「任務層敏感參數」(例如被掃網站的登入密碼),那一類在資料庫裡是密文,因為它在綁定寫入時就被包成加密信封了(D3-2 查證過),心跳組裝時才解。所以 agent_tasks.params 裡面是混的:任務層那些是密文(安全),_credentials 這個鍵是明文(就是 F1)。看到「這欄位裡有密文」不代表整欄安全,兩者要分開看。

§7

這份結果可信到什麼程度

分兩層講:

「報出來的這條存在嗎」——可信度高。 F1 的每一環首腦都開檔核對過,而且修法的成立性做了三個 repo 的交叉查證(雲端組裝端、代理程式讀取端、既有設計註解),三邊結論一致。三位檢查員從三個不同角度獨立追同一條鏈,三票全過。

「只有這條嗎」——可信度中等,有四個已知限制。

  1. 強度是 low,只有一位研究員讀一遍,不是多位分工交叉讀。低強度的設計目的是快速篩,不是窮盡。
  2. 檔案大、強度低,這是本棒最該留意的一點。 2,498 行由一位研究員一次讀完,是本 arc 目前單棒行數最高的一次(D3-2 是 1,328 行、D2 切成四小棒每棒約 1,600 行)。切成 4a/4b 是為了控制首腦核對的負荷,但工具那一端並沒有跟著切——它還是整檔讀。所以「前半 1,151 行被讀得多仔細」沒有獨立保證。
  3. 重點①的「沒問題」是人工定向查證,不是面板投票的結果。 工具對這一項一條候選都沒提,九支端點的守門是首腦逐支開檔比對出來的。這個結論沒有三票背書,可信度低於 F1 那條。查的是「有沒有呼叫守門函式」與「守門函式的判定邏輯對不對」,若有人用別的路徑繞過這整套(例如直接對資料庫下 SQL),本次查證看不到。
  4. F1 的修法沒有實跑驗證。 「刪掉之後代理程式照樣拿得到帳密」是讀三個 repo 的程式碼推出來的,邏輯鏈完整但沒有真的派一張工單試。修的時候務必實測一次完整掃描(見 F1 的修法注意事項)。

verification.status 為 verified:三位檢查員對 3 條候選各投一票,9 票全數投出,沒有漏投,票數由工具自己的程式碼統計、不是任何 agent 自報。

§8

執行概況

項目 數字
run ID wf_3487f8c6-0ca
掃描 commit 955e40964baa3cfc0e8766078db7d65075d3c50b(工作區有其他未 commit 改動,與本棒無關)
範圍 1 檔 2,498 行(本棒歸屬前 1,151 行)
強度 low(一位研究員 + 三票面板)
研究員 派 1 位、回 1 位、零重試
面板 3 條候選 × 3 位檢查員 = 9 票,全數投出
總耗時 約 102 分鐘(04:17 啟動 → 05:59 報告產出)
候選 → 成立 3 → 3(零駁回)
淨新增 1 條(F2=重複第 88 項、F3=重複第 105 項)
驗證輪數 1
驗證章 verified
工具原始產物 jedi-detection/CLAUDE-SECURITY-20260919-041735/(不入版控)

三個觀察,留給後續切棒參考。

  1. 零重試,但耗時是 D3-2 的 1.9 倍不到。 2,498 行對 1,328 行是 1.88 倍,102 分鐘對 140 分鐘反而更短——這一棒是單獨跑的(D3-2 是與另一支平行跑的)。單點樣本不足以下結論,但至少說明 2,500 行這個量級一位研究員扛得住,沒有觸發停滯判定。
  2. 零駁回是本 arc 少見的。 前幾棒常有候選被面板否決(D3-2 是 2 選 1)。這棒 3 條全過,三條也確實都經得起首腦複核——但其中兩條是前面已經記過的,等於工具在同一個檔案裡重新發現了已知問題。這是低強度整檔掃的必然副作用,不是工具變準了。
  3. 「切棒」目前只切首腦這一端,工具那一端沒切。 這是本棒暴露出來的機制落差(見上方可信度第 2 點)。若後續要讓行號分界真的生效,得想別的辦法(例如把檔案複製一份截斷後掃,或改用支援範圍的強度),現行做法只能控制核對負荷、控制不了研究深度。