本卡屬 FR-075(母卡 CM-1546)。S2 掃描(CM-1548)0 發現,但首腦復核後判定第一輪的 M-1/M-2/M-3 三條同根因問題仍然成立,工具沒報是因為程式碼註解把它寫成刻意設計。本卡由首腦開,不是掃描產出。
資料庫的租戶隔離(RLS)靠 session_scope() 在每次查詢前設定「這個人是誰、能看哪些租戶」。現在的判定有兩個 fail-open 的洞:
db.py:115-119 的 elif is_pg: 分支)。任何在沒有身分下跑的 @transaction 都以最高權限執行。主專案 before_request 會把 context 重設為 None,所以漏掛 JWT 檢查的路由、公開端點、沒還原 context 的背景執行緒,全部踩到。db.py:102 的 is_super = (not user_ctx.tenant_id) or ...)。None 或 0 都算。該檔自己的註解記載了實際被利用的案例:tenant-102 的使用者帶 X-Tenant-ID: 0 看到整張 roles 表。jedi-iam 的 signed_token.py:83-100 _set_minimal_context() 刻意把 tenant_id 設成 None 來取得 super admin,docstring 寫明是為了「避免掛 decorator 反而縮小資料可見範圍」。這是依賴洞來運作,洞補了它會壞,所以要一起改。
signed_token.py 確實在 S2 的 8 檔範圍內(runner 報告說在範圍外是誤判)。工具沒報的原因是程式碼註解把 fail-open 寫成刻意設計,研究員讀到「與原本既有行為一致」就接受了。這正是「工具會被註解說服」的例子——註解說是設計不代表設計是對的。
~/Projects/Jedicogy/module/jedi-python-package/jedi-common/jedi_common/session/database/db.py:100-104 is_super 判定(tenant_id falsy 即 super)
~/Projects/Jedicogy/module/jedi-python-package/jedi-common/jedi_common/session/database/db.py:115-119 無 context 即 super admin
~/Projects/Jedicogy/module/jedi-python-package/jedi-iam/jedi_iam/authz/signed_token.py:83-100 _set_minimal_context(依賴上述洞)
第一步:盤點誰在依賴 fail-open。這是本卡最重要的一步,做錯會讓一堆功能靜默壞掉。
@transaction?候選:登入流程本身(還沒登入哪來 context)、migration / seed script、背景排程、socketio handler、elevated_session.py。逐一列冊,每條標「是刻意要 super admin」還是「只是沒人設 context」。tenant_id=None 或 0 的 UserContextDTO?已知一處是 signed_token.py,可能還有。第二步:把「super admin」改成明確的正向訊號,不再從「缺值」推論。
UserContextDTO 加一個明確欄位(例如 is_system_context: bool,先 grep 確認沒有同義欄位),只有第一步列冊出來「刻意要 super admin」的呼叫者才設 True。db.py 的 is_super 改成:user_ctx.is_system_context or _session_paths_have_root(...)。not tenant_id 那個條件拿掉。db.py 無 context 的 elif is_pg: 分支改成 fail-closed:設 is_super_admin = 'f' 且不設 allowed_tenant_paths(RLS 會擋掉一切)。第一步列冊出來需要 super admin 的無 context 呼叫者,改成明確建一個 is_system_context=True 的 context 再進 @transaction。第三步:修 signed_token。