性質:純盤點(零 code 變更)。全站
api/寫入端點(POST/PUT/DELETE/PATCH)+ 敏感 GET(匯出/下載/報表/qrcode/token)逐支判軸,供 user 過目定調。 上游設計:../../analysis/2026-07-07-unified-auth-guard-design.md(§2 軸模型、§3.2 決策表、§5.0 scope、§7 phase)。 交叉驗證:../../analysis/2026-07-07-known-pits-remediation-tracker.md§SEC 六族 75 條。 建立:2026-07-07,branchfix/v1.8.0-bugs。逐模組事實底稿在_partials/(route/method/呼叫 service/守門 file:line 皆已實查引用)。 盤點法:8 個 subagent 按模組分工,追 route → app service body 找守門呼叫;主 session 判軸 + 交叉驗證。
軸別(設計 §2):
require_platform_admin)。全域共用資源(框架、平台設定、LOG)。require_super_admin)。租戶級系統設定 / 範本 / 管理頁;Phase 3 過渡、Phase 4 換 capability。assert_project_manager / SspPermissionChecker / workflow guard)。狀態:
已守 — 已有正確守門,本案不動。待補 — 軸別明確、目前裸奔,待掛守門(Phase 2b/3/4)。待定調 — 軸別或角色層級未定,集中在 §1 問 user。no-action — 刻意公開/自我 scope/低風險,確認即可。dead — route 未註冊(註解停用)或 service 為 stub,無 live 風險。Phase:2b=軸③專案域補守門|3=軸①②主體域 decorator|4=軸④capability+軸⑤signed-token|—=已守/no-action/dead。
| 分類 | 端點數(約) | 說明 |
|---|---|---|
| 已守(no action) | ~110 | participant 12、SSP-scoped 34、audit-round 家族 30、framework 家族 18、cloud_integration 5、其他個別 |
| 待補 軸③(Phase 2b) | ~35 | grc job/review/comment/export、flow_engine 證據&任務(含停用 guard 復活)、project_summary_report、subtask_status_history、task_survey 填答、evidence_classification state |
| 待補 軸①②(Phase 3) | ~55 | auth role/user/tenant/org CRUD、system_config、system_menu、device、bulletin、information_system、notification test、remote_agent 管理面、log export(軸①) |
| 待補 軸⑤ signed-token(Phase 4) | 2(+新發現若干) | otp-qrcode、framework OSCAL download(+ uploadfile download/pdf-preview、report preview、task-survey import-template 待定調) |
| 待定調(§1) | ~15 群 | 見下 |
| no-action(刻意) | ~20 | 公開登入流程、OAuth/webhook callback、health、FR-039 控制面、自我 scope(ai/chatbot) |
| dead(未註冊/stub) | ~30 | FR-038 DEAD-V1 註解 route、2A dark stub |
| 母數 | 373 寫入 method + 敏感 GET | 8 partial 逐支列出 |
「已守」大宗=Phase 1 之前已鋪守門的三塊(participant / SSP / audit-round)+framework。裸奔集中在系統管理頁(軸②)與部分專案域寫入(軸③),與設計 §5.0 判斷一致。
判定規則:spec 權限矩陣有寫→照 spec;spec 沒寫 / FE-BE 粒度不一致 / 權限主體不明→集中在此,不自己猜。
定調結果(全數同意建議案):
| # | 定調 |
|---|---|
| D1 | 軸②(Phase 3 掛 super-admin,Phase 4 換 module_frame.manage)。範本無 participant,tracker 歸 C 族係誤判 |
| D2 | 軸①platform(clone 全域框架三件組,同 framework 家族) |
| D3 | 軸②;直接採用既有錯誤碼 GRC_FLOW_TEMPLATE_NO_PERMISSION(403041) |
| D4 | 軸①platform(建租戶是平台操作) |
| D5 | 維持開放(by design,建立者自動 manager);收緊等 Phase 4 project.create |
| D6 | 軸③ manager,粒度照 spec 權限矩陣對齊 |
| D7 | manager-only(job 建改刪=規劃行為;執行/完成走 flow_engine 給 assignee) |
| D8 | manager(若 reviewer 角色實際在用再放寬為 manager+reviewer) |
| D9 | 軸②過渡 → Phase 4 survey.manage |
| D10 | 填答=被指派者本人(assignee);configure/import=manager |
| D11 | enroll-token/revoke/rebind → 軸② admin;register/heartbeat 維持 F |
| D12 | 改刪=owner 本人;export → 軸② admin |
| D13 | 軸②(租戶資產,不引入 owner) |
| D14 | file download/pdf-preview → 軸⑤ signed token(動之前必查 FE 呼叫方式,有前科);schedule-report/preview → 補 JWT+admin+path 消毒(最危險,插隊優先);task-survey import-template → 補 JWT(查 FE);靜態範本、issue/get_members → 補 JWT |
| D15 | 直接移除 route(debug 殘留,hardcoded email 已實查確認 user_route.py:164) |
| # | 端點群 | 問題 | 傾向(供參考) | 對應 pit |
|---|---|---|---|---|
| D1 | module_frame 全模組(範本 CRUD + defaults + 元件/資產/party,~43 支,全裸奔) | 範本(module_frame)管理權限主體是誰?範本無 project participant,SspPermissionChecker 對 template 分支是用帳號層 is_admin(軸②)。但 tracker 把 module-frame-defaults#5/#6 歸 **C 族(軸③) — 兩者矛盾。 |
軸②super-admin 或 軸④capability(module_frame.manage);非**軸③(範本非專案) |
module-frame-defaults#5/#6 |
| D2 | oscal resource-libraries(POST create / publish) | 資源庫(clone framework_version 三件組)是平台級共用資產還是租戶級? | 軸①platform(clone 全域框架)或 軸②租戶 | — |
| D3 | flow_engine flow-templates(create/update/delete/duplicate/publish/unpublish,6 支) | 流程範本管理權限主體?現只有 _guard_not_builtin(資源守門,非角色)。 |
軸①platform 或 軸②(見 flow-template-manage spec) | — |
| D4 | auth tenant CRUD(tenant POST/PUT/DELETE、add_tenant_with_defaults) |
建/改/刪租戶是平台操作還是租戶管理員操作? | 軸①platform-admin(建租戶=平台級) | tenant-manage#1/#4 |
| D5 | /oscal-project/start(建專案) | 任何登入者可建專案(現況 by design,建立者補 manager);要不要限制? | 維持開放 或 軸④project.create |
project-create#2 |
| D6 | planning-status#1 | FE/BE 權限粒度不對齊(繞 FE 權限面更寬)——BE 該收斂到哪個粒度? | 軸③,粒度待對齊 spec | planning-status#1 |
| D7 | grc job 編輯(job PUT/DELETE/create、batch-complete) | 專案任務編輯是 manager-only 還是被指派 auditor 也可寫? | 軸③,角色層級待定 | project-task-edit#1/#2、planning-batch-ops#4 |
| D8 | grc review mark/unmark(control/AO ×4) | 標記審查是 auditor / reviewer / manager 哪一層? | 軸③,角色層級待定 | project-planning#4 |
| D9 | survey 設計/管理(survey/folder/question/page/discussion CRUD,~20 支) | 問卷設計器/管理是租戶級 B 族還是有 owner 概念? | 軸②super-admin 或 軸④survey.manage |
survey-v2-designer#1、survey-v2-manage#1/#2 |
| D10 | task_survey 填答端點(answers update/patch/checkpoint、history revert、configure) | 填答/設定是「被指派者本人」還是「任一專案 participant」? | 軸③,assignee vs participant 待定 | survey-v2-fill#1/#2 |
| D11 | remote_agent FR-039 FE 端點(create/update/delete/revoke/unrevoke/rebind/enroll-token gen+revoke) | 程式碼註解稱「需 admin」但全無守門;FR-039 demo 要維持開放,還是這些涉及 enroll-token(安全憑證)的要補 admin? | enroll-token/revoke/rebind → 軸②admin;demo 控制面(register/heartbeat)維持 F | remote-agent-manage#4 |
| D12 | feedback 編輯/刪除(PUT/DELETE、file DELETE、export) | 回饋編輯刪除是 owner 還是 admin?export(匯出全站 feedback)是否限 admin? | 編輯刪除→owner;export→軸②admin | feedback-management#2/#5 |
| D13 | information_system / device 管理 | 資訊系統/設備是租戶管理員(軸②)還是有建立者 owner 概念?(device 連 owner 都沒擋) | 傾向軸②super-admin(租戶資產) | information-system-manage#1、device-manage#1/#6 |
| D14 | **【新發現】未認證端點(不在 75 pit) | 下列 route 完全無 @jwt_required**,處置待定(加 JWT / 軸⑤ signed-token / owner):• GET /file/download/<uid>、GET /file/pdf-preview/<uid>(uploadfile,任意 uid 下載)• GET /schedule-report/preview(report,query 拼路徑→任意檔讀取風險)• GET /task-survey/import-template/<task_uid>(洩該 task 題庫)• GET /survey/import-template、/module-frame/download/template+/yaml-template(靜態範例,低風險)• GET /issue/get_members(純讀裸奔) |
download/preview→軸⑤或JWT+owner;靜態範本→補 JWT;issue→補 JWT | 無(新增) |
| D15 | **【新發現】POST /send-user-test-mail |
無 @jwt_required、寄測試信到 hardcoded email**(raymond.jedicotech@gmail.com);疑似 debug 殘留。 |
建議移除或補 JWT+admin | 無(新增) |
D1 是最大結構性問題:module_frame 43 支全裸奔且軸別本身有爭議(tracker 軸③ vs 程式碼軸②),必須先定調才能規劃 Phase 2b/3 分工。
tracker §SEC 六族 + 漏網 8 條,每條對映到矩陣模組/狀態。對不上的回頭補。
| pit | 對映端點 | 軸 | 狀態 |
|---|---|---|---|
| user-log#1 | GET /log/api-logs/export |
軸①(角色)+ 認證 | 認證已修(182bb2f7);角色守門(SEC-002)=軸① Phase 3 |
| compliance-framework-manage#5 | GET /oscal-framework-version/download/oscal/<uid> |
軸⑤ | ✅ 已修待驗(40a7d9c5,signed-token) |
| user-profile#3 | GET /otp-qrcode/<uid> |
軸⑤ | ✅ 已修待驗(40a7d9c5,signed-token+自我限定 bind_uid) |
| pit | 對映模組/端點 | 狀態 |
|---|---|---|
| ldap-config#1 | user_auth_provider ldap/connect-test、system_config LDAP group |
待補 軸② P3 |
| notify-channels#2 | notify_config test(已有 admin 守門)、system_config notify | 部分已守;config 寫入待補 P3 |
| smtp-config#6 | system_config SMTP group + notification mail/test |
待補 軸② P3 |
| issue-integrate-config#2/#6/#7 | system_config issue-integration group | 待補 軸② P3;#2 另=D 族 token 明文(見下) |
| system-menu-manage#3 | system_menu POST/PUT/DELETE | 待補 軸② P3 |
| role-manage#1 | auth role POST/PUT/DELETE/status | 待補 軸② P3 |
| user-form#2、user-manage#1、user-import#1 | auth user POST/PUT/DELETE/change-status/batch-upload/upload | 待補 軸② P3 |
| tenant-manage#1/#4 | auth tenant CRUD | 待定調 D4(軸①?) |
| department-manage#8 | auth org-unit CRUD | 待補 軸② P3 |
| device-manage#1/#6 | device POST/PUT/DELETE | 待定調 D13(傾向軸② P3);#6=E 族(IP 格式 FE) |
| information-system-manage#1 | information_system POST/PUT/DELETE | 待定調 D13 |
| storage-config#3 | (storage-config 設定頁)→ system_config storage group | 待補 軸② P3 |
| background-services#1 | 見 F 族(設計如此) | no-action 候選 |
| _overview#2/#5 | system-admin 首頁彙總(多為讀) | _overview#5=F 族 |
| pit | 對映端點 | 狀態 |
|---|---|---|
| planning-basic-info#1 | grc project/<uid> PUT(update_project) |
待補 軸③ P2b |
| project-planning#4 | grc review mark/unmark | 待定調 D8 |
| project-task-edit#1/#2 | grc job PUT/create/DELETE | 待定調 D7;#2 另=E 族(審核人 FE) |
| planning-batch-ops#4 | grc jobs/batch-complete |
待定調 D7 |
| project-create#2 | /oscal-project/start |
待定調 D5 |
| project-ap-list#1 | grc project/<uid>/ap/<ap_uid> PUT(dark stub) |
dead(2A stub,不做事);DELETE 已守 |
| project-ssp-basic#1 | oscal /ssp/<uid>/system-characteristic PUT |
已守(require_manager)→ pit 應已解;#1 另=E 族(name 必填 FE) |
| project-ssp-components#2 | oscal /ssp/<uid>/components |
已守(require_manager)→ pit 應已解 |
| project-ssp-inventory#1 | oscal /ssp/<uid>/inventory-items |
已守 → pit 應已解 |
| project-ssp-leveraged#5 | oscal /ssp/<uid>/leveraged |
已守 → pit 應已解 |
| project-ssp-parties#3 | oscal /ssp/<uid>/parties |
已守 → pit 應已解 |
| module-frame-defaults#5/#6 | module_frame defaults PUT/DELETE | 待定調 D1(軸別爭議) |
交叉驗證發現:C 族的 5 條
project-ssp-*pit 在實查中已有require_manager守門(SSP-scoped 34 支全數已守)——這些 pit 可能是 spec §12 撰寫時的舊觀察、之後已補。建議 user 定調時將其標為「已解,複驗即可」。真正待補的 C 族集中在 grc(job/review/update_project/batch)與 flow_engine。
| pit | 對映 | 狀態 |
|---|---|---|
| issue-integrate-config#2 | system_config GET secret 明文回傳(實查確認 jedi_system_config 無 mask) |
待修(遮罩),逐案 |
| user-log#3(response 未遮罩)/ #4(api_logs 無 tenant 隔離) | log 模組 | 逐案(併軸① P3 時檢視) |
| forget-password#1(帳號列舉)、user-profile#2(讀他人完整資料) | auth | 逐案 |
forget-password#2、device-manage#6、survey-v2-fill#1、project-task-edit#2、project-ssp-basic#1 — 非授權缺口,屬 FE 驗證,本案不處理,維持 tracker E 族後排。
cloud-integrations#4/#2(OAuth callback state token / webhook channel token,實查確認)、my-audits#8(SQL 已鎖自己)、remote-agent-manage#4(FR-039 控制面 register/heartbeat)、evidence/_overview#6/#7(FR-039 demo)、ai-dashboard#6/#2(health 公開)、background-services#1、_overview#5、login#7/#3(captcha 順序 / 設計)→ 全數 no-action,已於各模組矩陣標明理由(防下次重查)。
compliance-framework-manage#5(A)、user-profile#3(A)、information-system-manage#1(B/D13)、planning-status#1(D6)、user-profile#6(LDAP 綁定不驗本人,低風險→逐案)、user-manage#2(排序唯讀,非缺口)、ai-dashboard#2(F)、login#7(F)——8 條全部對映到矩陣列(無漏)。
每支端點一列。事實欄(現況守門/呼叫 service)源自
_partials/,此處補「軸別/角色/Phase/狀態」判斷欄。dead(未註冊/stub)僅列關鍵者,完整 dead 清單見各 partial。
| Route path | Method | 現況守門 | 軸別 | 角色 | Phase | 狀態 | 備註 |
|---|---|---|---|---|---|---|---|
| /oscal-framework(+/versions/parse-jobs 全家族 CRUD/import/confirm) | POST/PUT/DELETE | require_platform_admin | ① | platform | — | 已守 | 18 支,framework_app/version/edit/parse |
| /oscal-framework-version/download/oscal/ |
GET | @signed_token_required | ⑤ | — | 4 | 已修待驗(40a7d9c5) | 發 token 端點 .../oscal/ |
| /oscal/resource-libraries | POST | 僅 JWT | ①/② | — | 待定 | 待定調 D2 | clone framework_version |
| /oscal/resource-library/ |
POST | 僅 JWT | ①/② | — | 待定 | 待定調 D2 | 狀態轉換 |
| /ssp-excel-imports、/ssp-docx-imports(parse/discard/confirm,6 支) | POST/DELETE | 條件式:ssp→require_project_manager;framework_version/module_frame→僅 tenant | ③(+②) | manager | 2b/待定 | 部分已守 | ssp 分支已守;非 ssp 分支軸別隨 D1/D2 |
| /ssp/ |
POST | require_project_manager | ③ | manager | — | 已守 | |
| /ssp/ |
POST/PUT/DELETE | SspPermissionChecker.require_manager | ③ | manager | — | 已守 | SSP-scoped 全數已守;解 project-ssp-* pit |
| /ssp/ |
GET | require_participant | ③ | 參與者 | — | 已守 | 敏感 GET 已守 |
| /ssp/ |
POST | require_participant | ③ | 參與者 | — | 已守 | 讀預覽 participant,confirm 為 manager |
| /ssp/ |
GET | require_participant | ③ | 參與者 | 2b | 已修待驗(99945a10) | export_from_ssp_version 補 require_participant;export_from_module_frame(軸②)不動 |
| Route path | Method | 現況守門 | 軸別 | 角色 | Phase | 狀態 | 備註 |
|---|---|---|---|---|---|---|---|
| /module-frame(CRUD)、/clone、/item(+xml)、/import(+yaml/verify) | POST/PUT/DELETE | 僅 JWT | ②/④? | — | 待定 | 待定調 D1 | 範本 CRUD ~13 支 |
| /module-frame/ |
PUT/DELETE | 僅 JWT | ②/④? | — | 待定 | 待定調 D1 | module-frame-defaults#5/#6 |
| /module-frame/ |
POST/PUT/DELETE | 僅 JWT | ②/④? | — | 待定 | 待定調 D1 | 範本子結構 ~25 支 |
| /module-frame/ |
POST | 僅 JWT | ②/④? | — | 待定 | 待定調 D1 | |
| /module-frame/ |
GET | 僅 JWT | ②/④? | — | 待定 | 待定調 D1 | 敏感 GET(含 MF/SSP 實際資料) |
| /module-frame/download/template、/download/yaml-template | GET | 無 JWT | — | — | 待定 | 待定調 D14 | 靜態範例檔,低風險 |
| (ModuleFramesRoute/StartRoute) | POST | 僅 JWT | — | — | — | dead | init 註解未註冊 |
| Route path | Method | 現況守門 | 軸別 | 角色 | Phase | 狀態 | 備註 |
|---|---|---|---|---|---|---|---|
| /oscal-project/start | POST | 僅 JWT(建立者補 manager) | ④? | — | 待定 | 待定調 D5 | project-create#2 |
| /projects/ |
POST | _check_role(manager) | ③ | manager | — | 已守 | |
| start-auditing、launch-reverify、generate-draft、finalize、AR judge/observation/risk、AP set-* | POST/PUT | _check_role/_check_auditor(manager override) | ③ | auditor | — | 已守 | |
| ap/docx-imports、ar-imports(parse/confirm/discard) | POST/GET/DELETE | resolve_ap/round_and_check_auditor/participant | ③ | auditor/參與者 | — | 已守 | |
| poam remediation/milestone(新增/改/刪) | POST/PUT/DELETE | _check_manager | ③ | manager | — | 已守 | |
| audit-rounds/list、round/ap/findings/risks/poam-items(GET/list POST) | GET/POST | 僅 JWT | ③ | 參與者(讀) | 2b? | 讀取 | 專案域讀未驗參與者;RLS 擋租戶,低優先,可併 2b 順補 |
| Route path | Method | 現況守門 | 軸別 | 角色 | Phase | 狀態 | 備註 |
|---|---|---|---|---|---|---|---|
| /project-summary-report(create/update/delete) | POST/PUT/DELETE | 僅 JWT(未查專案歸屬) | ③ | 待定 | 2b | 待補 | project-summary-report#1;角色層級待 spec |
| /project-summary-report/export/ |
GET | 僅 JWT | ③ | 待定 | 2b | 待補 | 敏感 GET(PDF),無 ownership |
| /project-summary-report/history-revert | POST | 僅 JWT | ③ | 待定 | 2b | 待補 | |
| /subtask-status-history(POST/PUT/DELETE) | POST/PUT/DELETE | 僅 JWT | ③ | 待定 | 2b | 待補 | 需確認可反查專案輪次 |
| Route path | Method | 現況守門 | 軸別 | 角色 | Phase | 狀態 | 備註 |
|---|---|---|---|---|---|---|---|
| /projects/batch-delete、/project/ |
POST/DELETE | is_admin 或 owner/manager | ③ | manager | — | 已守 | |
| /project/ |
PUT | assert_project_manager | ③ | manager | 2b | 已修待驗(80514dff) | D6 planning-basic-info#1 |
| /project/ |
PUT | 僅 JWT(dark stub) | ③ | — | — | dead | 2A stub,不做事 |
| /project/ |
DELETE | manager | ③ | manager | — | 已守 | |
| /job/ |
PUT/DELETE/POST | assert_project_manager | ③ | manager | 2b | 已修待驗(80514dff) | D7;delete_job 新增 project_uid 參數 |
| /jobs/batch-complete | POST | 僅 JWT | ③ | manager | 2b | 待補(handoff) | D7;每 job 完成走 flow_engine complete_job,隨 guard 復活受守;自身 project 反查待復活波 |
| control/AO review mark/unmark(×4) | POST/DELETE | assert_project_manager | ③ | manager | 2b | 已修待驗(80514dff) | D8 project-planning#4 |
| job-execution comments POST | POST | 僅 JWT | ③ | 參與者? | 2b | 待補(handoff) | 角色未定調(參與者 vs manager);需 assert_project_participant canonical |
| job/comment PUT/DELETE | PUT/DELETE | 作者本人(owner) | owner | — | — | 已守 | owner-of-record |
| /jobs/export | GET | 僅 JWT | ③ | 參與者 | 2b | 待補(handoff) | 敏感 GET;參與者級讀,需 assert_project_participant canonical(見 §7) |
| /jobs/import/confirm | POST | manager | ③ | manager | — | 已守 | validate/import 為讀 |
| /start-task-execution | POST | manager | ③ | manager | — | 已守 | |
| projects/list、jobs/list、jobs/my/list、audits/my/list | POST | 僅 JWT(可見性過濾/自我 scope) | ③ | — | — | 讀取 | POST 實為查詢 |
| Route path | Method | 現況守門 | 軸別 | 角色 | Phase | 狀態 | 備註 |
|---|---|---|---|---|---|---|---|
| /task/complete、/task/revert | POST | **已有(停用) assert_workflow_project_participant(body return) |
③ | 參與者 | 2b | 待補(復活)** | FR-038 2B 重建 v2 反查鏈 |
| /job-evidence PUT、DELETE | PUT/DELETE | **已有(停用) 同上 | ③ | 參與者 | 2b | 待補(復活)** | 停用期間僅 RLS |
| /job-evidences POST、/job-evidence POST | POST | 僅 JWT | ③ | 參與者 | 2b | 待補 | 證據新增裸奔 |
| /process/comments PUT、/job-execution/task/link PUT | PUT | 僅 JWT | ③ | 參與者? | 2b | 待補 | |
| /flow-templates(create/update/delete/duplicate/publish/unpublish) | POST/PUT/DELETE | 僅 JWT(僅 _guard_not_builtin 資源守門) | ①/② | — | 待定 | 待定調 D3 | 範本管理權限主體 |
| /stage/advance | POST | role gate(force 需 MANAGER) | ③ | 角色/manager | — | 已守 | |
| /flow-templates/validate | POST | 僅 JWT | — | — | — | 讀取 | 純驗證 |
| Route path | Method | 現況守門 | 軸別 | 角色 | Phase | 狀態 | 備註 |
|---|---|---|---|---|---|---|---|
| /user(POST/PUT/DELETE)、/user/change-status、/user/batch-upload、/users/upload | POST/PUT/DELETE | 僅 JWT(delegate jedi-auth,主專案無守門) | ② | admin | 3 | 待補 | user-manage#1/user-form#2/user-import#1 |
| /user-profile/ |
PUT | route self-check(uid==本人) | owner | — | — | 已守 | 本人改自己 |
| /role(POST/PUT/DELETE)、/role/status | POST/PUT/DELETE | 僅 JWT | ② | admin | 3 | 待補 | role-manage#1 |
| /tenant(POST/PUT/DELETE)、add_tenant_with_defaults | POST/PUT/DELETE | 僅 JWT | ①? | — | 待定 | 待定調 D4 | tenant-manage#1/#4 |
| /org-unit(POST/PUT/DELETE) | POST/PUT/DELETE | 僅 JWT | ② | admin | 3 | 待補 | department-manage#8 |
| /user/download/sample | GET | 僅 JWT | ② | admin | 3 | 待補 | 敏感 GET(scoped 內容) |
| /otp-qrcode/ |
GET | @signed_token_required(bind_uid) | ⑤ | — | 4 | 已修待驗(40a7d9c5) | /otp-qrcode-token(@jwt) 發放、token 綁本人 uid=只能拿自己 QR |
| /totp-secret | POST | 僅 JWT(get_user_context 綁本人) | owner | — | — | no-action | 取本人 TOTP secret |
| /send-user-test-mail | POST | 無 JWT(hardcoded email) | — | — | 待定 | 待定調 D15 | 疑 debug 殘留 |
| /login、/refresh、/logout、/forget-password、/change-pwd-*、/otp-verify、/otp-resend、/verify-captcha | POST/GET | 無/refresh JWT | F | — | — | no-action | 公開登入流程 |
| user_auth_provider /auth-provider POST/DELETE | POST/DELETE | 僅 JWT(可指定任意 user_uid) | ②/owner | 待定 | 3 | 待補 | 應限本人或 admin |
| user_auth_provider /ldap/connect-test | POST | 僅 JWT | ② | admin | 3 | 待補 | ldap-config#1 |
| Route path | Method | 現況守門 | 軸別 | 角色 | Phase | 狀態 | 備註 |
|---|---|---|---|---|---|---|---|
| survey/folder/question/page/discussion(CRUD/reorder/move/clone/release/import,~20 支) | POST/PUT/DELETE | 僅 JWT(jedi_survey,全裸) | ②/④? | 待定 | 待定 | 待定調 D9 | survey-v2-designer/manage |
| /survey/import-template | GET | @signed_token_required | ⑤ | — | 4 | 已修待驗(40a7d9c5) | /survey/import-template/token(@jwt) 發放;BatchImportDialog 改帶 ?st= |
| task-survey update/configure/import-answers | PUT/POST | 僅 JWT(有專案 context 未驗) | ③ | 待定 | 待定 | 待定調 D10 | |
| project-survey answers(update/patch/checkpoint)、history/revert | PUT/POST | 僅 JWT | ③ | assignee? | 待定 | 待定調 D10 | survey-v2-fill#1/#2 |
| /task-survey/import-template/<task_uid> | GET | @jwt_required | — | — | 4 | 已修待驗(40a7d9c5) | 唯一 consumer 在 _archived=dead,直接補 JWT 即可(4b 判定) |
| Route path | Method | 現況守門 | 軸別 | 角色 | Phase | 狀態 | 備註 |
|---|---|---|---|---|---|---|---|
| task-assignee、project-participant、project-control-participant、control-group-participant(POST/PUT/DELETE,12 支 live) | POST/PUT/DELETE | assert_project_manager | ③ | manager | — | 已守 | scope 各異(project/group/control) |
| task-assignees/batch | POST | 僅 JWT(stub return []) |
③ | — | — | dead | 2A stub |
| process-participant / project-group-participant(全組) | POST/PUT/DELETE | 部分無守門 | ③ | — | — | dead | init 註解未註冊 |
| remote-agents(create/update/delete) | POST/PUT/DELETE | 僅 JWT | ② | admin? | 3 | 待定調 D11 | 註解稱需 admin |
| agents/enroll-token(gen/revoke)、agents/ |
POST | 僅 JWT | ② | admin? | 3 | 待定調 D11 | 安全憑證,優先定調 |
| agents/register、agents/heartbeat | POST | 無 JWT | F | — | — | no-action | FR-039 控制面(mTLS/token) |
| Route path | Method | 現況守門 | 軸別 | 角色 | Phase | 狀態 | 備註 |
|---|---|---|---|---|---|---|---|
| cloud_integration google-drive(disconnect/auth-url/retry/rebuild-all) | DELETE/POST | is_admin / super-admin | ② | admin | — | 已守 | |
| cloud_integration verify-and-repair-folders | POST | project manager | ③ | manager | — | 已守 | |
| cloud_integration init-folders | POST | 僅 JWT(刻意不擋 is_admin) | ③? | — | 待定 | 待定 | 註解稱避免擋 PM,但也無 PM 檢查;併 D13 檢視 |
| cloud_integration webhook/register | POST | 僅 JWT(doc 稱 admin only) | ② | admin | 3 | 待補 | |
| cloud_integration OAuth callback、webhook push | GET/POST | 無 JWT(state/channel token) | F | — | — | no-action | cloud-integrations#2/#4 |
| system_config(config CRUD、by-group/key) | POST/PUT/DELETE | 僅 JWT | ② | admin | 3 | 待補 | smtp/ldap/notify/issue B 族 |
| system_config GET(by-group/uid/key) | GET | 僅 JWT(secret 明文) | ② + D | admin | 3 | 待補 | issue-integrate-config#2(遮罩 D 族) |
| system_menu(POST/PUT/DELETE) | POST/PUT/DELETE | 僅 JWT | ② | admin | 3 | 待補 | system-menu-manage#3 |
| information_system(POST/PUT/DELETE) | POST/PUT/DELETE | 僅 JWT | ② | 待定 | 3 | 待定調 D13 | information-system-manage#1 |
| device(POST/PUT/DELETE) | POST/PUT/DELETE | 僅 JWT(無 owner) | ② | 待定 | 3 | 待定調 D13 | device-manage#1 |
| bulletin(POST/PUT/DELETE) | POST/PUT/DELETE | 僅 JWT | ② | admin | 3 | 待補 | bulletin-manage#4 |
| feedback(POST/PUT/DELETE/file/export) | POST/PUT/DELETE | 僅 JWT | owner/② | 待定 | 待定 | 待定調 D12 | feedback-management#2/#5 |
| evidence_classification trigger-classify | POST | project manager | ③ | manager | — | 已守 | |
| evidence_classification run/state、archive、ground-truth | PUT/POST | 僅 JWT(僅 tenant 解析) | ③/② | 待定 | 2b | 待補 | classify 專案域;GT 匯入或屬 admin |
| notification、mail/test | POST | 僅 JWT | ② | admin | 3 | 待補 | 測試發送 |
| notify_config/ |
POST | is_admin | ② | admin | — | 已守 | notify-channels#2 部分 |
| ai-chatbot(POST/DELETE)、ai-dashboard/auto-generate | POST/DELETE | 僅 JWT(自我 scope user.id) | F/self | — | — | no-action | |
| ai-dashboard/health | GET | 無 JWT | F | — | — | no-action | 刻意公開 |
| log/api-logs/export | GET | 僅 JWT(認證已修) | ① | platform | 3 | 待補 | SEC-002;LOG 能力 platform-only(軸①) |
| log/api-logs(list) | POST | 僅 JWT | ① | — | 3? | 讀取 | 隨 export 一併檢視角色 |
| uploadfile /file/download/ |
GET | @signed_token_or_jwt(雙軌) | ⑤ | — | 4 | 已修待驗(40a7d9c5) | /file/access-token/ |
| uploadfile /file/upload(+uploads)、/upload/ |
POST/DELETE | 僅 JWT(delete 無 owner) | owner | — | 待定 | 待定 | delete 應限 owner |
| report /schedule-report/preview | GET | 無 JWT(query 拼路徑) | ⑤/② | — | 待定 | 待定調 D14 | 任意檔讀取風險(新發現) |
| report /schedule-report/file-list | POST | 僅 JWT | ② | — | 3? | 讀取 | 檔案清單 |
| issue /get_members | GET | 無 JWT | — | — | 待定 | 待定調 D14 | 純讀裸奔(新發現) |
| translate、label | POST/GET | 僅 JWT/查詢 | — | — | — | no-action | 非寫入/非敏感 |
「已守」~110 支不是同一套寫法,分三類處理:
| 類 | 內容 | 處置 | 時機 |
|---|---|---|---|
| A. 已用 canonical | participant 12 / SSP 34 / framework 18(用 assert_project_manager / SspPermissionChecker / require_platform_admin) |
只把 import 從舊 shim 換 common.authz 新路徑,零行為變更 |
Phase 2b 順手 |
| B. 私有 helper 複製 7 份 | audit-round 家族 ~30 支:_check_role/_check_manager/_check_auditor 散在 app/grc/service/ 7 個 service(audit_service.py:27、poam_app_service.py:91、audit_round_app_service.py:99、job_import_service.py:81、task_execution_service.py:47、assessment_plan_app_service.py:230、assessment_result_app_service.py:111)——未登記的「第 5 套」,但行為正確 |
✅ 已收編(Phase 2c,見 §7.5):common/authz/project.py 泛化 assert_project_role(allowed_roles=...),7 份 _check_* 改為薄殼委派(call site 全不動、行為保真);複製邏輯已刪 |
Phase 2c 已完成 |
C. inline is_admin 散用 22 處 |
cloud_integration / notify_config / flow_engine stage 等直接讀 ctx.is_admin——兩個 is_admin 地雷的散彈面 |
換 require_super_admin() / viewer_is_super_admin()(同為帳號層語意,零行為變更),消滅語意混用 |
Phase 3 順手 |
本檔為 living tracker:Phase 2b/3/4 落地時逐列更新狀態欄(待補→已守)。
fix/v1.8.0-bugs)主體域 route decorator 建置 + 掛點完成。decorator 本體:common/authz/decorators.py (require_platform_admin_route / require_super_admin_route,後者支援 error_code= 覆寫), 委派 platform.py / admin.py 純 context 判定,掛在 @jwt_required() 之內、@inject 之外。 單元測試 test/test_authz_decorators.py(7 案:raise/pass/fail-closed/custom-code 全綠)。 pytest test/ 對比 baseline 零新增失敗(既有 71 fail / 43 err 全為 stale/env,與本案無關)。
掛點統計:軸① platform 7 支、軸② super-admin 109 支(含 flow-template 6 支用 alias), 共 116 支,分佈 16 模組。
| 端點 | 處置 | 備註 |
|---|---|---|
GET /schedule-report/preview(D14) |
補 @jwt_required + @require_super_admin_route + _safe_report_path() path 消毒(擋 ../絕對路徑/分隔符 + realpath 容器檢查) |
FE 唯一呼叫方在 _archived(2026-03-31 停用、路由已移除),無 live consumer;同時把 file-list 的 path 併入 _safe_report_path 硬化。新增 error code REPORT_400001 |
POST /send-user-test-mail(D15) |
直接移除 route(class + __init__ 兩處註冊) |
debug 殘留 hardcoded email |
@require_platform_admin_route,7 支)| 模組 | 端點 | 定調 |
|---|---|---|
| log | POST /log/api-logs、GET /log/api-logs/export |
SEC-002 首發,LOG 能力 platform-only |
| auth | POST/PUT/DELETE /tenant(3 支,含 add_tenant_with_defaults) |
D4 建租戶=平台級 |
| oscal | POST /oscal/resource-libraries、POST /oscal/resource-library/<uid>/publish |
D2 clone 全域框架三件組 |
掛2026-07-07 Phase 4a 完成換裝(見 §8)。策略(user 定調)=復用既有 granular cap、精準對 HTTP verb(POST→@require_super_admin_route皆為過渡;Phase 4 能力點 seed 齊後逐支換<res>.create/PUT→<res>.update/DELETE→<res>.delete/敏感 GET→.read), resource 用模組既有 DBresource_type;只 3 組缺口新 seed(system_config/feedback.export/ notification.test)。下表「建議能力點」欄的.manage命名為原始草案,實作採 granular verb 映射。
| 模組 / 端點群 | 支數 | 建議能力點 |
|---|---|---|
| auth user CRUD + change-status + import(upload/validate/batch-upload/sample-download) | 8 | user.manage(read/create/update/delete) |
| auth role CRUD + status | 4 | role.manage |
| auth org-unit CRUD | 3 | org_unit.manage |
| system_config CRUD(by-uid + by-group/key,含 SMTP/LDAP/notify/issue group) | 5 | system_config.manage |
| system_menu CRUD | 3 | system_menu.manage |
| device CRUD | 3 | device.manage(D13 租戶資產) |
| information_system create/update/delete | 3 | information_system.manage(D13) |
| bulletin CRUD | 3 | bulletin.manage |
notification mail/test + notification 測試發送 |
2 | notification.test |
user_auth_provider ldap/connect-test |
1 | auth_provider.manage(ldap-config#1) |
| remote_agent 管理面(create/update/delete/health/reconcile/enroll-token gen+revoke/revoke/unrevoke/rebind) | 10 | remote_agent.manage(D11;register/heartbeat 維持 F) |
feedback export |
1 | feedback.export(D12;編輯/刪除=owner,另案) |
| module_frame 全模組寫入(CRUD/clone/import/defaults/元件/資產/party/reference/inventory/leveraged/ssp-resources/system-characteristic) | 44 | module_frame.manage(D1;Phase 4 換此點) |
| survey 設計/管理(survey/folder/question/page/discussion CRUD + reorder/move/clone/release/import) | 24 | survey.manage(D9) |
| flow_engine flow-templates(create/update/delete/duplicate/publish/unpublish) | 6 | flow_template.manage(D3;錯誤碼沿用 GRC_FLOW_TEMPLATE_NO_PERMISSION(403041),非預設碼) |
已收編(14 處,全為 ambient get_user_context().is_admin 請求路徑讀取 → common.authz canonical):
notify_config_test_service.test_channel:inline gate → require_super_admin(NOTIFY_CHANNEL_TEST_FORBIDDEN)(測試 patch 路徑同步改指 common.authz.admin.get_user_context)google_drive_sync_route(3 param 讀 + webhook/register inline gate require_super_admin())、google_drive_integration_route(disconnect/auth-url 2 param 讀)→ viewer_is_super_admin()project_route(4)、job_route(1)、job_import_route(1)、user_route(1,update user)→ viewer_is_super_admin()刻意不收(scope 邊界):
user_context.is_admin(ap_docx/ar/ssp_excel/ssp_docx 共 8 處):user_context 是顯式參數(route 傳入),非 ambient;已是正確帳號層 DTO 讀取。改成 viewer_is_super_admin()(讀 ambient ContextVar)會回退成 ambient 依賴、破壞可測試性與 worker-thread 安全 → 維持顯式參數形式。common/decorator/common_decorator.py:superuser_required:讀 legacy Flask session(非 UserContextDTO),且全站無 caller(dead)→ 不動。| 項目 | 原因 / 後續 |
|---|---|
| module_frame 敏感 GET(control-defaults/export、ssp-export、ssp-import-template 含實際資料)→ 軸② | D1 有涵蓋,但為 GET;若 FE 以 window.open/iframe 原生呼叫,盲加守門有 A 族回歸前科 → 需先查 FE 呼叫方式再掛,留待 GET 專項(同 D14 方法論) |
| module_frame/survey 靜態範本 GET(download/template、yaml-template、survey import-template,D14 無 JWT) | 靜態範例檔、低風險;補 JWT 需查 FE,留 GET 專項 |
user_auth_provider /auth-provider POST/DELETE |
矩陣標 ②/owner 待定(本人 or admin 未拍板)→ 跳過 |
| 軸③ / 2b / 2c 全部 | 非本波(Phase 3 只做主體域軸①②) |
fix/v1.8.0-bugs)軸③ 資源域守門,一律 app service 層、common.authz canonical import。
| 端點群 | 守門 | 角色 | commit |
|---|---|---|---|
| §4 A 類:participant 5 + SSP 11 + framework 4 service + oscal DI + ui_route | import 換 common.authz.*(零行為變更) |
— | cb558280 |
/ssp/<uid>/export(export_from_ssp_version) |
SspPermissionChecker.require_participant |
參與者 | 99945a10 |
grc update_project(PUT project) |
assert_project_manager |
manager | 80514dff |
grc create_job / update_job / delete_job |
assert_project_manager(走 resolve_project_id;delete_job 新增 project_uid 參數) |
manager | 80514dff |
grc review mark/unmark control+ao ×4 |
assert_project_manager(走 project_domain_service.get_by_uid) |
manager | 80514dff |
單測:test_fr048_ssp_export_guard(2)、test_fr048_grc_guards(4)。pytest test/ 對比 baseline 零新增失敗(76 fail / 41 err 全為既有 stale/env;passed 662→675=本波 6 + Phase 3 的 7)。DI 全 boot 驗證受限於本 sandbox 缺 GITLAB/GITHUB token(jedi_issue import 期實例化,Phase 3 已記同狀況);wiring 以嚴格 inspection 確認(participant_role_service / associations_container.project_domain_service 皆既有 provider)。
| 端點群 | 原因 | 建議 |
|---|---|---|
flow_engine 守門復活(task/complete、revert、job-evidence PUT/DELETE/POST、process/comments、task/link;assert_workflow_project_participant 開頭 return) |
需以 v2 反查鏈(workflow_execution→輪次/SSP→project,走 task_assignees.project_id)重建;v1 AP-task mapping 已 dark。量大、屬 FR-038 2B 排程,prompt 明示可拆下 session。 |
下一波壓軸;判定沿用任一參與者、fail-closed、admin 放行;不另立新軸 |
grc jobs/batch-complete |
每 job 完成實走 flow_engine complete_job,隨上項 guard 復活即受守;自身 project 反查(job_uids 可跨專案)待復活波一併處理 |
併 flow_engine 波 |
grc jobs/export、job-execution comments POST |
角色為「參與者」層級,但 common.authz 目前無 assert_project_participant(只有 manager / SSP);comments 角色(參與者 vs manager)未定調 |
先於 common/authz/project.py 增 assert_project_participant(design §2.1「任一參與者」屬軸③),再逐支掛 |
| **project_summary_report(create/update/delete/export/history-revert)、subtask_status_history(POST/PUT/DELETE) | spec 明載「無角色細分**」=參與者-成員資格層級(非 manager);缺口是「未驗專案歸屬」而非角色。同樣需 assert_project_participant canonical;且 update/delete/export 依 uid,需先反查 report/subtask→project |
同上,補 assert_project_participant 後掛;update/delete 需 domain service 提供 report→project 反查 |
**evidence_classification run/state、archive、ground-truth import |
矩陣標 軸③/② 待定**;classify 已守 manager,但 run-scoped 端點目前僅 tenant 解析,ground-truth import 可能屬 admin(軸②)。非 D 定調行 | 待 user 定調 run-scoped 角色(專案參與者 vs tenant admin) |
關鍵待辦:
common/authz/project.py增assert_project_participant(任一參與者放行)=解鎖 jobs/export、comments、project_summary_report、subtask 四組的前置。這是小 canonical 補強(design §2.1 已認可「任一參與者」為軸③一支),非新軸。
fix/v1.8.0-bugs)完成 2b 交棒單剩餘 + 跨 phase 縫隙。守門一律 common.authz canonical。
| 端點群 | 守門 | 角色 | commit |
|---|---|---|---|
common/authz/project.py 增 assert_project_participant(任一參與者,登決策表) |
— | — | 6d5deac8 |
evidence_classification put_state / archive_run(run_folder→專案)+ ground-truth import(租戶任一專案 manager) |
manager(模組 EC_NOT_MANAGER) | manager | a68c1c82 |
grc /jobs/export |
assert_project_participant |
參與者 | ed5eb47d |
flow_engine 守門復活:task/complete、revert、job-evidence PUT/DELETE(既有 wrapper 自動復活)+ job-evidence(s) POST、task/link PUT(加掛);v2 反查鏈 workflow_execution→USER job→task_assignees.project_id(common.authz.workflow 收斂為政策層,解析器在 WorkflowExecutionService) |
任一參與者 | 0f50bbd0 |
|
D14 補 JWT:/module-frame/download/template+/yaml-template、/issue/get_members(FE 實查安全) |
JWT | — | 88d1050f |
單測新增 16 案(participant 5 / evidence 5 / workflow 政策 6)全綠。回歸驗證:對 baseline f1ea97c3 比對 FAILED/ERROR 集合,76 failed / 41 errors 逐一相同(零新增失敗),passed 675→691=本波 16 新測;無既有通過測試回歸。
| 端點群 | 原因 | 建議 |
|---|---|---|
grc job-execution comments POST |
角色未定調;且需 workflow_execution→project 反查(JobCommentService 無 workflow_execution_service 依賴,或改由 job_execution_uid 走已復活的 flow 鏈) | 定調角色後,注入 workflow guard 或 assert_project_participant |
| **project_summary_report(create/update/delete/export/history-revert)、subtask_status_history(POST/PUT/DELETE) | assert_project_participant 已就緒;但 ProjectSummaryReportContainer / subtask container 未接 project_participant_container**(需加 DI wiring);update/delete/export 依 uid,PSR 走 entity.project_id 反查、subtask 需確認 subtask→project 反查 |
補 container wiring(participant_role_service)→ create 走 find_project_id_by_uid、update/delete/export 走 entity.project_id → assert_project_participant |
| MF 敏感 GET(軸②):control-defaults export/download、ssp-import-template、control-defaults GET、ssp-export | FE 實查:api.js 標「GET (blob)」者為 axios(bearer-safe,可加 @require_super_admin_route);ssp-export / 其餘需逐支確認非 window.location |
逐支 FE 確認 blob → 掛 @require_super_admin_route(併 module_frame.manage 過渡名單) |
軸⑤ signed-token(Phase 4)新增:/survey/import-template(BatchImportDialog window.location LIVE)、/task-survey/import-template/<task_uid>(_archived window.location) |
原生下載帶不了 bearer,盲加 JWT 踩 ffa01a78 前科 | Phase 4 signed-token(與 otp-qrcode / framework download 同機制) |
| process/comments PUT | route 直呼 jedi_flow_engine ElementVariableService,無主專案 service 可掛守門 |
Phase 5 下沉套件;低風險留言 |
grc jobs/batch-complete 自身入口 |
每 job 走 complete_job 已受守(transitive);自身入口 project 反查(job_uids 可跨專案)未加 | 逐 job 反查驗(低優先,transitive 已覆蓋主風險) |
fix/v1.8.0-bugs)收掉 2b-2 §7.3 尾巴。守門一律 common.authz canonical。
| 端點群 | 守門 | 角色 | commit |
|---|---|---|---|
| project_summary_report create/update/delete/export/history-revert(含 history service) | assert_project_participant(container 補接 project_participant_container;entity.project_id 反查) |
參與者 | 02e7674d |
| grc job 留言 POST(add_comment) | 復用 WorkflowExecutionService.assert_project_participant(job_execution→workflow_execution_id) |
參與者 | e2c448ff |
grc jobs/batch-complete 入口 |
assert_project_manager(route project_uid thread 進;per-job transitive 守門保留) |
manager | e2c448ff |
| MF 敏感 GET:control-defaults GET list/detail・export・template/download、ssp-import-template ×2・excel-template・ssp-export | @require_super_admin_route(FE 逐支實查 axios/blob,bearer-safe) |
super-admin | a83a8619 |
單測新增 7 案(PSR 4 / job 留言 3)全綠。batch-complete 守門=既有 assert_project_manager 呼叫(test_fr048_grc_guards 已覆蓋;本 service import WorkflowExecutionService 有既有循環 import,不另單獨 import 測)。回歸:對 baseline c05ed713 比對,76 failed / 41 errors 相同(零新增失敗),passed 691→698=本波 7 新測。
| 端點 | FE 呼叫 | 結論 |
|---|---|---|
| control-defaults export / template/download | axios.get(url, {responseType:'blob'})(ModuleFrameTemplateService.js:208/219) |
✅ blob → 掛 @require_super_admin_route |
| ssp-import-template ×2 / excel-template / ssp-export | api.js 全標「GET (blob)」 | ✅ blob → 掛 |
| control-defaults GET list/detail | this.get(...)(BaseService axios) |
✅ JSON 讀 → 掛 |
| 端點群 | 原因 / 後續 |
|---|---|
| **subtask_status_history(POST/PUT/DELETE) | 唯一 FE consumer 在 _archived/projects/oscal/ProjectTaskManage.vue(v1-dead,無 live 流量);task_id 為 opaque 字串、無 documented project 反查、無既有反查方法 → 猜反查鏈有正確性風險(verify-first / 禁重複造輪)。待確認 task_id 指涉**(或隨 v1 端點退役)再處理 |
| process/comments PUT | route 直呼 jedi_flow_engine ElementVariableService,無主專案 service 可掛守門 → Phase 5 下沉套件;低風險留言 |
| survey / task-survey import-template | 原生 window.location → Phase 4 signed-token(見 §7.3;盲加 JWT 踩 ffa01a78 前科) |
Phase 2b(含 2b/2b-2/2b-3)軸③ 資源域收口大致完成;剩餘為 v1-dead(subtask)、套件下沉(process/comments)、Phase 4 signed-token(原生下載)三類非本軸工作。
fix/v1.8.0-bugs)§4 B 類收編完成:app/grc/service/ 7 份私有 _check_* 複製品 → 委派 common.authz canonical,行為保真、零行為變更、call site 全不動。
canonical:common/authz/project.py 新增 assert_project_role(participant_domain_service, project_id, user_id, allowed_roles=("manager",), error_code=None)——project 層 participant 精確查詢守門,不做 control→group→project fallback(那是 assert_project_manager 的軸,兩支並列在 __init__.py 決策表、語意不可互相冒充)。error_code 未給時按 allowed_roles 選預設(含 auditor→GRC_NOT_AUDITOR,否則 GRC_NOT_MANAGER);manager override 由 caller 用 allowed_roles=("auditor","manager") 明示。
7 份對映:
| 檔 | method | 委派參數 | 特殊處理 |
|---|---|---|---|
| audit_service | _check_role |
manager→("manager",)/其他→(required_role,"manager") |
uid→resolve+404 保留;override 於 2026-07-07 user 定調後對齊(見下) |
| poam_app_service | _check_manager |
預設 ("manager",) |
— |
| audit_round_app_service | _check_role |
manager→("manager",)/auditor→("auditor","manager") |
manager override 保住 |
| job_import_service | _check_manager_role |
預設 ("manager",) |
uid→resolve+404 保留 |
| task_execution_service | _check_manager |
預設 ("manager",) |
resolve+404 保留、維持回傳 project entity |
| assessment_plan_app_service | _check_auditor |
("auditor","manager") |
manager override |
| assessment_result_app_service | _check_auditor |
("auditor","manager") |
manager override |
✅ 已定調並對齊(2026-07-07 user 拍板「讓 manager 可代行」):audit_service._check_role 原為 exact match 無 manager override(與 audit_round / assessment_* 不同調),收編時先保真並標註。user 定調後已對齊:auditor 端點改 allowed_roles=(required_role, "manager"),manager 可代行 AR 相關寫入(generate-draft / judge / observation / risk),全站 override 語意一致。此為 2c 收尾唯一的行為變更(放寬,非收緊)。
驗證:baseline worktree(2c 前 commit 8068d954)集合 diff——pytest test/ --continue-on-collection-errors FAILED/ERROR 節點集比對,新增失敗 0;2 支 task_execution 測試在全套跑 FAILED→PASSED 為既有跨測試順序 flakiness(隔離跑兩樹皆 5 綠),與本案無關。7 份方法體皆只剩委派一行+註解,殘留複製邏輯 0;now-unused import(ForbiddenError / ProjectParticipantQueryEntity)順手清除。
fix/v1.8.0-bugs)軸④ capability guard 建置 + §6.3 過渡名單換裝完成。
① guard 本體:common/authz/capability.py — @require_capability(*names, mode="any"/"all", error_code=?)。 委派 DI CapabilityGuard(auth_container.capability_guard,自帶 @transaction),走 web-menu 既有查詢鏈 user→roles→role.capabilities→cap.name(不另立查詢路徑);flask.g per-request memoize;帳號層 super_admin break-glass 直接放行(viewer_is_super_admin())。錯誤碼 GRC_CAPABILITY_REQUIRED(GRC_403022)。單測 test_authz_capability.py 14 案全綠。commit eb590ae4。
② 能力點策略(user 定調):復用既有 121 顆 granular cap(<resource>.<action>)、精準對 HTTP verb(POST→create/PUT→update/DELETE→delete/敏感 GET→read),不新造 .manage 平行方案 (避免與 FE 選單所讀的 granular RBAC 分裂=第五套)。Administrator[root,id=2] 已全持有既有 write cap, 換裝零 role_capabilities 變更。只 3 組缺口新 seed(migration 2026-07-07-fr048-phase4a-capability-seed.sql, 已套 DEV、驗冪等):system_config.{create,update,delete}(is_platform,泛用設定寫入觸及平台級)、 feedback.export(feedback.create 語意為使用者提交、不可復用)、notification.test(無 notification resource)。commit 7828653e。
③ 換裝(35 檔,commit e8b28a4a):@require_super_admin_route → @require_capability("<res>.<verb-action>")。 模組→resource:auth user/role/department、bulletin、device、information-system、system-menu、 remote-agent-manage、module-frame(15 檔)、survey(4 檔,survey_route 見下)、ldap-config、report、 system_config、feedback.export、notification.test。flow_template 用 3 支 verb-specific alias 保留自訂碼 GRC_FLOW_TEMPLATE_NO_PERMISSION。軸① platform decorator 本波不換(tenant/log/framework 維持 @require_platform_admin_route)。
④ 語意變化(重要,須手測確認):判定從「帳號層 super_admin」→「持有能力點 或 super_admin break-glass」。持 Administrator 角色的一般帳號現在也能過(原本 403)——放寬到 RBAC 正軌,符合設計 §2.0;但權限面「誰能過」變了,須讓 user 知悉。
⑤ 驗證:clean worktree 集合 diff(baseline c29acbd0 無 4a/test e8b28a4a 4a-only,隔離 4b 未 commit 干擾)——FAILED/ERROR 節點集 128↔︎128 新增失敗 0,passed +14(capability 單測);17 支換裝 route 模組 import smoke 全過。DEV live 403/pass 三態測列手測 checklist(BE user 自起)。
⚠️ 與 Phase 4b(signed-token)同 working tree 交錯:api/survey/routes/survey_route.py 的 survey CRUD 換裝已在工作區完成,但該檔 import 行同時被 4b 改(issue_signed_token/signed_token_required)、且依賴 4b 未 commit 的 common/authz/signed_token.py → 暫不納入 4a commit,待與 4b 協調後一併 commit。其餘 35 檔 不受影響。
未處理/延後:tenant.* 標 is_platform(Phase 5 併軸鋪路,有存量副作用,刻意延後);STG/POC migration (待 user 指示);FE 角色矩陣新 cap 顯示 / i18n(查 FE 慣例後回報)。
fix/v1.8.0-bugs;本節由 review session 補記——4b 執行 session 未寫矩陣)軸⑤ signed-token 機制+五件收口。BE 40a7d9c5、FE 79c0133(compliance-manager-fe,17 檔含共用 signedDownloadUtil.js)。
機制:common/authz/signed_token.py — itsdangerous.URLSafeTimedSerializer,key 自 JWT_SECRET_KEY 派生、scope 當 salt(跨 scope 重放無效),payload {scope, resource_uid, user_id},TTL 120s。issue_signed_token / verify_signed_token / @signed_token_required(純簽章軌,bind_uid_arg 可綁 path 參數)/ @signed_token_or_jwt(雙軌,bearer 軌刻意不吞例外→保留 TOKEN_401002 觸發 FE refresh)。錯誤碼 GRC_401011。單測 test_authz_signed_token.py 12 案。
五件:①otp-qrcode(發放端 /otp-qrcode-token+token 綁發放者本人 uid=自我限定,修掉「知 uid 拿他人 TOTP」本體洞)②framework OSCAL download(.../oscal/<uid>/token)③file download/pdf-preview(/file/access-token/<uid>+雙軌,既有 axios 呼叫方零改動)④survey import-template(LIVE window.location 件)⑤task-survey import-template(實查 consumer 僅 _archived=dead → 直接補 @jwt_required,不上 token)。
Review 補核(2026-07-07):全套件 baseline 集合 diff(c29acbd0→HEAD,涵蓋 4a+4b)新增失敗 0、passed 692→724;signed-token/capability 單測 26 案獨立跑全綠。
⚠️ 4b 未完項(待補):
fix/v1.8.0-bugs)主體收尾後的裸奔稽核(5 群 subagent + 主導逐條驗 code)挖出的真漏網——大多數 agent 報的裸奔是誤判 (只看 route 沒追 service 層守門)。逐條先驗 live 再改,通過驗證的真缺口 4 條處置如下。
| # | 端點群 | 現況 | 軸 | 處置 | 狀態 |
|---|---|---|---|---|---|
| 1 | task_survey 填答 7 支:answer update/patch/checkpoint(question_answer_service)、history revert(question_answer_history_service)、update_task_survey / configure / import-answers(task_survey_service) |
service 零身分檢查,任何登入者可改任何人填答 | ③ | D10:填答=assignee 本人(manager 可代填);管理=manager。新 guard app/task_survey/common/task_survey_guard.py(assert_task_survey_writer / assert_task_survey_manager),assignee 走 task_assignees(task_id,user_id) 自然鍵、manager 走 assert_project_manager canonical(project_id 由 task_assignees.project_id 反查)。新 error code TASK_SURVEY_403001。DI 補接 project_participant_container → participant_role_service。 |
✅ 已修待驗 |
| 2 | **poam update_poam(app/grc/service/poam_service.py:96) |
service 零守門 | ③ | dead,不改**:route PoamDetailResource PUT 在 api/grc/__init__.py:167-170 已註解停用(FR-038 DEAD-V1 §1B,FE PoamView→RoundPoamView 走 /audit-round/*)。PoamService 全站僅此 dead route 引用,無 live 風險。真 live 的 v2 是 poam_app_service(有 _check_manager)。 |
dead(複驗即可) |
| 3 | report file-list(api/report/routes/scheduler_report_route.py:44 POST) |
只有 @jwt_required,與 preview 不一致 |
② | 對齊 preview 掛 @require_capability("report")(見 #4 修正) |
✅ 已修待驗 |
| 4 | report 能力點名稱錯(4a 換裝後遺症) | preview 用 @require_capability("report.read"),但 DB 無 report.read(DEV live 查證:只有既有 report id=49/is_platform=f/desc「report?v=1 read access」,掛 Administrator)→ 該守門 fail-closed 擋掉 super_admin 以外所有人=preview 現行已壞 |
④ | user 定調①:report.read→既有 report。preview + file-list 皆改 @require_capability("report"),零 migration。 |
✅ 已修待驗 |
驗證:新增單測 test/test_fr048_task_survey_guard.py(6 案:writer assignee/manager-override/deny、manager pass/deny/unresolvable)全綠;test_audit_event_instrumentation.py 補守門 fixture(1 案回綠 + _build_* 注入 guard deps)。回歸:baseline worktree(起點 108df8f8)集合 diff——FAILED/ERROR 128↔︎126(新增失敗 0;少 2 為既有 test_task_execution_service 跨測序 flaky,隔離跑綠),passed 724→730=本波 6 新測。
裸奔稽核 agent 報的其餘「缺口」經主導逐條追 service 層後確認已守 / 非缺口,記此免重查:
| 稽核指稱 | 澄清 |
|---|---|
| flow_engine 12 支「裸奔」 | §7.2/§7.3 已復活 assert_workflow_project_participant 守門鏈(task/complete、revert、job-evidence PUT/DELETE/POST、task/link);agent 只看 route 未追 wrapper 復活狀態 → 誤判 |
| oscal 13 支「裸奔」 | §3.1 已列:SSP-scoped 30+ 支全數 SspPermissionChecker.require_manager、framework 家族 require_platform_admin;agent 未追 route decorator/service resolve → 誤判 |
| cloud 4 支「裸奔」 | §3.10/§F 族已列:disconnect/auth-url/webhook is_admin→require_super_admin、verify-and-repair manager;OAuth callback/webhook push 刻意 F(state/channel token)→ 誤判 |
| participant「那批裸奔」 | §3.9 已列:task-assignee/project/control/group participant 12 支 live 全 assert_project_manager;batch/process-participant 為 2A stub / 未註冊 dead → 誤判 |
本 §10 收口後,Phase 2b 軸③ 資源域含裸奔稽核補洞大致封版;剩 v1-dead(subtask)、套件下沉(process/comments)、Phase 4 signed-token(原生下載)三類非本軸工作(見 §7.4 末)。