本卡屬 FR-096(母卡 CM-1803),第 3 棒(收尾):選單字典表的完整路徑(jedi-system-core 套件,33 檔 842 行,其中 13 支是空的
__init__.py)。只掃不修。
掃「選單字典」這張表從端點到資料庫的完整路徑。這張表存的是前端下拉選單的選項文字(例如使用者狀態有哪幾種、問卷題型有哪幾種),是全站 UI 共用的一本字典。
要回答兩個問題:① 誰改得動這本字典? ② 一個客戶改一筆,是不是就改到全站所有客戶了?
風險最低的一棒,但不跑完就不能說這支套件掃過了。 表的內容已查證無個資(見下段),所以看的不是「資料會不會外洩」,而是**「完整性」——這張表沒有客戶隔離、(分組,鍵) 全域唯一,任一客戶改一筆等於改到全站的 UI。改掉一個選項的文字不致命,但改掉一個鍵**可能讓某個頁面對不到值而壞掉,或把一個停用的選項打開。
這是合理的停損點之後——第 1 棒(設定表,密碼在那)+第 2 棒(主專案接線,權限在那)跑完已涵蓋全部的密碼與權限風險面。這一棒是為了覆蓋率完整。
system_menus 這張表沒有隔離,是正確的,不是漏洞。 首腦 2026-09-15 對 DEV 實查:rls=false、0 條規則、共 74 筆,內容全是下拉選單的碼表(USER_STATUS/ENABLE_STATUS/ANS_TYPE 這類),沒有客戶資料、沒有個資。選單字典是全站 UI 的資料來源,隔離起來會壞掉所有頁面。套件 plugin/__init__.py 的說明也寫明兩張表的租戶語意刻意相反:「設定是每個客戶自己的參數,選單是全站共用的字典」。報「這張表沒有隔離」=重報已知事實,不算發現。
但有一件相關的事要記一句:查選單的請求格式(api/serializers/system_menu.py:25-31 的 SystemMenuQueryRequest)六個欄位全部非必填,送一個空的查詢條件就會回全表。以這張表的內容而言影響有限,但它屬於首腦正在盤的「共用底層送空條件就回全表」那條線(跨 arc 總表 §6 A 組第 15 項,已經是第三次撞到)。報告裡寫一句、標明歸那條線即可,不要當獨立發現升級成中高風險。
首腦 2026-09-15 讀過這一半的程式碼,以下是值得從這裡往外追的:
@platform_admin_required(必須是平台管理員),消費類的一條(前台依分組取下拉選項 SystemMenuListRoute)刻意不疊。看 api/routes/system_menu_route.py 每個方法的 decorator 堆疊,確認四條管理端點一條都沒漏,以及 decorator 的順序(@auth_required → @capability_required → @platform_admin_required)會不會讓其中一道被跳過。SystemMenuListRoute.get() 只要登入就能打,程式在回傳前過濾 enable == 1。但表裡還有一個 public 欄位——消費端點沒有過濾 public,而管理端點的分頁查詢反而在 app/service/system_menu_service.py 的 get_system_manus_and_pager() 裡硬塞 _filter.public = 1。要追:public=0 的列是什麼、消費端點會不會把本來不該給前台看的列回出去。app/service/system_menu_service.py:22 有一行 logger.info(f"Get system menus: {menus}"),與設定那半(第 1 棒重點①)是同款。這張表無機密,所以影響低,但要確認:這是同一個壞習慣的第二處,還是這一半另有別的整包輸出。infra/repository/system_menu_repo_impl.py 與 domain/service/system_menu_domain_service.py 的查詢怎麼拼條件、有沒有把使用者送來的欄位名直接當欄位用(這張表無隔離擋著,所以底層有多寬就是多寬)。分頁與排序的參數(pager/sort)是從請求直接帶下去的,要看排序欄位有沒有白名單。infra/repository/system_menu_repo_impl.py 都接住資料庫的唯一約束錯誤轉成友善的 409。要追:錯誤訊息會不會洩漏既有的鍵值,以及修改時有沒有辦法把 A 分組的列改成 B 分組而覆蓋掉別人的鍵。migrations/004-system-menus-seed.sql 塞預設選單、005-system-core-ui-routes.sql 塞 UI 路由。要追:有沒有塞進任何不該公開的東西(內部管理頁的路由、測試用的選項),以及冪等性(重複執行會不會塞兩份、或把客戶改過的值蓋回預設)。__init__.py)jedi_system_core/api/routes/system_menu_route.py
jedi_system_core/api/serializers/__init__.py
jedi_system_core/api/serializers/_shared.py
jedi_system_core/api/serializers/system_menu.py
jedi_system_core/app/__init__.py
jedi_system_core/app/dto/__init__.py
jedi_system_core/app/dto/base_dto.py
jedi_system_core/app/dto/system_menu.py
jedi_system_core/app/service/system_menu_service.py
jedi_system_core/common/__init__.py
jedi_system_core/common/enum/__init__.py
jedi_system_core/common/enum/code.py
jedi_system_core/common/enum/error_code.py
jedi_system_core/common/enum/event_code.py
jedi_system_core/common/utils/__init__.py
jedi_system_core/common/utils/common_util.py
jedi_system_core/domain/__init__.py
jedi_system_core/domain/entities/__init__.py
jedi_system_core/domain/entities/system_menu_entity.py
jedi_system_core/domain/entities/system_menu_query_entity.py
jedi_system_core/domain/repository/__init__.py
jedi_system_core/domain/repository/system_menu_repo.py
jedi_system_core/domain/service/__init__.py
jedi_system_core/domain/service/system_menu_domain_service.py
jedi_system_core/infra/__init__.py
jedi_system_core/infra/mapper/__init__.py
jedi_system_core/infra/mapper/system_menu_mapper.py
jedi_system_core/infra/models/__init__.py
jedi_system_core/infra/models/system_menu.py
jedi_system_core/infra/repository/__init__.py
jedi_system_core/infra/repository/system_menu_repo_impl.py
jedi_system_core/migrations/004-system-menus-seed.sql
jedi_system_core/migrations/005-system-core-ui-routes.sql