本卡屬 FR-088(母卡見 FR-088 母案),第 2 棒 H1:稽核階段推進/回退/階段歷程,含推進時觸發的業務 handler 與前置條件檢查(BE repo)。只掃不修。
每個稽核專案的每一輪都有「目前在哪個階段」的橫幅,管理者或該階段的負責角色按「推進」就進下一階段(並觸發像「啟動稽核」「結案」這種業務動作),管理者也能按「返回上一階段」。這一棒掃的是這兩個按鈕背後的 21 檔:誰能按、按了系統核對了什麼、有沒有辦法按到別人專案的按鈕。只找問題、不修問題。
推進與回退是改變稽核狀態的最高權限操作(啟動稽核、結案、退回規劃期)。首腦讀碼發現它的權限判定用了兩個編號(專案 uid、輪次 uid),兩個編號各查各的、從不核對彼此;另有一個旗標在請求裡存在兩個版本。這兩個疑點若成立,一個是跨專案操作、一個是跳過檢查啟動稽核。
行號已開檔核對,是思考起點不是檢查清單:
app/flow_engine/service/stage_advance_service.py:529-549 _resolve_workflow_context() 只用 round_uid 找輪次與流程;:578-587 _get_user_role() 只用 project_uid 查你的角色;advance_stage()(:191起)與 get_current_stage_info()(:82起)都是「用 project_uid 查角色、用 round_uid 做事」。首腦 grep 整支檔與 stage_rollback_service.py,沒有任何一處比對 round.project_id 與 project(輪次表有 project_id 欄:jedi-compliance-audit infra/model/project_audit_round.py:27),而 project_audit_rounds 隔離關閉、零規則(DEV 實查)。要驗的攻擊路徑:我是 A 專案的 manager,打 POST /project/<A 的 uid>/audit-round/<B 的 round uid>/stage/advance,能不能推進 B 專案的稽核?回退同理(stage_rollback_service.py:73-101)。 回退只查「你在 A 是不是 manager」。force 旗標有兩個版本:api/flow_engine/serializers/stage_advance.py:11-12 body 有 force(bool)與 ctx(自由字典);service :209 ctx.setdefault("force", force)——只在 ctx 裡沒有 force 時才填;:303 的「非 manager 不可 force」檢查看的是外層 force 變數;但 handler app/flow_control/service/oscal_stage_handlers.py:41 force = bool((ctx or {}).get("force")) 讀的是 ctx 裡的。要驗:外層 force=false、ctx={"force": true},非 manager 的階段負責角色能不能跳過 launch_audit 的「規劃就緒檢查」(:42-45)啟動稽核?其他 handler 有沒有同樣讀 ctx 的?前置條件檢查(:314)看的是哪個 force?api/flow_engine/routes/stage_rollback_route.py:52-67 只掛 jwt_required,直接 list_stage_transitions(round_uid),service 端只查 NotFound(jedi-compliance-audit audit_round_app_service.py:867-875),完全不看 project_uid、不看你是誰。歷程含誰在何時推進/退回、理由。stage_advance_service.py:82-120 對不是該專案的人只是把 user_can_advance 設 false,仍回目前階段、進度、前置條件狀態、流程 uid。要驗是不是刻意的(可能是),記錄外洩範圍。ctx 整包往下傳給 handler:stage_advance_service.py:358 附近把 ctx 交給 handler.execute(...)。六支 handler(oscal_stage_handlers.py:40-140)與六支前置條件(oscal_stage_preconditions.py:35-128)各自從 ctx 拿什麼?有沒有拿使用者可控的值當內部參數(例如覆寫 user_nickname、決策 decision)?decision 與 comment 落到哪:review 階段 decision=reject 時 comment 必填,comment 寫進哪張表、有沒有長度限制、之後在哪裡渲染。domain/flow_engine/service/stage_completion_registry.py:128-146 同 key 會覆蓋並只印 warning。要驗:有沒有任何 request 期的路徑能觸發註冊(應該只在啟動時),否則只記錄。stage_objects、round_stage_transitions、project_audit_rounds 三張表隔離全關、零規則、無租戶欄(DEV 實查)——第一條若成立,資料庫層零兜底。known_project_id 繞路(stage_rollback_service.py:171-182 回傳 project_id 供 revert_job 用)——本棒要看這條繞路有沒有把可控值餵進守門。complete_main_workflow_job(H4 檔 workflow_execution_service.py:513,本棒可越界讀)。