---
title: 稽核流程
eyebrow: Guidant AI 資安檢視 · 模組報告
h1: 稽核流程（jedi-flow-engine）
lede: 每個稽核專案從規劃走到結案，靠的就是這一塊。**這裡有一個很不一樣的發現**——共用套件本身的 49 個檔案沒查到新問題，真正的問題出在主系統把它接上來的那段程式，而那段是整個重寫的。**這塊也是全案第二塊拿到實測數字的**：第 3 條的「伺服器會算不完」已經實際跑出時間，不再只是讀程式碼的推論。
---

## 這塊在產品裡做什麼

每一個稽核專案都照一張「流程圖」在跑：規劃 → 稽核 → 改善 → 結案。

::: grid2
::: {.card .plain}
#### 它平常在做什麼
1. 決定**每個階段誰能按「推進」**、按了之後系統要做什麼
2. 管理**流程圖本身**——內建範本、客戶自訂範本、每個稽核輪次的凍結副本
3. 管理每個任務的**完成、退回、留言、上傳證明文件**
:::
::: {.card .crit}
#### 為什麼這塊特別要緊
稽核的**證明文件清單**與**階段歷程**都掛在這裡。

階段歷程裡有每一次退回的完整理由——那是稽核人員親筆寫的「哪裡有問題」。而證明文件是稽核結論的依據。**這兩樣外洩或被改動，稽核報告就站不住。**
:::
:::

---

## 檢視軌跡

**檢視期間**：2026-09-12 ～ 2026-09-14，另 2026-09-23 補三輪｜**範圍**：157 個檔案（主系統這端 78、共用套件本體 79）；另加主系統任務那一端 13 個檔｜**共 9 輪**（原 6 輪＋任務接線 3 輪）｜**另加 2026-09-20 一次實際測試**

