FR-088 流程引擎(jedi-flow-engine)資安掃描 — 收口 SUMMARY

FR-088 流程引擎(jedi-flow-engine)資安掃描 — 收口 SUMMARY

母卡 CM-1669 | 收口日 2026-09-14 | 六棒 CM-1670~1675,只掃不修 沿革(每棒 commits/決策/教訓)見 ../../FR-075-2609-jedi-package-security-audit/handoff/security-scan-LOG.md 與 git log --grep='FR-088';此檔只寫終局。

1. 一句話結論

流程引擎的套件本體加上主專案接它的那半邊,共 157 支程式檔分六棒掃完,範圍內確認 12 條問題(登記為跨 arc 總表 11 條資安+4 條非資安真 bug,算法見下),最嚴重的三件是:① 任何登入者知道一個任務編號就能讀走別家客戶的稽核證明清單(第 49 條);② 一張惡意流程圖能讓整個系統對所有客戶停止回應(第 55/57 條,兩條同一組病);③ 流程留言不檢查你是不是這個專案的人,可以假冒同事發訊息釣魚(第 51 條)。修正卡本線不開:程式層「這筆是不是你的」那批併進 FR-086 由該側首腦開卡,資料庫層隔離歸 FR-094 八棒接。

「12 條」的算法:以每棒 runner 報的範圍內條數加總(3+4+2+2+1+0),對應跨 arc 總表 §3.1 第 49~59(11 列,H1 的一列是兩位研究員各報一次的同一個洞);非資安 bug 另登記 §3.2 第 13~16(4 條)。各處數字口徑不一之處見本檔末段「數字對帳備註」。

2. 這個 arc 在檢查什麼

  • 流程引擎在產品裡做什麼:每個稽核專案都照一張「流程圖」(BPMN 格式的 XML 文字檔)在跑——規劃 → 稽核 → 改善 → 結案。誰能按「推進」、按了之後系統要建什麼任務、任務完成後換誰、留言與證明文件存哪裡,全由這個引擎決定。
  • 為什麼要掃:它從沒掃過,而且同時有兩種風險密度最高的東西——「讀 XML」(歷來有讀本機檔案、撐爆記憶體的老問題)與「授權缺口」(網址只帶一個編號,後端要主動反查才知道是不是你的專案)。跨 arc 總表排它為下一支第一名。
  • 套件與宿主怎麼切:套件(jedi monorepo 的 jedi-flow-engine/,72 檔)是「零件供應商」,自己沒有任何對外網址;主專案(宿主,92 檔)接線後才有 19 條真正的 API 入口,所以「有沒有檢查權限」這件事全在宿主那半邊。六棒按業務功能垂直切(任務/階段/流程圖解析/範本管理/執行宿主/執行核心),不按程式層切,讓每棒的問題能一次看完。

3. 六棒總表

「面板」=工具派出的三個獨立檢查員對每條候選各投一票(不是儀表板);「verified」=工具自己蓋的章,代表每條候選都投完、沒有漏投;「範圍外」=工具的密鑰專項順手掃全 repo 撈到的,不在該棒檔案清單內。

棒 卡 檔數 面板票數與狀態 範圍內發現 首腦改判 報告
1 H2 任務完成/回退/留言/證明 CM-1670 23 候選 11 去重 10,三人各投一票共 30 票全投出,verified 3 條(HIGH 1/MEDIUM 2;其中第 51 條後被裁升 HIGH) 無改判;人工補一條「流程定義 GET 任何登入者可讀」併 P1;證偽卡片一項(is_admin 是平台超管不是租戶管理員) ../scan-H2-task-comment-evidence.md
2 H1 稽核階段推進與回退 CM-1671 21 候選 12 去重 11,33 票全投出,verified 4 條(MEDIUM 3/LOW 1) 無改判;卡片兩大疑點一個「讀成立、寫不成立」、一個 0:3 否決 ../scan-H1-stage-advance-rollback.md
3 P1 流程圖範本與 BPMN 解析 CM-1672 30 候選 4 去重 3,9 票全投出且每條 3:0,verified 2 條(MEDIUM 2;第 55 條後被裁升 HIGH) runner 報 3 條,F2(套件庫走明文 http)改判範圍外+重複 CM-1634 ../scan-P1-bpmn-parsing-and-templates.md
4 H3 流程範本管理 CM-1673 13 候選 10,30 票全投出且每條 3:0,5 條被檢查員降級,verified 2 條(MEDIUM 1/LOW 1;第 57 條後被裁回 HIGH) 無改判;runner 沒交付報告,首腦補寫 ../scan-H3-flow-template-management.md
5 H4 流程執行宿主服務與接線 CM-1674 21 候選 9,27 票全投出(8 條 3:0、1 條 0:3),verified runner 報 3 條 → 1 條新(MEDIUM) 2 條改判重複:留言零守門(=第 51 條且檔案不在 scope)、projects 表隔離關(三處已記) ../scan-H4-execution-host-service.md
6 P2 流程執行核心 CM-1675 49 候選 3 去重 2,6 票全投出(1 條 3:0、1 條 0:3),verified 0 條新(runner 報 1,自標重複第 51 條) 同意 runner;最有價值的是「套件公開方法 vs 宿主守門」對照表,帶出 §3.2 第 16 未來陷阱 ../scan-P2-execution-core.md
合計 157 135 票 12 條