> **原始技術報告**放在需求中心的 [FR-088 站](https://guidantai-feature-doc.jedicotech.com/FR-088-2609-flow-engine-security-scan/)，檔名見下表最後一欄。那裡有每一輪的完整技術細節、檔案清單與逐條推理，供需要深究或稽核抽查時查閱。

| 輪次 | 查什麼 | 檔數 | 覆核投票 | 結果 | 原始報告檔名 |
|---|---|---:|---|---|---|
| **H2** | 任務完成、退回、留言、證明文件 | 23 | 30 票全投完（**其中 18 票投在範圍外的舊案上**） | 找到 3 條（1 高 2 中）＋人工另查 1 條 | `scan-H2-task-comment-evidence` |
| **H1** | 稽核階段推進與回退 | 21 | 33 票全投完 | 找到 4 條（3 中 1 低） | `scan-H1-stage-advance-rollback` |
| **P1** | 流程圖範本與流程圖檔解析（**共用套件本體**） | 30 | 9 票全投完、三位意見一致 | 找到 2 條中風險；**原列八項懷疑，五項經實測不成立** | `scan-P1-bpmn-parsing-and-templates` |
| **H3** | 流程範本管理 | 13 | 30 票全投完、三位意見一致（**其中 24 票投在範圍外的舊案上**） | 找到 2 條（1 中 1 低） | `scan-H3-flow-template-management` |
| **H4** | 流程執行的主系統服務與接線 | 21 | 27 票全投完 | 執行者報 3 條，統籌者判定**只有 1 條是新的**、2 條與既有項目重複 | `scan-H4-execution-host-service` |
| **P2** | 流程執行核心（**共用套件本體**） | 49 | 6 票全投完 | **範圍內沒有新問題**；最有價值的產出是一張對照表（見下） | `scan-P2-execution-core` |
| **實測** | 實際動手驗證第 3、4 條 | — | （實測，不投票） | **第 3 條確認成立且拿到時間數字；第 4 條的「會癱瘓」部分證實不成立、已更正** | （見下方第 3、4 條兩節） |
| **W2**（任務接線） | 任務那一端怎麼使用問卷與檢測 | 6 | 15 票全投完 | 淨新增 1 條（第 17 條，任務清單不查專案成員），其餘與既有條目重複 | `scan-W2`（在 FR-115 站） |
| **W4**（任務接線） | 任務的 Excel 匯入匯出直接查問卷、設備資料 | 3 | 18 票全投完 | 淨新增 3 條（第 18、20、21 條） | `scan-W4`（在 FR-115 站） |
| **W8**（任務接線） | 管理人「強制開始」卡住的任務 | 4 | 6 票全投完 | 淨新增 2 條（第 19、22 條） | `scan-W8`（在 FR-115 站） |

::: {.callout .warn}
**四輪的覆核票數裡，有一半以上投在範圍外的舊案上——這要講清楚**

覆核票是投在「這一條發現成不成立」上。有幾輪執行時撈到的其實是之前已經記錄過的舊問題，票就投在那上面了，**等於這一輪真正投在該查的檔案上的票數，比帳面數字少**。

具體是：H2 的 30 票裡只有 12 票投在該查的 23 個檔案上；H3 的 30 票裡只有 6 票投在該查的 13 個檔案上；H4 的 27 票裡有 18 票投在舊案上。

**所以這幾輪不能宣稱「這些檔案只有這幾個問題」**，只能說「這一輪在這個範圍內找到這幾條」。另外 H3 那一輪原本負責執行的人沒有交付成果，由統籌者自己開檔補查完成。
:::

::: {.callout .ok}
**這個 arc 的證偽比證實更有價值**

開工時列的懷疑，經查證後**有相當比例不成立**——而且幾條不成立的理由都被寫清楚了：

- 「一張惡意流程圖檔可以讀走伺服器上的任意檔案」——實測被解析工具擋住
- 「超大巢狀結構的流程圖檔可以撐爆記憶體」——實測擋住（五萬層都過不了）
- 「跨專案推進別人家的稽核」——**寫入路徑不成立，但擋住它的是下游套件、不是這一層**
- **「檢查流程圖那支功能可以讓伺服器癱瘓」——實測不成立**，它走的是另一套檢查邏輯，處理一千個節點只要 0.027 秒（見第 4 條）

最後兩條的處理方式值得記：報告裡把「是什麼擋住了它」「為什麼原本判斷錯了」都寫清楚了，因為**哪天新增一支不走那個下游套件的功能入口，洞就自己開了**。
:::

---

## 問題一覽

**「出事會怎樣」那欄講的是業務影響**——誰受影響、損失什麼，不是技術現象。

| # | 問題 | 風險 | 分類 | 出事會怎樣（誰受影響） | 怎麼修 | 狀態 |
|---|---|---|---|---|---|---|
| **1** | 知道一個任務編號就能讀走別家客戶的稽核證明清單 | 🟠 高 | 只驗登入、不檢查歸屬 | 拿得到檔名、描述、雲端硬碟連結、上傳者帳號、檔案指紋與**檔案編號**——**拿到檔案編號之後可以接著直接下載檔案本體**。這不只是一份清單，是一串通往檔案的鑰匙，**與[檔案上傳那塊](M02-file-upload.html)的第 1、3、7 條是同一條鏈**：那邊是「拿到編號就能下載」，這邊是「連編號都送給你」。**同一個檔案裡的新增、更新、刪除都有做檢查，只有讀取漏掉，明顯是漏寫不是設計** | **統一修法**——見[下方這一節](#principle-2) | ✅ **已修（CM-2059）** |
| **2** | 流程留言的讀與寫都只檢查有沒有登入 | 🟠 高 | 只驗登入、不檢查歸屬 | **這是這一組五條裡唯一「能寫」的，其他四條只能讀。** 任何登入者能讀走別人專案的整串稽核討論，**也能往裡面塞留言——而留言會即時推播給該專案全體成員**。攻擊者拿別人的專案發一則留言，全體成員收到推播，**看起來就是同事發的**，可以拿來騙人點連結或交出資料。同一個功能模組裡的「完成任務」「退回任務」都有檢查，只有留言漏掉 | **統一修法**——見[下方這一節](#principle-2)；另外補留言的長度與筆數上限 | ✅ **已修（CM-2035）** |
| **3** | 一張惡意設計的流程圖可以讓伺服器永遠算不完 | 🟠 高 | 資源耗盡 | 一張特製的流程圖存進去之後，**只要有人打開那一輪稽核的頁面，就會替攻擊者觸發**——伺服器一條處理程序算不完、不報錯也不留紀錄。**同時打中四次，整個產品對所有客戶停止回應**。⚠️ **不一定要有人存心**——流程畫得太複雜的客戶可能無意間就做出同樣的效果 | 五件事，有先後順序——見[下方這一節](#bpmn-dos)。走訪記已走過的連線並加深度／節點上限，同一份清單不再重算 | ✅ **已修（CM-2059）** |
| **4** | 「檢查流程圖」這支功能任何登入者都能打，而且不限大小 | 🟡 中 | 只驗登入、不檢查歸屬 | 同一個檔案裡其他功能都掛了權限檢查，**只有這一支沒有**；送進來的內容也沒有長度上限、節點數與連線數都不設限。⚠️ **原本報告寫「打幾次就讓全站停擺」，實測後確認不成立**（它走的是另一套檢查邏輯，處理一千個節點只要 0.027 秒），該敘述已移到第 3 條。目前的實質影響是「沒有權限的人可以用它，而且可以丟很大的東西進來」。**之所以還是中、沒有降到低**：低通常留給「有別的關卡擋著、實際打不到」的情形，**這一條一道關卡都沒有**，只是打進來的後果輕 | 補權限檢查、加內容長度與節點數上限——見[下方這一節](#check-endpoint)。**已修：端點補上與其他寫入功能同等的權限檢查，並在後端加上節點數 100 個、分岔深度 8 層的上限，超限時維持原本 200＋警告清單的回應方式，不改成錯誤** | ✅ **已修（FR-114.1-8）** |
| **5** | 知道一個稽核輪次編號就能讀走別家客戶的階段歷程 | 🟡 中 | 只驗登入、不檢查歸屬 | 拿得到每次推進或退回的操作者帳號、暱稱、從哪一階段到哪一階段，以及**退回理由的完整內容**——那是稽核人員親筆寫的「哪裡有問題」。網址上明明帶了專案編號，**程式從頭到尾沒拿來用** | **統一修法**——見[下方這一節](#principle-2) | ✅ **已修（CM-2035）** |
| **6** | 「查目前在哪個階段」用你填的專案編號查角色、用你填的輪次編號撈資料，兩者不核對 | 🟡 中 | 只驗登入、不檢查歸屬 | 角色不符也不會擋下來，只是把「能不能推進」標成否、**其他資料照樣整包回傳**。**連帶問題**：「推進」與「退回」兩支寫法完全一樣、都沒檢查，**現在沒事是靠運氣、不是靠設計**——只是剛好被下游套件的另一道檢查擋住 | **統一修法**——見[下方這一節](#principle-2)；並把推進與退回一併補上，**不要繼續依賴下游套件擋** | ✅ **已修（CM-2035）** |
| **7** | 一般成員（定義上「只能看、沒有待辦」）可以完成別人的稽核任務、把流程退回上一關 | 🟡 中 | 要先做產品決策 | **稽核紀錄上的經手人會變成這個只能看的人**，連帶任務、問卷、流程的狀態都被他改動。**影響面比表面大**——弱點掃描模組有八支會改狀態的功能吃的是同一道檢查，所以他同樣可以對客戶的正式機器發動帶帳密的掃描、反覆取消再重派、刪掉失敗的執行紀錄 | **決策者已裁定：這個角色不可以完成或退回任務，只能留言**。修法是在原本的「是不是這個專案的人」之後，再加一道「是不是這個任務的負責人、或是這個專案的管理者」——**在 `common/authz/` 另開一支嚴格版守門 `assert_workflow_job_operator`（不收嚴既有那支，否則會連帶改掉佐證文件的增刪改），完成與退回兩支改吃它；弱點掃描那八支已由卡 1-2 接手改吃同一支（經 `WorkflowExecutionService.assert_job_operator` 包裝）** | ✅ **已修（FR-114.1-1／FR-114.1-2）** |
| **8** | 讀取流程範本時無條件解析內容、沒有防呆 | 🟡 中 | 外部送什麼就收什麼 | 一筆空白內容的範本，會讓**所有人的清單頁面都出錯、而且不會自己恢復正常** | 寫入端補格式檢查（空白或不合法一律拒絕）；讀取端遇到空值就跳過解析。寫入端顯式區分未送與空字串，讀取端解析失敗退成 0 個任務 | ✅ **已修（CM-2059）** |
| **9** | 流程範本的新增與修改，完全不跑任何檢查 | 🟡 中 | 外部送什麼就收什麼 | **只有「發布」那一支會檢查內容**，新增與修改都不檢查。**所以沒檢查過的內容存得進資料庫**——第 3、8 條講的那些惡意或壞掉的流程圖，從這個門進來不會被攔。**它本身不是洞，是讓別的檢查失效的放大器**——所以要跟第 3 條綁在同一張工單 | 範本的新增與修改也要跑同一套檢查——見[下方這一節](#validation-bypass)。新增與修改改為呼叫 publish 既有的兩支驗證，規模上限擴及所有寫入路徑 | ✅ **已修（CM-2059，後半待裁）** |
| **10** | 啟動一輪稽核去複製範本時，不要求那份範本已經發布過 | 🟡 中 | 外部送什麼就收什麼 | 取範本的查詢只過濾「啟用中」、**沒有過濾「已發布」**，所以一份從沒檢查過的草稿也能被複製去啟動一輪真實的稽核流程。**與第 9 條合起來，等於前面講的檢查連「一定會被跑到」都做不到**。同樣是放大器、不是洞本身，要跟第 3 條綁在同一張工單 | 起輪次時要求範本必須是已發布狀態——見[下方這一節](#validation-bypass)。同上，兩條同一張工單一起處理，後半（起輪次要求已發布）尚待裁定 | ✅ **已修（CM-2059，後半待裁）** |
| **17** | 規劃頁的任務清單不問你是不是這個專案的人 | 🟡 中 | 只驗登入、不檢查歸屬 | 同一家客戶裡任何有專案模組的帳號，帶一個別專案的編號、把查核項目換成公開的標準條號一條條查，就列出那個專案每個查核項目底下的任務——指派人、部門、設備、問卷、檢測工具設定（帳密有遮）。**拿到的任務編號正好是改、刪、匯入別人任務的材料** | 清單一開頭補「你是不是這個專案的成員」，專案編號改必填 | ⬜ **未修，歸 1.21.1 hotfix**（CM-2289） |
| **18** | 批次匯入任務 Excel 的「確認」那一步，可以順便改掉別的專案的任務 | 🟡 中 | 只驗登入、不檢查歸屬 | 專案 A 的管理者把 Excel 的「任務識別碼」欄換成專案 B 的任務、指派人填自己。「驗證」那步會報錯，但「確認」那步不重跑這道比對，**一次批次改掉 B 的任務指派人、類型、部門、設備**。程式註解寫著「防止未經驗證的確認」，讓人以為守住了 | 確認時只收「這個計畫底下的任務」，並核對計畫所屬的專案就是網址上那個專案 | ⬜ **未修，歸 1.21.1 hotfix**（CM-2289） |
| **19** | 甲專案的管理人，可以把乙專案卡住的任務「強制開始」 | 🟡 中 | 只驗登入、不檢查歸屬 | 甲的管理人網址填自己的專案、任務填乙專案一張卡在合流的任務，就把它推進：**乙的主管收到通知、但兩條分支的證據其實沒收齊；稽核事件記在甲專案名下**，事後看不出他動的是乙。只推得動「待辦且真的卡在合流」的任務 | 查到任務後確認它屬於網址上那個專案，不屬於就回「找不到」。⚠️ 修之前先查有多少任務沒有指派紀錄——用指派紀錄認歸屬會誤擋正常操作 | ⬜ **未修，歸 1.21.1 hotfix**（CM-2289） |
| **23** | 匯入任務的 Excel 完全沒有上傳檢查，而且整份檔案一次攤開在記憶體裡 | 🟡 中 | 資源耗盡 | **有匯入權限的人丟一份特製的 Excel，就能讓伺服器記憶體被吃光**——實測 9.8MB 的檔花 22 秒、吃掉 1.46GB；連送幾份，所有客戶一起連不上。這是「試算表炸彈」的第五個入口，先前修好的四個入口沒列到這一條 | 讀檔前先檢查「解開後有多大」，並改成一列一列讀 | ✅ **已修**（CM-2203，commit `eb4ff79cd`／`040c3c827`，1.21.0 出貨）。主系統掃描查出 |
| **11** | 流程範本的列表與單筆讀取沒有掛上權限檢查 | ⚪ 低 | 只驗登入、不檢查歸屬 | **前端選單擋住了看不到的人，但功能本身是敞開的**——同一家客戶內沒被授權的帳號可以讀走全部流程範本與系統內建範本的完整內容（階段設計、角色指派、判斷條件）。跨客戶讀不到（那張表的資料庫隔離有開，已實際查證） | **統一修法**——見[下方這一節](#principle-2)；或者產品決定「人人可看」就把權限與選單綁定一起拿掉，不要前後端規則不一致 | ✅ **已修（CM-2035）** |
| **12** | 推進或退回階段時，畫面上顯示的操作者名稱可以由呼叫端自己填 | ⚪ 低 | 外部送什麼就收什麼 | 只影響畫面顯示的名稱，**真正代表身分的欄位是伺服器自動填的、不受影響**；而且要先有推進這個階段的權限（等於自己人偽造顯示名稱） | 改成一律由伺服器強制覆寫顯示名稱，不採用呼叫端傳入的值。顯示名改由伺服器強制指派，ctx 加白名單 | ✅ **已修（CM-2059）** |
| **20** | 匯出任務 Excel 時，檢查的專案和實際匯出的專案可以是兩個 | ⚪ 低 | 只驗登入、不檢查歸屬 | 專案 A 的一般成員把網址裡的計畫編號換成專案 B 的，系統確認「你是 A 的成員」之後，匯出的是 **B 的整張任務表**（任務編號、指派人帳號、部門、設備）。限同一家客戶、內容多是規劃資訊 | 核對計畫所屬的專案就是網址上那個專案 | ⬜ **未修，歸 1.21.1 hotfix**（CM-2289） |
| **21** | 匯入任務的「驗證」與「重新驗證」兩步完全不檢查身分 | ⚪ 低 | 身分驗證缺失 | 同一家客戶任何登入帳號都能呼叫，拿回傳的錯誤訊息試探「某任務屬不屬於某計畫」「某帳號、部門、設備名稱存不存在」。**不會改資料** | 兩步開頭補「計畫屬於網址專案＋你是成員」 | ⬜ **未修，歸 1.21.1 hotfix**（CM-2289） |
| **22** | 「查卡住狀態」誰都能查別的專案 | ⚪ 低 | 只驗登入、不檢查歸屬 | 同一家客戶任何登入帳號拿一個任務編號、網址隨便填個專案，就讀到那任務在等哪幾條分支：任務名稱、流程節點、狀態、**每條分支的任務編號**（正好是第 19 條的材料）。說明文字寫「任一專案參與者可看」，程式沒做這件事 | 補「任務屬於網址專案」與「你是成員」兩道 | ⬜ **未修，歸 1.21.1 hotfix**（CM-2289） |
| **24** | 流程圖編輯器存檔時刪掉方塊，會直接把對應的任務連同底下的證據硬刪，不問操作者是不是那個專案的管理人 | ⚪ 低 | 只驗登入、不檢查歸屬 | 同一家公司裡**有「改流程範本」權限的人**，知道範本編號就能刪掉別人專案裡的任務與證據。只限同公司、要有改範本的權限 | 決策者裁定：查得到所屬專案時，就要是該專案的管理人才能刪 | ✅ **已修**（CM-2208，commit `1516d90bc`，1.21.0 出貨）。主系統掃描查出 |
| **25** | 清理「孤兒資料」的排程若哪次沒帶身分，會把全部正常資料判成孤兒、整批刪掉 | ⚪ 低 | 客戶資料沒隔開 | **今天不會發生**（排程有帶系統身分）。但這種判斷寫法只要身分一抓不到，方向就整個反過來：**所有客戶的任務與證據對應關係一次被清空**。已在開發環境用可撤回的方式實測 | 刪之前先確認「看得到的任務數大於 0」，看不到就中止並記錯誤 | ✅ **已修，1.21.0 出貨**（8-A，CM-2211，commit `fe928859f`） |
| **26** | 匯出任務 Excel 時，以 `=` `+` `-` `@` 開頭的欄位會被試算表軟體當成公式 | ⚪ 低 | 外部送什麼就收什麼 | 有人在任務欄位填進一段公式，**下載的人打開、按了「啟用內容」，那段指令就在他自己的電腦上跑** | 沿用先前修好的那支「強制當文字」的處理，不要另寫一份 | ✅ **已修，1.21.0 出貨**（8-C，CM-2213，commit `a5804a990`／`bde8f9d16`） |
| **27** | 批次完成任務的通知信，把操作者的暱稱原樣塞進信件內容；另有兩處通知信同樣寫法 | ⚪ 低 | 外部送什麼就收什麼 | 任何人把自己的暱稱改成一段網頁語法，**之後他觸發的通知信裡就能放一個看起來像系統發的釣魚連結**，收信的同事點了就上當 | 三處暱稱都先轉成純文字再放進信裡 | ✅ **已修，1.21.0 出貨**（8-C，CM-2213，commit `a5804a990`／`bde8f9d16`） |

### 另外四條：不是資安問題，但確實是程式錯誤

| # | 問題 | 影響 | 狀態 |
|---|---|---|---|
| **13** | 「強制執行」這個旗標有兩個不同來源，容易搞混 | 經三位覆核一致認定**不是漏洞**：呼叫端自己傳的那個旗標唯一能跳過的只是兩項「軟性提醒」，真正會擋下操作的檢查一個都跳不過。但同一個概念有兩個來源，本身就是日後埋雷的寫法 | ⬜ 未修（與第 12 條同型，可一起清理） |
| **14** | 三支接收檔案路徑、但現在完全沒人呼叫的程式 | 負責「存流程圖到檔案」「匯出 XML」「從 XML 匯入」，都是直接拿傳入的路徑開檔讀寫。**現在無害的唯一理由是沒人用它**——只要哪天有人把使用者輸入接到這幾支的參數上，就變成任意檔案讀寫 | ⬜ 未修（**建議直接刪掉，不要留地雷**） |
| **15** | 啟動稽核流程時，會把任務編號寫回「當初傳進來的那份範本」 | 傳凍結副本沒問題，**傳多個流程共用的母版就會互相覆蓋**。寫進去的是系統產生的編號、不是使用者能控制的輸入，所以不算資安問題 | ⚠️ **部分修**——**這是順手消失的、不是刻意修的**：會踩到的那條路徑隨另一項清理整支刪掉了，**機制本身原封不動**，下一個接這支功能的人傳母版就會重現 |
| **16** | 套件裡有兩組「接好但沒人用」的備用服務，完全沒有權限檢查 | 見上方[對照表那一節](#host-vs-package)。**與第 14 條是同一種**——現在無害是因為沒人用它，**只要哪天有人接上去，就憑空多出一組沒有檢查的入口** | 🗑️ **已拆除，1.21.0 出貨**（8-E，CM-2215，commit `822216b6c`：拆掉沒人用的那組服務註冊） |

---

## 這塊最不一樣的發現：東西寫對過，但被重做的時候沒跟上 {#host-vs-package}

::: {.callout .crit}
**共用套件本體的 49 個檔案，這一輪沒有查到新問題。**

**真正的問題全部在主系統把它接上產品的那段程式——而那段是整個重寫的。套件原本那份有檢查，主系統重寫的那份漏了。**
:::

### 對照表

最後一輪特地做了一張對照，結果是這樣：

| 套件提供的東西 | 主系統實際怎麼用 |
|---|---|
| 一支管「流程執行」的完整服務 | **完全沒有引用**。主系統自己另外重寫了一支一千多行的版本（有做權限檢查） |
| 一支管「任務新增刪改」的服務 | 組裝好放在那裡備用，**整個程式庫沒有任何地方呼叫它**。而這支**完全沒有任何權限檢查、也沒有客戶歸屬檢查** |

### 為什麼這件事重要

**這解釋了為什麼「掃套件」跟「掃產品」是兩件事。** 套件本身一行檢查都沒有，這是它的設計——**套件不知道「誰有權限」，那是用它的人要決定的**。所以：

| | |
|---|---|
| **套件零守門** | 這是設計，不是漏洞 |
| **但這也代表** | **這道防線不可能指望套件自己擋住**，只能在主系統這一層補 |
| **而主系統重寫了一份** | 重寫的那份有補檢查，但補得不完整——上面那些問題全部在這一份裡 |

### 這不是孤例，防竄改那塊也是同一種病

[防竄改那塊](M08-integrity.html)有一個跨端的觀察：**同一套憑證機制，客戶端的代理程式那頭做滿三層檢查，我們這頭一層都沒有。**

兩件事的成因是同一個：

> **不是「沒想到」，也不是「做了一半就停了」，是「東西寫對過，但被重做的時候沒跟上」。**

這一種比前兩種難發現——因為**正確的版本確實存在**，看程式庫會以為有做。要抓到它，只能去比對「寫對的那一份」與「實際在跑的那一份」。

### 一個未來的陷阱

套件裡那支「完全沒有權限檢查」的任務服務，**現在已經組裝好、放在那裡備用了**，只是沒有人呼叫它。

**哪天有人把它接上去用，等於憑空多出一組完全沒有權限檢查的功能入口。** 建議直接把這兩個沒人用的備用服務拿掉，不要留地雷。

---

::: {.callout .plain}
**下面只展開需要你自己判斷、或容易被誤解的幾條。** 其餘的修法明確，看上面表格的「怎麼修」那一欄就夠了——**沒有展開不代表沒查，也不代表不重要。**
:::

## 第 1、2、5、6、11 條：同一個缺口，套已拍板的統一修法 {#principle-2}

這五條是同一種病：**系統知道你是誰，但沒有問「這筆資料是你的嗎」。**

::: {.callout .decided}
**不需要各自設計修法——直接套決策者在[檔案上傳那塊](M02-file-upload.html#dispatch)已經拍板的形狀。**

那個形狀是：**一個共用通道 ＋ 一張登記表**。

| 誰 | 做什麼 |
|---|---|
| **共用的那一層** | 留一個空位，說「我需要一段能回答『這個人能不能碰這筆資料』的程式」 |
| **各業務模組** | 把自己的那段回答程式**登記**進來 |
| **要用的時候** | 先查出這筆資料屬於哪一類、屬於誰，再去呼叫對應的那一段 |

**規則各自寫 ≠ 入口各自開。** 不需要為每一種情境各開一支功能、各做一套檢查。

三條要定死的規則：

| 規則 | 為什麼 |
|---|---|
| ① 查不到登記的類型 → **拒絕**（不是放行） | 漏登記時往「看不到」倒，不是往「全都看得到」倒 |
| ② 沒有任何模組認領的資料 → **拒絕** | 沒人管不代表人人可取 |
| ③ 拒絕時回「**找不到**」而不是「你沒有權限」 | 後者等於告訴對方「這個編號是有效的」，可以拿來一個一個試 |
:::

這五條各自還要多做的事，只有兩點：

- **第 2 條**還要補留言的長度與筆數上限
- **第 6 條**要把「推進」與「退回」兩支一起補上，**不要繼續依賴下游套件擋**——哪天有人整理那個套件、把那道檢查改掉，漏洞就自己回來，而且沒有人會發現

---

## 第 3 條：一張流程圖就能讓伺服器永遠算不完 {#bpmn-dos}

**這是全案第二條拿到實測佐證的發現**（第一條是[防竄改那塊](M08-integrity.html)的第 1 條）。原本只是讀程式碼推論出來的，現在有實際跑出來的時間數字。

### 成因：兩段程式互相呼叫，而且不記得走過哪裡

流程圖上有「分岔點」——走到這裡要判斷條件，決定往哪一條路走。系統要沿著流程圖往下推算「下一步輪到誰」。

問題出在**套件裡有兩支函式互相呼叫、而且都沒有記錄自己走過哪些節點**。

每多一個分岔點，就讓它下游的整段流程**重新算一次**，而且是**每一條分支各算一次**。分岔一層一層接下去，工作量就一層一層相乘。

::: {.callout .crit}
**關鍵是：這完全不需要「繞回自己」**

很容易以為這種問題一定是「流程圖畫成一個圈、繞回起點」。**不是。**

一條**沒有任何回頭路、只是一層接一層分岔**的流程圖就夠了——而那是一張**完全合法的流程圖**，任何人都畫得出來。
:::

### 實測數字：每多一層分岔，大約慢四倍

本機空跑（純運算，完全不碰資料庫）：

| 分岔層數 | 算完要多久 |
|---:|---|
| 10 層 | **2.16 秒** |
| 11 層 | **8.76 秒** |
| 12 層 | **超過 10 秒還沒算完** |

**每多一層，大約慢四倍。** 所以這不是「有點慢」的問題——再多幾層就是「這輩子算不完」，而且**不報錯、不留任何紀錄**，從外面看只會覺得「系統怎麼卡住了」。

### 什麼時候會被觸發：五個時機

系統**只有在回答「下一步輪到誰」的時候**才會走這段程式。共五個時機：

| 時機 | 誰會做 | 說明 |
|---|---|---|
| **打開稽核輪次的頁面** | **任何看得到這個專案的人** | **最關鍵的一個**——畫面載入時就自動送出，使用者什麼都不用做、什麼都不會察覺 |
| 完成一項任務 | 任務負責人 | 每天會發生很多次 |
| 退回一項任務 | 稽核人員 | 常有 |
| 啟動一輪稽核 | 專案管理者 | 每一輪一次 |
| 建立子流程 | 系統自動 | 啟動流程時 |

**第一個時機是這條的要害**：攻擊者存進去之後什麼都不用做，**受害者自己打開頁面就替他觸發了**。

### 為什麼現有的兩道檢查擋不住

很容易以為「存檔的時候會整包檢查過，擋得掉」。**對這個形狀不成立**——因為現有的兩道檢查，擋的是**別的東西**：

| 現有的檢查 | 它實際擋的是什麼 | 擋得住這條嗎 |
|---|---|---|
| **可達性檢查**（這一支有記錄走過哪裡） | **繞回自己形成圈的圖** | ❌ **這個攻擊根本不需要繞回自己** |
| **結構檢查**（看分岔點有沒有寫判斷條件） | 分岔點忘了寫條件 | ❌ 每條出線隨便掛一個條件就通過了 |

::: {.callout .crit}
**攻擊用的是「不繞回自己、但一層接一層分岔」的圖——一張完全合法的流程圖。兩道檢查都過得了。**

實測佐證：**結構檢查處理 1000 個分岔點只要 0.027 秒。** 這個數字本身就說明了問題——它跟會算不完的那段程式是**兩套完全不交叉的邏輯**。

**結論：通過檢查不代表安全。**
:::

### 為什麼「幾個請求」就能讓整個產品沒反應

正式環境的服務**預設 4 個工人、每個工人一次只處理一個請求、逾時 120 秒**。

所以只要**同時打中上面那五個時機裡的任何一個四次**，四個工人一起卡住——**整個產品對所有客戶都不回應**，最多要等到逾時才會放人。

### 這也可能是意外，不一定有人存心

::: {.callout .warn}
**一個把流程設計得太複雜的客戶，可能無意間就畫出這種圖。**

真實的稽核流程如果分岔很多層，效能就會急速惡化——**這不需要任何人存心攻擊**。而且系統不報錯、不留紀錄，客戶只會看到「系統很慢」，沒有人知道為什麼。

**反過來也要講清楚**：

- 簡單的線性流程（規劃 → 稽核 → 改善 → 結案）**完全沒事**
- 分岔少的流程圖**也沒事**
- 問題出在「**一層接一層的連續分岔**」

這也是它一直沒被發現的原因——產品目前在用的流程都不夠複雜。
:::

### 修法：五件事，有先後順序

| | 做什麼 | 施工風險 |
|---|---|---|
| **①** | **記住走過哪裡**——推算的時候記錄走過的節點，走回同一個點就跳過 | **零**。純粹是效能改善，**算出來的答案完全一樣**，不影響任何現有行為。**這是治本、也是主力** |
| **②** | **加上限**——分岔深度超過 **8 層**、節點總數超過 **100 個**就拒絕 | 低。數字已經定案，依據見[下方這一段](#limits) |
| **③** | **那支「檢查流程圖」的功能補上權限檢查** | 零（這是第 4 條的一半，順手做） |
| **④** | **範本的新增與修改也要跑檢查** | 零（這是第 9 條） |
| **⑤** | **啟動輪次時要求範本已經發布過** | 要先確認現有流程沒有依賴「草稿也能被複製」這件事（這是第 10 條） |

**施工順序**：先做 ①（零風險、治本）→ 接著做 ②（上限數字已定，見下）→ ③④⑤ 順手做完。

::: {.callout .warn}
**這五件事要綁在同一張修正工單**

① 是治本（記住走過哪裡），③④⑤ 是把該有的關卡補齊——③ 補權限（第 4 條），④⑤ 補驗證的入口（第 9、10 條）。**只修 ① 而不補 ④⑤，等於把門修好、卻留了兩條側道**——沒檢查過的流程圖照樣從別的門進得來。

另外，要調整出貨的內建範本時得動到**資料庫初始化腳本**（那幾張內建流程圖是寫在腳本裡、不是獨立的檔案），排工時要把這件事算進去。
:::

::: {.callout .decided}
**為什麼止血跟治本兩件都要做**

- **只做治本**：有人存一張一百萬個節點的圖進來，還是會慢（只是不會算不完）
- **只做止血**：上限訂太鬆擋不住、訂太嚴會擋掉客戶正常的複雜流程

**上限的數字已經定了**——分岔深度 8 層、節點總數 100 個，依據見下。
:::

### 上限訂 8 層與 100 個節點，是怎麼來的 {#limits}

先看現有的東西有多複雜：

| 來源 | 份數 | 節點數最大 | 一層接一層的分岔最深 |
|---|---:|---:|---:|
| **出貨的預設範本** | 4 | **9** | **2** |
| 開發環境裡的另一批流程範本 | 59 | 3 | **0** |

- 現有最複雜的是「**完整稽核流程（含審核）**」——**9 個節點、2 層分岔**。
- 那 59 份全部是「開始 → 一個任務 → 結束」，**一個分岔都沒有**。
- 對照上面那張爆炸曲線（10 層 2.16 秒、11 層 8.76 秒、12 層算不完）：**現有最複雜的範本，離出事還有八層的距離。**

兩個數字各自的理由：

| 上限 | 值 | 為什麼訂這個 |
|---|---:|---|
| **一層接一層的分岔深度** | **8 層** | 現有最大是 2，給四倍的成長空間。8 層的運算時間仍在毫秒到半秒之間，離爆炸區還很遠。**要畫到第 9 層才會被擋下來，而那已經不是一份正常的稽核流程了** |
| **節點總數** | **100 個** | 現有最大是 9。這一道純粹是擋離譜的輸入，**訂小反而更有效**——訂到幾百等於幾乎不擋 |

::: {.callout .warn}
**這裡要誠實講一件事**

開發環境裡**沒有任何一份是客戶自己畫的範本**，全部都是系統預設的。所以「**客戶實際上會畫多複雜**」我們手上**沒有真實樣本**——**8 這個數字是拿現有預設的四倍推出來的**。

**將來真的出現畫得更複雜的客戶，這個上限要重新檢視。**
:::

---

## 第 4 條：「檢查流程圖」那支功能誰都能打，而且不限大小 {#check-endpoint}

::: {.callout .warn}
**這一條原本的描述有一半是錯的，已經更正。**

| 原本報告寫的 | 實測查證結果 |
|---|---|
| 這支功能不檢查權限 | ✅ **屬實**——同一個檔案裡其他功能都掛了權限檢查，**只有它沒有** |
| 沒有大小上限 | ✅ **屬實**——送進來的內容沒有長度上限，後端也不限制節點數與連線數 |
| **「它會讓伺服器癱瘓」** | ❌ **不成立**。它呼叫的是**另一套檢查器**，裡面所有檢查不是一遍過、就是有記錄走過哪裡——**實測處理 1000 個節點只要 0.027 秒**。它從頭到尾沒有碰到第 3 條那兩支會算不完的程式 |

**所以「讓整個產品沒反應」那段敘述已經整個移到第 3 條**，那才是它真正的所在位置。
:::

### 那它現在的問題是什麼

兩件事，都還是要修：

1. **越權**——這支功能任何登入者都能打，同檔其他功能都有權限檢查，只有它漏掉
2. **沒有輸入上限**——可以丟任意大的內容進來

### 定級：🟡 中

原本標的是高風險，實測後確定要降——但**降到中、不降到低**。理由是：

- **越權是真的**——同一支檔案裡其餘功能都掛了權限檢查，**只有這一支沒有**，沒有任何一道關卡擋著它。
- **但打不壞東西**——實測 1000 個節點只要 0.027 秒，丟再大的東西進來也癱不掉伺服器。

**低**這一級通常留給「有其他關卡擋住、實際上打不到」的情形。**這一條沒有任何關卡擋**，只是打進來之後造成的後果輕，所以落在中。

修法就是第 3 條那張表的 **③**：補上權限檢查，並加上內容長度與節點數上限。

---

## 第 9、10 條：前面講的檢查，連「一定會被跑到」都做不到 {#validation-bypass}

::: {.callout .crit}
**這兩條是這次補上的，原本報告完全沒有。不寫的話，讀者會以為「把檢查補好就安全了」。**
:::

| | 問題 | 後果 |
|---|---|---|
| **第 9 條** | 流程範本的**新增與修改完全不跑任何檢查**——只有「發布」那一支會檢查 | **沒檢查過的內容存得進資料庫** |
| **第 10 條** | 啟動一輪稽核去複製範本時，**不要求那份範本已經發布過**——取資料的查詢只過濾「啟用中」，沒有過濾「已發布」 | **沒檢查過的草稿也能被複製去啟動一輪真實的流程** |

**兩條合起來的意思**：不管把檢查寫得多好，都可以繞過去——因為**根本沒有一條路徑保證會走到它**。

### 定級：兩條都是 🟡 中

**這兩條本身不是漏洞，是「讓別的檢查失效」的放大器。** 單獨看都不嚴重——沒有誰因為它們直接被偷走資料或被癱瘓；但不補的話，**第 3 條就算修好了也能繞過去**。所以它們的分量來自「它們讓別的洞繼續活著」，落在中。

這和[防竄改那塊](M08-integrity.html)的關係是同一種：那邊的第 3 條之於第 1 條，也是一條「讓另一條更省事」的放大器——本身不致命，但拿掉它，主要那條的難度就整個不一樣了。

### 這兩條要跟第 3 條綁在同一張修正工單

| 誰 | 角色 |
|---|---|
| **第 3 條** | 治本——推算的時候記住走過哪裡 |
| **第 9、10 條** | 把驗證的入口補齊——讓檢查一定會被跑到 |

**只修第 3 條、不補這兩條，等於把門修好了卻留了兩條側道。**

**修法**：範本的新增與修改也要跑同一套檢查（第 9 條）；起輪次時要求範本必須已發布（第 10 條，要先確認現有流程沒有依賴草稿被複製）。

---

## 第 7 條：只能看的人可以完成別人的任務 {#viewer}

::: {.callout .decided}
**這一條決策者已經裁定了，修法方向確定。**

**裁定內容**：這個角色不可以完成或退回任務，**只能留言**。

**背景**：這個角色原本的用意是開放觀看權限給外部顧問或其他單位的同事。而檢視時發現，系統裡的權限判斷政策寫明「任何角色的專案參與者都會通過檢查，這是先前使用者的決策」，另一處對這個角色的定義卻寫著「純瀏覽、沒有待辦任務」——**產品對外的承諾與實際的判斷邏輯互相矛盾**，所以必須由決策者裁定哪一個才算數。

**修法**：在原本的「是不是這個專案的人」之後，再加一道「是不是這個任務的負責人、或是這個專案的管理者」，同時把政策說明的文字一併改掉。
:::

### 修的時候範圍要算大一點

這一條的影響面比表面大。**弱點掃描模組有八支會改狀態的功能吃的是同一道檢查**——開始掃描、取消、重跑單台、取消單台、整組取消、刪單筆、整組刪、立即開始。

所以這個只能看的角色同樣可以：

| 能做什麼 | 後果 |
|---|---|
| 對客戶的正式機器發動帶帳密的掃描 | 掃描動作會實際連進客戶的機器 |
| 反覆取消再重派 | 干擾正常的檢測工作 |
| 刪掉失敗的執行紀錄 | 抹掉「這次掃描失敗過」的痕跡 |

**只改流程模組那兩支，檢測模組這八支仍然敞開。** 開工單時要把這八支一起算進範圍。

---

## 第 17～22 條：任務的五個入口，同一個形狀 {#task-entries}

**在哪個畫面發生**：都是規劃頁的「任務」——看任務清單、匯入匯出任務 Excel、強制開始卡住的任務、查卡住狀態。

這幾個入口的網址同時帶「專案編號」和「任務編號」，而系統**只檢查你在網址上那個專案的身分，從不核對這筆任務到底屬不屬於那個專案**。資料庫那道隔離只分客戶、不分專案，所以兩層都擋不住——**同一家客戶內，換一個別專案的任務編號就進去了**。

| 入口 | 這頁第幾條 | 做得到什麼 |
|---|---|---|
| 單筆看／改／刪任務 | 既有問題（在另一張修正卡上，已修單筆三支） | 看、改、刪別人專案的任務 |
| 批次匯入確認 | 第 18 條 | 一次改掉別人專案的一批任務 |
| 匯出（＋匯入驗證兩步） | 第 20、21 條 | 拿到別人專案整張任務表、試探名稱存不存在 |
| 強制開始（＋查卡住狀態） | 第 19、22 條 | 把別人專案的任務推進 |
| 任務清單 | 第 17 條 | 拿到別人專案的任務編號——**前面每一條的入場券** |

::: {.callout .warn}
**修的時候五個入口一定要逐一列成驗收項、放同一批**

先前那張修正卡只修了第一列那三支。**只修一個入口，另外四個照開**——這正是這一輪最常見的漏法（修好一個入口、另一個原封不動，而驗收看起來是綠的）。

還有一件事要先定：「這個任務屬於哪個專案」目前是靠任務指派紀錄推出來的，而開發環境裡 89% 的任務沒有指派紀錄——**用它來判斷會把正常操作誤擋**。這件事的做法要先由決策者裁（已有分析，待裁），在那之前各張修正卡不要各自發明判斷寫法。
:::

---

## 這塊的結論

::: {.callout .warn}
**一句話**：這塊的高風險有三條——兩條是「少問一句這是你的嗎」，一條是「一張合法的流程圖就能讓伺服器算不完」。而它守的是稽核證明與階段歷程，也就是稽核報告的依據本身。補查任務那一端又多出六條同一形狀的（第 17～22 條），**要跟既有那張任務修正卡綁成同一批**。

**建議**：

1. **第 1、2、5、6、11 條可以一次修完**——同一個缺口的五個出口，直接套[檔案上傳那塊已拍板的形狀](#principle-2)，不需要重新設計。其中**第 1 條的清單裡有檔案編號**，拿到編號就能接著下載檔案本體，與檔案上傳那塊是同一條鏈；**第 2 條是唯一能寫的**，可以拿來冒充同事發留言釣魚。
2. **第 3 條單獨排，而且第一件事是零風險的**——「記住走過哪裡」這一步純粹是效能改善、答案完全不變，可以立刻做。上限的數字已經定了（分岔深度 8 層、節點總數 100 個，依據見[上面那一段](#limits)）。**這條有實測數字撐著，而且不一定要有人存心才會發生**——客戶自己把流程畫複雜了就會踩到。
3. **第 3、9、10 條要綁在同一張工單，第 4 條跟著一起做**——第 3 條是治本，第 9、10 條是把驗證的入口補齊。**只修第 3 條不補那兩條，等於把門修好卻留了兩條側道。** 第 4 條的補權限、加上限也是同一批工。
4. **第 7 條決策已經裁好了，可以直接動**——但**範圍要算進弱點掃描模組那八支功能**，只改流程模組等於沒修完。
5. **第 17～22 條（任務的五個入口）併成同一批**，逐一列驗收項——見[上面那一節](#task-entries)。
6. **第 14、16 條是最便宜的兩條**——刪掉幾支沒人用的程式就好。它們現在無害的唯一理由是「沒人用它」，**只要哪天有人接上去，就憑空多出一組沒有檢查的入口。建議直接刪掉，不要留地雷。**

**另外要提醒兩點**：

- **第 1 條雖然標示部分修，但資料庫那層是別的功能開發順手補的、不是針對這個問題修的。** 跨客戶被擋住了，但同一家客戶內跨專案、跨部門仍然拿得到，而且證明文件那張表連客戶欄位都還沒有。不要因為看到「部分修」就認為這條快好了。
- **第 15 條的「部分修」也是順手消失的、不是刻意修的**——會踩到的那條路徑隨另一項清理整支刪掉了，**機制原封不動**，下一個接這支功能的人傳母版就會重現。
:::

| 統計 | |
|---|---|
| 檢視輪數 | 9 輪、170 個檔案（原 6 輪 157 個＋任務接線 3 輪 13 個）＋ 1 次實際測試 |
| 找到的問題 | 22 條（18 條資安 ＋ 4 條非資安但確實是程式錯誤） |
| 高風險 | **3 條**（第 1、2、3 條） |
| 中風險 | **10 條**（第 4～10、17～19 條） |
| 低風險 | **5 條**（第 11、12、20～22 條） |
| 有實測佐證 | 1 條（第 3 條）；另有 1 條經實測**更正並降級**（第 4 條，原列高風險） |
| 已修 | 12 條（第 1～12 條，1.21.0 出貨） |
| 未修，歸 1.21.1 hotfix（CM-2289） | 6 條（第 17～22 條，任務越權） |
| 主系統掃描另查出 | 5 條（第 23～27 條：中 1、低 4；全部已修，1.21.0 出貨） |

---

| 你想知道 | 看哪裡 |
|---|---|
| 我們用什麼方法查、結果有多可信 | [檢視方法與工具](GUIDE-01-method-and-tools.html) |
| 其他模組的檢視結果 | [回總報告首頁](index.html) |