每棒驗收的固定步驟(六棒一致):① 核工具蓋的章與票數,對照 runner 自述;② 範圍內每條首腦自己開檔核對行號,需要時到 DEV 資料庫唯讀實查(隔離開關、規則數、筆數);③ 越界檢查——runner 報的每條對照 scope 檔案清單與跨 arc 總表既有條目,越界或重複的改判、不另計;④ 核「交付四件」(報告/commit/Notion 回寫含 hash 與 run ID/狀態);⑤ 跑 git diff <掃描版本> HEAD -- <下一棒 scope> 看平行改動有沒有咬到下一棒。

檔數說明:母卡標題寫「150 檔」是開卡時的數字;P2 開掃前因平行重構(plugin.py 拆成五檔、新長出 api//domain/ports.py)由 42 重算為 49,六棒實際合計 157。

4. 12 條發現一覽

嚴重度以決策者 2026-09-13 裁定後為準;標 裁升 的是檢查員原評 MEDIUM、決策者改 HIGH。「歸屬」是修正卡的落點。行號以跨 arc 總表登記為準(各棒掃描期間程式有漂移,同一支函式在不同報告的行號不同,見末段備註)。完整的「出事會怎樣/怎麼修」在總表與各棒報告,這裡一條一段只寫到能判斷急不急。

4.1 資安類(跨 arc 總表 §3.1 第 49~59)

第 49 條|HIGH|併 FR-086 程式層那批

  • 現況:✅ 已修(M06-1,CM-2059)
  • 問題:任何登入者只要知道一個任務編號,就能讀走別家客戶的稽核證明清單(檔名、描述、雲端硬碟連結、上傳者帳號、檔案指紋;拿到檔案編號還能接第 34 條下載本體)。
  • 為什麼 HIGH:只要登入+一個編號,不需任何權限;同一支檔的新增/更新/刪除都有檢查、只有「讀」漏掉,是漏寫不是設計;資料庫層零兜底(表的隔離關、沒有客戶欄位)。
  • 在哪:app/flow_engine/service/job_evidence_service.py:42-59,修法是 :48 之後補一行既有的 assert_project_participant(...)。

第 50 條|MEDIUM|併 FR-086 程式層那批(與 49 必須同卡)

  • 現況:✅ 已修(隨 M06-1,CM-2059)
  • 問題:同一支服務「查單一筆證明」也沒檢查歸屬,目前靠一個序列化 bug 擋住(route 把單筆餵進「多筆」序列化器、回傳前先炸)。
  • 為什麼 MEDIUM:守門缺口是真的、外洩目前不成立;那個 bug 一修好缺口就變真外洩,所以兩者要同卡。
  • 在哪:同檔 :61-70;route api/flow_engine/routes/job_evidence_route.py:70-71。

第 51 條|HIGH(裁升)|併 FR-086 程式層那批

  • 現況:✅ 已修(M06-2,CM-2035)
  • 問題:流程留言的讀與寫只驗登入,任何登入者可讀走別家客戶的稽核討論、用自己名義塞留言,留言還會即時推播給該專案成員——可假冒同事發訊息釣魚。留言存 JSON 陣列無筆數與長度上限。
  • 為什麼裁升:檢查員原評 MEDIUM(前提與 49 相同、外洩的是留言不是證明本體),決策者以「可假冒同事釣魚」裁 HIGH。P2 從套件側證實套件層也一行檢查都沒有,守門只能加在主專案。
  • 在哪:api/flow_engine/routes/flow_engine_route.py:44-64(讀)/:66-121(寫);套件 element_variable_service.py:26/:50。

第 52 條|MEDIUM|併 FR-086 程式層那批

  • 現況:✅ 已修(M06-5,CM-2035)
  • 問題:任何登入者知道一個輪次編號,就能讀走別家客戶的稽核階段歷程——每次推進/退回的操作者、從哪階段到哪階段、退回理由全文(管理者自由填寫,通常直接寫明稽核哪裡出問題)。網址上的專案編號從頭到尾沒被用。
  • 為什麼 MEDIUM:前提需取得輪次編號(亂數 UUID,猜不出來);但外洩的是稽核判斷的敘事文字,總表註明可視需求升 HIGH。
  • 在哪:api/flow_engine/routes/stage_rollback_route.py:52-67;下游 jedi-compliance-audit audit_round_app_service.py:867-887 只查輪次存不存在。

第 53 條|MEDIUM|併 FR-086 程式層那批(附規劃相依)

  • 現況:✅ 已修(M06-6,CM-2035)
  • 問題:「查目前階段」用你填的專案編號查你的角色、用你填的輪次編號撈資料,兩者不核對;角色不符也不擋,只把「可否推進」標 false 照樣回整包(含流程執行編號,那是另一批 API 的鑰匙)。
  • 連帶:「推進」「退回」兩支同樣寫法,目前靠下游套件用輪次自己的專案再查一次角色擋住(七條寫入路徑首腦逐一核對)——修時一併補,讓本層自己站得住。
  • 在哪:app/flow_engine/service/stage_advance_service.py:84/:118/:120;連帶 :224 與 stage_rollback_service.py:95。

第 54 條|LOW|一行修法,隨 53 同批順手改

  • 現況:✅ 已修(M06-12,CM-2059)
  • 問題:推進/退回的操作者顯示名稱可由呼叫端自填(程式用「沒填才補真名」的寫法);真實帳號欄由伺服器填不受影響,只影響畫面顯示。
  • 為什麼 LOW:攻擊者本來就要有權推進,是「自己人竄署名」不是外人打得到的洞。
  • 在哪:api/flow_engine/routes/stage_advance_route.py:59、stage_rollback_route.py:40。

第 55 條|HIGH(裁升)|與 57 合一張「BPMN 輸入強固化」卡

  • 現況:✅ 已修(M06-3,CM-2059)
  • 問題:程式沿流程圖分岔點往下走時不記得走過哪裡,還把同一個分岔點算兩次——「分岔點繞回自己」的圖每次讀都 500;「一路串 40 個分岔點」的圖工作量翻 40 次倍、永遠算不完、不報錯、log 沒東西,一條工作執行緒卡死,多打幾次全站對所有客戶不回應。
  • 為什麼裁升:存進去要有存流程圖的權限(一次性),但之後任何正常使用者打開那輪稽核就替攻擊者觸發;決策者裁「一個帳號讓全站對所有客戶不回應不可接受」。同檔另外兩支走訪都有記路,只有這支沒有。
  • 在哪:套件 jedi_flow_engine/common/utils/bpmn_uilts.py:276/:294/:295/:361。

第 56 條|MEDIUM|與 55/57 同批

  • 現況:✅ 已修(M06-8,CM-2059)
  • 問題:讀範本時無條件解析 XML 且沒防呆;寫入端用「有沒有值」判斷,空字串反而跳過驗證——一筆空字串範本讓所有人的清單頁都 500、不會自己好,要進資料庫修那筆才恢復。
  • 為什麼 MEDIUM:要有 module-frame.update 權限;runner 誠實標「寫入當下交易能否 commit」未實測,可信度較低。
  • 在哪:套件 infra/mapper/workflow_template_mapper.py:21;寫入端 workflow_template_repo_impl.py:52/:61;宿主入口 api/module_frame/routes/module_frame_item_route.py:100。

第 57 條|HIGH(裁升)|與 55 合一張「BPMN 輸入強固化」卡

  • 現況:✅ 已修(M06-4,FR-114.1-8)
  • 問題:任何登入者丟一份特製流程圖給「檢查流程圖」API,就卡住一條工作程序 120 秒;後端預設只有 4 條工作程序,連打四次全站對所有客戶不回應,重複送就能一直維持。
  • 為什麼裁升:研究員報 HIGH、檢查員 3:0 降 MEDIUM,決策者裁回 HIGH——同第 55 條理由,而且門檻更低:不用存進去,打一次就炸,不需要任何流程範本權限,全站也沒有請求頻率限制。
  • 在哪:api/flow_engine/routes/flow_template_route.py:130-144;schema serializers/flow_template.py:28-29 無長度;驗證器 jedi-flow-engine bpmn_topology_validator.py:168/:271 線性掃描。

第 58 條|LOW|一行 decorator,可隨程式層那批順手

  • 現況:✅ 已修(M06-11,CM-2035)
  • 問題:流程範本列表與單筆讀取沒掛 flow_template.read 能力點(「能力點」=系統裡「有沒有權限做這件事」的開關),前端選單擋住、API 敞開,同租戶內任何登入者能讀走全部範本的完整流程圖。
  • 為什麼 LOW:跨租戶讀不到(這張表是本 arc 唯一隔離開著的);是「讀端點漏守門」第四次出現。
  • 在哪:api/flow_engine/routes/flow_template_route.py:29-45/:58-66;可抄 api/flow_control/routes/project_route.py:41 的做法。

第 59 條|MEDIUM|併 FR-086 程式層那批(決策者已裁方向)

  • 現況:✅ 已修(M06-7,FR-114.1-1/FR-114.1-2,CM-2119)
  • 問題:專案裡定義為「只能看、沒有待辦」的 viewer,可以把別人的稽核任務標成完成、或把流程退回上一關,稽核紀錄的經手人變成他,連帶改 job/問卷/workflow 狀態。
  • 為什麼 MEDIUM:前提是該專案參與者(任何角色)+知道流程與任務編號(列表 API 就有)。守門政策檔自陳「任一角色皆通過,per 2026-06-05 決策」而 viewer 的定義寫「純瀏覽,無待辦」——產品承諾與守門政策矛盾,決策者已裁(見 §5)。
  • 在哪:app/flow_engine/service/workflow_execution_service.py:655(complete_job)/:833(revert_job);政策自述 common/authz/workflow.py:15-16。

4.2 非資安但是真 bug(跨 arc 總表 §3.2 第 13~16)

第 13 條|非資安 bug|與第 54 條同種寫法一起清

  • 現況:🚫 裁定不修(M06-13,非資安,決策者 10-01)
  • 問題:force 旗標有兩個來源(外層參數與 ctx 裡的),守門看外層、執行看 ctx。三個檢查員 0:3 判非漏洞、首腦同意——能跳過的只有兩項「軟提醒」(程式自己註明「軟提醒非硬擋」),真正守門一個都跳不掉;但同一個意思兩個來源是異味。
  • 在哪:stage_advance_service.py:209/oscal_stage_handlers.py:41。

第 14 條|非資安|建議刪

  • 現況:🗑️ 裁定刪除,已刪(M06-14,1.21.0)
  • 問題:三支吃檔案路徑的方法直接開檔讀寫,套件與主專案兩邊零呼叫者(死碼)。今天不是洞,日後有人把使用者字串接上去就是任意檔案讀寫。
  • 在哪:套件 bpmn_generator.py:963/1220/1241。

第 15 條|非資安 bug

  • 現況:🚫 裁定不修(M06-15,非資安,決策者 10-01)
  • 問題:啟動流程時把每個任務的執行 uid 寫回「傳進來的那份範本」——輪次流程傳凍結快照沒事,module-frame 匯入傳的是共用母版,多流程共用時會互相覆蓋。寫進去的是系統自產 uid 不是使用者輸入,所以不算資安。
  • 在哪:workflow_execution_service.py:429-441/module_frame_service.py:241。

第 16 條|未來陷阱|加陷阱註解或拔掉 provider

  • 現況:🗑️ 已拆除(M06-16,CM-2215,1.21.0)
  • 問題:接好了但沒人用的插頭——套件 JobExecutionService(任務增刪改 6 支公開方法,零守門零租戶檢查)在 DI 容器(程式啟動時把各元件組裝起來的登記簿)建了 provider 但全 codebase 零取用;套件 WorkflowExecutionService(含 complete_job/revert_job)主專案根本沒 import,宿主整支重寫了自己的。今天無害,哪天有人接上就是一組現成的無守門端點。
  • 在哪:di_containers/flow_engine/workflow_excution_containers.py:113。

5. 決策者已裁(2026-09-13)

  1. viewer 唯讀、只能留言:viewer 的本意是開放觀看權限給外部顧問或其他單位同事,不可完成/退回任務(除非他就是被指派的人)。第 59 條修法定案:完成/退回兩支補「呼叫者是該任務負責人或該專案 manager 才放行」,並改掉 common/authz/workflow.py:15-16 的政策自述。
  2. 第 51/55/57 三條升 HIGH:留言可假冒同事釣魚;惡意流程圖與「檢查流程圖」API 讓一個帳號就能把全站對所有客戶弄到不回應,不可接受。
  3. 程式層那批併 FR-086、現在開卡排修:這批資料表根本沒有客戶欄位,資料庫想擋也擋不了,只能靠程式檢查「這筆是不是你的」——FR-088 的第 49/50/51/52/53/59 與 FR-086 的第 34/35/37/40/45/47 同一批,開卡由 FR-086 側首腦負責。
  4. 資料庫層歸 FR-094:有客戶欄位的 13 張表+4 支 view 的隔離開關由 FR-094(母卡 CM-1766,八棒已開)接走,含本 arc 看到的 workflow_executions/job_executions/workflow_templates;element_variables 連欄位都沒有,只能靠程式層(即第 51 條那批)。
  5. 問卷不加平台範本旗標:問卷引用是複製進任務,不需要公版旗標;要修的是 survey 另 12 張裸表,FR-094 第 8 棒接走。

另有沿用的舊裁定:修正卡一律不派、由 PM 統一安排(早期修正卡 09-21 作廢,後改照內化版問題總表派工,已全數修完)。

6. 開卡時的疑點對帳

每張子卡開卡時首腦讀碼列了「重點看什麼」,runner(或首腦補寫時)逐項回答。證偽與證實同樣有價值——證偽了才知道是什麼擋住、擋的東西哪天會不會消失。

棒 疑點數 成立 證偽(不成立) 機制屬實但無入口/影響低 沒問題/只記錄
H2 8 5 2 — 1
H1 8 4 + 1 部分成立(讀成立寫不成立) 1 — 2
P1 8 2 + 錨點 1 5 — —
H3 7 4 — 1 待補查 2
H4 8 全答(兩處修正了卡片的猜測) 見報告 §5
P2 9 3(含對照表做完) 3 3 —

合計 48 項疑點(H4 一棒的分類材料只寫「八項全答」,未細分)。各棒重點:

  • H2:留言讀寫零守門、證明「讀」零守門、兩張表隔離關且無租戶欄——三項成立即 F1/F8/F9。證偽的是「租戶管理員可動別租戶」:守門檔註解寫 is_admin 是租戶管理員,追到源頭是平台超級管理員,所以不成立,但註解錯了、它宣稱的兜底也不存在。「反查鏈恆回 None 導致誤擋」屬實但沒引發繞過。
  • H1:階段歷程 GET 零守門、階段資訊 GET 對非參與者也回資料、ctx 整包往下傳(找到署名可自填)、三張表隔離全關——成立。「專案編號與輪次編號從不核對」讀成立、寫不成立。「force 雙版本」三個檢查員 0:3 否決、首腦同意。
  • P1:分岔點走訪不記路、讀範本無條件解析——成立;錨點「流程定義 GET 任何登入者可讀」答完(守門責任在宿主,套件提供的隔離規則「4 條、開關關」等於沒有)。五項證偽:讀本機檔案(XXE)回 None、實體炸彈與五萬層巢狀正常回傳、三支檔案路徑方法零呼叫者、整個套件零 eval/exec。
  • H3(首腦自答):validate 端點可卡死、範本讀取漏能力點、宿主 mapper 不解析 XML(壞 XML 讀不炸、發布會擋)、內建範本守門四處無遺漏——四項有答;DTO 無敏感欄位、守門無遺漏——兩項沒問題;「凍結副本不帶租戶」查出機制(租戶靠 jedi-common 在寫入前從登入身分自動填,沒身分就留空),且 DEV 實查修正 FR-087 戊組:191 筆裡只 5 筆快照、186 筆原廠母版,根因待補查。
  • H4:儀表板三支查詢不是 <strong>kwargs 形狀(卡片猜錯成因)但零租戶過濾是真的,併 FR-083「27 支零守門」;啟動流程改寫範本 XML 的對象視呼叫者而定**(記 §3.2 第 15);通知不帶留言原文、收件人從專案往下查;known_project_id 唯一來源是伺服器端解析;輪次凍結檢查三種情況預設放行(fail-open,即「出錯時預設放行」)屬實但影響低於預期。
  • P2:對照表做完(套件三支 service 共 25 支公開方法,主專案真正接來用的只有 2 支 service,且只有留言那 2 支方法有對外網址);blueprint 是空的、套件本身沒有對外網址;四張表隔離全關屬實。證偽:「完成任務的條件參數使用者可控」(宿主沒 import 那支 service)、「query entity 傳 None 等於不過濾」(用到它的兩支是死碼)、「repo 注入可被覆寫」(機制存在但無請求期觸發路徑)。「流程變數是 JSONB 任意寫」(JSONB=資料庫裡可存任意 JSON 結構的欄位)機制屬實,但唯一外部入口就是第 51 條那支留言 API,不另計。

最值得記的三個證偽:

  1. 「完成任務的條件參數使用者可控」→ 套件那支 service 宿主根本沒 import(P2 疑點 7)。卡片假設「套件零守門、宿主包一層」,實況是宿主整支重寫了自己的 WorkflowExecutionService,套件那 13 支公開方法目前沒有任何外部入口。這一條同時是 §3.2 第 16「接好沒人用的插頭」的來源。
  2. XML 老問題全擋(P1 五項):讀本機檔案回 None、實體炸彈與五萬層巢狀正常回傳、整個套件零 eval/exec。首腦驗收時各重跑一次(不是重讀 runner 的敘述),一分鐘就確認。
  3. 跨專案推進/退回「讀成立、寫不成立」(H1 疑點 ①):兩支 GET 確實不核對專案與輪次;但「寫」被下游 jedi-compliance-audit 拿輪次自己的專案再查一次角色擋住,七條寫入路徑首腦逐一開檔核對。擋住它的是別人不是本層——登記為規劃相依(總表 §2.9 🅘),修第 53 條時要連寫入面一起補。

7. 這個 arc 教會我們的事

  1. runner 對「scope 清單」與「既有卡」的比對是最常漏的一步:P1 的 F2 與 H4 的兩條重複,runner 開檔核對都正確、錯在沒對照檔案清單與總表既有條目。驗收第 3 步「越界檢查」不能省。
  2. 平行 session 重構同一套件會咬到後續棒的 scope:P1 掃描期間 jedi HEAD 從 151c5f88 走到 45f14ad9,P1 範圍只動註解與死碼,但 P2 範圍的 plugin.py 拆成五檔,scope 42 → 49、卡片要重寫。往後每棒驗收跑一次 git diff <掃描版本> HEAD -- <下一棒 scope>。
  3. 密鑰專項吃掉大半票數是結構性的:H2 18/30、H3 24/30、H4 18/27 票投在範圍外的舊憑證案(工具設了 focus 就會掃全 repo)。「不要讀已掃過的路徑」對 scope 內有效、對密鑰專項無效,不必再記,但要知道「真正投在範圍內的票」比帳面少很多。
  4. 「交付四件缺一不可」與「開掃前先回報四件落點」兩句都寫才有效:只寫前一句,H3 仍漏交付(本 arc 第一次、全計畫第四次);加了後一句,H4、P2 都做滿。推翻「寫了就有效」——它只是降低機率。
  5. 首腦補寫報告時要自己答卡片疑點:H3 最有價值的產出(FR-087 戊組「191 筆凍結副本」實為 5 筆快照+186 筆原廠母版)是答疑點五時查出來的,不是工具那兩條。
  6. 被否決的候選也有資訊量:P2 那條 0:3(「element_variables 沒掛租戶 mixin、比姊妹表危險」)被否決的理由是「姊妹三張的隔離開關也全關」——反差不存在,反而坐實四張流程表資料庫層一道隔離都沒有。
  7. 證偽要「重跑」不要「重讀」:P1 三題 XXE 首腦各跑一次只花一分鐘,比讀 runner 的敘述可靠。
  8. 排序用「可利用門檻」比嚴重度標籤好用:第 1 棒選 H2 而非疑點最「大」的 H1,因為 H2 只要登入+猜編號。同一判準也解釋了為什麼第 57 條比同組的第 55 條更急:不用存進去、打一次就炸。

8. 可信度兩層

「這些存在嗎」——高。 六棒面板全 verified、135 票全投出、無漏投;範圍內每一條首腦都自己開檔核對(不信 runner 自述),H2/H1/H3 另在 DEV 資料庫唯讀實查隔離開關、規則數與筆數,P1 五項證偽首腦重跑實測。runner 抓到工具原文一處錯(宣稱 compliance.projects 有隔離、實查關但有 4 條沒生效的規則),首腦複核屬實。

「只有這些嗎」——中等,不可宣稱乾淨。 原因四點:① 工具沒留逐檔閱讀帳本(P2 runner 自陳 coverage.research 空),「49 檔只有這一條」的信心停在中等;② 六棒都以 low 檔次(工具的 effort 等級,子卡標題可見)單輪跑完,沒有第二輪交叉;③ 四棒(H2/H1/H3/H4)票數大半投在範圍外的舊憑證案,真正投在範圍內的票比帳面少(例如 H2 30 票只有 12 票落在 23 檔);④ 總表 §3.4 登記了本 arc「等於沒查」的項目:留言在前端怎麼渲染(儲存型 XSS)、問卷 uid 進 jedi-survey 的租戶檢查、project_audit_rounds/round_stage_transitions 這類無租戶欄的表在 FR-087 之外還有幾張。

9. Commits 清單(BE feature/FR-075,未 push)

依時間順序,git log --oneline --grep='FR-088' 抄出,再補 LOG 第十任 block 列的兩筆跨 arc 裁定 commit;標 ※ 的是不屬本 arc 掃描作業、但與 FR-088 登記或裁定有關的 commit。

hash 一句話
d9d057e2 開卡:讀碼切六棒,母子卡 CM-1669~1675 一次建完
e9507d29 H2 掃描報告(runner)
306ef445 驗收 H2:三條「只驗身分不驗歸屬」全屬實,登記總表第 49~51
ceb455df ※ FR-089/FR-090 補母卡登記(README 登記列緊接 FR-088,故 grep 命中)
1e95823b H1 掃描報告(runner)
60d90cb1 驗收 H1:四條屬實、兩大疑點證偽複核,登記第 52~54+§3.2 第 13
ebd3b1e1 P1 掃描報告(runner)
12d0d06f 驗收 P1:兩條屬實、五項證偽重跑實測、F2 改判範圍外;P2 scope 重算 49 檔
5b2165b5 H3 runner 未交付,首腦補寫報告並驗收;修正 FR-087 戊組 191 筆描述
c6c12e68 H4 掃描報告(runner)
0dbe3586 驗收 H4:改判 1 新 2 重複、viewer 可完成別人任務登記第 59 條;第九任首腦交棒
4a4092f5 ※ 總表 §6 重整:補 viewer 唯讀待裁項、FR-088 三條升 HIGH 待裁項(跨 arc 動作,含 FR-088 項)
05e81f6e ※ 記帳決策者 2026-09-13 五項裁定(viewer 唯讀/問卷不加旗標/程式層併 FR-086/CM-1559 順序歸 FR-094/51、55、57 升 HIGH)——grep 不命中,從 LOG 抄
bf14cda8 ※ 追記 upload_files 加租戶欄+開隔離歸 FR-086 程式層那批——grep 不命中,從 LOG 抄
31c0d7f6 P2 掃描報告(runner)
17664009 驗收 P2:套件本體 49 檔零新發現、對照表證實宿主沒接套件執行 service;六棒收口

本 SUMMARY 自身的 commit 不在上表(寫檔時尚未 commit)。

10. Follow-up 與座標

SUMMARY 之後的後續(現況):

  1. 修正卡(現況:已處理,由 FR-114 修正線修完,隨 1.21.0 出貨):程式層歸屬檢查那批(第 49/50/51/52/53/59,與 FR-086 的第 34/35/37/40/45/47 同批)由 FR-086 側首腦開卡;55/57 合一張「BPMN 輸入強固化」卡;兩邊開完回報卡號後登記進總表 §2.2。
  2. FR-094 三張表:workflow_executions/job_executions/workflow_templates 的隔離開關已接在 FR-094(CM-1770/1771/1772);element_variables、job_evidences、project_audit_rounds、round_stage_transitions 無租戶欄,不在 FR-094,只能靠程式層。
  3. 總表 §3.4 死角:無租戶欄的表在 FR-087 13 張之外還有幾張,母卡收口時單獨盤一次(尚未盤)。
  4. 下一支掃描:jedi-oscal-v2 的宿主半邊(套件半邊已掃;順序依總表 §4,本檔未重讀該段)。
  5. 手冊:security-scan-lead 第一節「≤15 檔」與已裁的 ≤30/40 不一致,未改(收尾類,等令)。

座標:

數字對帳備註(寫本檔時在材料裡看到的口徑差異)

以下數字各處說法不一,本檔採用的口徑已在各段標明;未裁前不改任何來源。

  1. 「12 條」的算法:README/LOG/STATE 都寫「範圍內 12 條(§3.1 第 49~59+§3.2 第 13~16)」,但 §3.1 第 49~59 是 11 列、§3.2 第 13~16 是 4 條,加起來 15;12 是各棒 runner 報的範圍內條數加總(H1 把同一個洞的 F5/F6 算兩條)。本檔寫「12 條(11 條資安+4 條非資安另計)」。
  2. 檔數:母卡標題與 README 前 matter 寫「六棒 150 檔」、CM-1675 卡名寫「42 檔」;P2 實掃 49 檔,六棒實際合計 157。
  3. complete_job/revert_job 行號:總表第 59 條與 README 寫 :655/:833,H4 報告寫 :655/:805,P2 報告寫 :611/:788——同一支檔在三棒掃描期間有漂移。本檔採總表。
  4. H4 範圍外條數:README 與 LOG 寫「6 條憑證裡 1 條新型態」,H4 報告 §3.2 文字寫「六條」但表格只列外-1~外-5 五列。
  5. DEV 筆數:README「資料庫層實況」表(2026-09-12 首腦查)寫 job_evidences 782/element_variables 3,011/job_executions 10,783/workflow_templates 17,206;H2 驗收複核寫 848/3,236/11,065,P1 驗收寫 workflow_templates 18,267。同一庫兩組數字,可能是查詢時點不同。
  6. H2 疑點分類:README 一頁看完寫「八個疑點五成立、兩證偽、一無問題」,H2 報告 §5 的 ⑥ 與 ⑧ 都標「沒問題」(兩項)、④ 標「部分推翻」、⑤ 標「屬實但沒引發繞過」。
  7. 子卡狀態:STATE 第 442 行與 LOG(工作區未 commit 的第十任 block)都寫「母卡+六子卡已改 Done(決策者令)」,README front matter 六張子卡仍標 修正待驗證(未同步)。
  8. LOG 的 P2 block 尚未 commit:已 commit 的 LOG(HEAD 17664009)最後一個 block 是第九任首腦交棒;P2 驗收+六棒收口+第十任交棒的 block 在工作區(git status 顯示 LOG/STATE 均為 modified),寫本檔時一併讀了工作區版本。
  9. FR-064 SUMMARY 檔頭:派工單說抄它前 5 行的 title/status/relates 三欄 front matter,實際該檔沒有 YAML front matter(是 H1 標題+引言塊);本檔改用 README 同款 YAML 三欄+引言塊。

附錄:本檔用到的詞彙

詞 白話
套件/宿主 套件=jedi-flow-engine 這個「零件供應商」,自己沒有對外網址;宿主=主專案接它的那半邊,所有 API 入口與權限檢查都在這裡
scope/範圍內/範圍外 每棒卡片列的檔案清單;工具的密鑰專項會掃全 repo,撈到的東西若不在清單內就是「範圍外」,不算該棒發現
面板/檢查員/票 工具對每條候選發現派三個獨立檢查員各投一票;「3:0」=三人都認為成立,「0:3」=三人都否決
verified(stamp) 工具跑完自己蓋的章,代表每條候選都投完、沒有漏投;不代表「沒有漏掉的問題」
候選/去重 研究員報上來的原始條目;同一個洞被兩位研究員各報一次會合併成一條
守門/歸屬檢查 後端在做事前檢查「你有沒有登入」「你是不是這個專案的人」「這筆資料是不是你的」;本 arc 多數問題是只做了第一項
RLS/隔離開關/規則 資料庫層「每個客戶只能看自己資料」的機制;「開關關但有 4 條規則」=規則寫好了但沒啟用,等於沒有
租戶欄/tenant 欄 資料表裡標「這筆是哪個客戶的」的欄位;沒有這個欄位的表,資料庫層想擋也擋不了,只能靠程式
fail-open 出錯(例如查不到資料、元件沒注入)時預設放行;相反是 fail-closed 預設擋下
JSONB 資料庫裡可存任意 JSON 結構的欄位型別,沒有形狀限制
DI 容器 程式啟動時把各元件組裝、登記起來的登記簿;「建了 provider 沒人取用」=登記了但沒人來拿
能力點(capability) 系統裡「有沒有權限做這件事」的細粒度開關,例如 flow_template.read
密鑰專項 工具設了 focus 就會額外跑一次「找寫死的密碼與金鑰」的掃描,掃全 repo 不受 scope 限制
交付四件 runner 做完一棒必須交的:報告檔、commit、Notion 卡回寫(含 commit hash 與 run ID)、卡片狀態改「修正待驗證」