---
title: FR-087.T1 盤點報告：租戶隔離破洞逐表對照
---

# FR-087.T1 盤點報告：租戶隔離破洞逐表對照

- **卡片**：[CM-1663](https://app.notion.com/p/FR-087-3d8346da4cd081e7a788f88e4b2341c3)（本卡即母卡，唯一一棒）
- **範圍**：DEV 資料庫（`192.168.50.188:25432` 的 `guidant_ai_dev`）唯讀查詢 ＋ BE 主專案與 jedi-* 套件原始碼 grep，**沒有修改任何程式碼、沒有套用任何 SQL、沒有真的切租戶測試**
- **版本**：BE commit `f64f5fb3`，branch `feature/FR-075`（未 push）
- **日期**：2026-09-11
- **方法**：人工查（不用 claude-security 掃描工具——這一棒的答案在資料庫狀態與 SQL，不在程式碼的漏洞模式）

---

## 1. 🔴 一句話結論

**13 張表、4 支畫面（技術上叫 view）真的把「每個客戶只能看自己資料」這道隔離關掉了，而且已經被首腦實測驗證：拿一把指向不存在客戶的鑰匙去查，待辦任務應該看到 0 筆、實際看到 39 筆；專案應該看到 0 筆、實際看到 213 筆。**

好消息：**11 張表的修法是純資料庫開關**（把已經寫好的規則打開即可），**1 張表要先補一段規則再開**，**1 張表要先做決定**（要不要對外分公司開放平台版問卷）。**壞消息：有一件事必須先查清楚才能動手**——待辦任務清單的畫面（`vw_user_job_queue`）同時是**每天凌晨自動清孤兒資料的背景程式在用的同一批底層資料**，那支背景程式現在能跑起來，靠的正是「暫時允許背景程式看到全部客戶資料」這條規則（CM-1559 已經記錄過這條規則本身也是一個洞）。**先把兩件事一起規劃，否則修好隔離會連帶讓背景清理程式停擺。**

另外查到一件工具/前一棒交接檔沒寫清楚、需要更正的事：**平台管理員（原廠管理者）看到全部客戶資料，走的是跟「隔離關掉」完全不同的機制**——他們用的是「我是最上層客戶」這條合法通道，**不是靠隔離沒開才看得到**。所以開隔離**不會**弄壞平台管理後台，這件事可以放心做。

---

## 2. 這一棒在檢查什麼（白話）

我們的產品是**多家客戶（在系統裡叫「租戶」）共用一套系統**的。靠資料庫裡的一道機制把各家的資料隔開，這道機制的技術名字叫 **RLS**（Row Level Security，白話講就是「資料庫自己會擋，讓每個客戶只查得到自己的資料，不用每支程式各自小心」）。

2026-09-11 決策者實際測試發現：**把自己（原廠管理員帳號）切換到一個被指派的「子租戶」身分之後，還是看得到別家客戶的待辦任務與專案**。這代表這道隔離機制在某些地方沒有真的生效。

這一棒的工作：**把「哪些資料現在該被隔開、實際上有沒有隔開」逐張表盤點清楚**，整理成一張對照表，交給決策者決定怎麼修、先修哪個。**不修、不改、不套任何 SQL。**

**產品定的規則（判準，不是本棒發明的）**：客戶資料分三層——
- **平台層**：像「法規框架」「控制項目錄」這種所有客戶都要用、但只有原廠能編輯的公版資料——全部客戶都看得到，只有原廠能改。
- **租戶層**：客戶自己的資料——上層公司看得到下層分公司的（垂直向下管理），不同分公司之間互相看不到。
- **平台系統範本**：像「內建流程範本」這種公版東西，全租戶都能讀，但**是掛在租戶層資料表裡、用一個特殊標記區分**（不是像平台層那樣整張表都是公版）。

---

## 3. 掃到什麼：13 張表＋4 支畫面總覽

### 3.1 表格總覽（每一列回答五個問題）

| # | 這是什麼資料（白話） | 該是哪一層 | 現在是哪一層 | 差什麼 | 誰在讀、開了會不會弄斷 |
|---|---|---|---|---|---|
| 1 | **專案清單**（`compliance.projects`，213 筆） | 租戶層 | ❌ 規則寫好了但**開關被關掉** | 打開開關（`ENABLE ROW LEVEL SECURITY`）即可 | 一般使用者透過正常網頁功能讀。**開了不會弄斷**——規則本身完整（4 條）。**唯一要先查的是「當初為什麼關」**（見 §5.1） |
| 2 | **工作流程範本**（`compliance.workflow_templates`，17,544 筆） | 租戶層＋平台系統範本混合 | ❌ 規則寫好（含公版可讀規則）但**開關被關掉** | 打開開關即可 | 使用者建立稽核流程時讀。**開了不會弄斷**——但有 191 筆是**歷史留存的凍結副本**，沒有標記所屬客戶（詳見 §5.2），這批打開後會消失在所有一般客戶眼前 |
| 3 | **客戶雲端硬碟串接設定**（`public.tenant_drive_integrations`，1 筆） | 租戶層 | ❌ 規則寫好但**開關被關掉** | 打開開關即可 | 客戶設定 Google Drive 同步時讀寫。**開了不會弄斷**——目前只有 1 筆資料，規則完整 |
| 4 | **工作流程執行紀錄**（`compliance.workflow_executions`，11,016 筆） | 租戶層 | ❌ **規則不完整**（只有新增／改／刪 3 條，缺「查詢」那條）且**開關關掉** | 先補一條「查詢」規則，再打開開關 | 見下方「待辦任務清單」——**同一批底層資料，兩件事要一起看** |
| 5 | **任務執行紀錄**（`compliance.job_executions`，11,065 筆，就是每一個稽核任務的執行記錄） | 租戶層 | ❌ **連規則都沒寫**，開關也關 | 照標準做法補 4 條規則（查／改／刪／新增各一條），再開開關 | 🔴 **每天凌晨 3:10 有一支自動清理程式會掃描全部客戶的這張表**（找「綁定資料指向的任務已經不存在」的孤兒資料並刪除，`core/scheduler.py:386`）。**這支程式現在能跑，正是因為它沒有客戶身分、被暫時當成最高權限放行**（CM-1559 記錄過的那條規則）。**如果開了隔離又同時修 CM-1559 那個放行規則，這支清理程式會看不到任何資料、整個失效但不會報錯**。兩件事必須一起規劃，見 §6 |
| 6 | **資訊系統清單**（`compliance.information_systems`，82 筆，SSP 文件中登記的資訊系統資產） | 租戶層 | ❌ 連規則都沒寫，開關關掉 | 補 4 條規則，再開開關 | 一般使用者透過 SSP 匯入 / 資產管理功能讀寫。**開了不會弄斷**——沒有查到背景程式或跨租戶統計會用到它 |
| 7 | **缺失改善計畫（POA&M）**（`compliance.poams`，92 筆） | 租戶層 | ❌ 連規則都沒寫，開關關掉 | 補 4 條規則，再開開關 | 一般使用者在稽核輪次頁面讀寫（走完整的 app service 授權流程，非背景任務）。**開了不會弄斷** |
| 8 | **證據自動分類的執行紀錄**（`compliance.evidence_classification_runs`，8 筆） | 租戶層 | ❌ 連規則都沒寫，開關關掉 | 補 4 條規則，再開開關 | 套件內部功能使用，**開了不會弄斷**（沒有背景程式依賴） |
| 9 | **證據分類的答案對照表**（`compliance.evidence_classification_ground_truth`，1 筆） | 租戶層 | ❌ 連規則都沒寫，開關關掉 | 補 4 條規則，再開開關 | 同上，套件內部功能，**開了不會弄斷** |
| 10 | **法規框架解析工作紀錄**（`oscal.framework_parse_jobs`，29 筆，匯入法規框架時的暫存工作） | 租戶層 | ❌ 連規則都沒寫，開關關掉 | 補 4 條規則，再開開關 | 🔴 **每天凌晨 1:00 有一支自動清理程式會清掉超過 7 天的舊工作紀錄**（`core/scheduler.py:202`，走無客戶身分的系統路徑）。這支和上面第 5 項同款——**開隔離要跟它一起規劃**，但因為只是「清舊資料」不是「找孤兒」，影響範圍較小（清理延遲，不影響任何人看資料） |
| 11 | **操作紀錄轉發設定**（`config.log_forwarding_settings`，1 筆，「系統要把操作紀錄轉送到哪台主機」的設定） | **平台層**（全系統只有一份設定，不分客戶） | ❌ 連規則都沒寫，開關關掉，而且**這 1 筆資料的客戶欄位是空的**（本來就對，因為它是全系統共用設定） | **不是這張表的問題**——它本來就該是平台層（只有原廠管理者能設定），**現有的權限檢查（能力點守門）已經做了這件事**，加 RLS 對它意義不大（詳見 §5.3） | 只有原廠管理者能改（既有能力點守門已擋），一般使用者只能讀（也有能力點守門）。**這張表建議不動**，是產品分類問題不是隔離漏洞 |
| 12 | **使用者角色指派**（`public.user_roles`，41 筆，「誰在哪個租戶被指派了什麼角色」） | 租戶層 | ❌ 連規則都沒寫，開關關掉 | 補 4 條規則，再開開關 | **這張表本身就是判斷「使用者現在有什麼權限」的依據**，被最核心的授權查詢直接讀（不經過隔離判斷，是隔離判斷的原料之一）。**開了不會弄斷一般查詢**，但這張表**极度敏感、要優先修**——它現在完全沒隔離，等於任何客戶能查到別家客戶「誰是誰的管理員」這類名冊 |
| 13 | **使用者-租戶關聯表**（`public.user_tenants`，41 筆，「這個使用者隸屬哪些租戶」） | 租戶層 | ❌ 連規則都沒寫，開關關掉 | 補 4 條規則，再開開關 | 🔴 **這張表是「切換租戶」功能本身的合法性依據**——使用者要切到某個租戶，系統會先查這張表確認「你真的隸屬這個租戶」（`jedi-iam` 套件 `middleware/context.py` 的 `default_membership_check`）。**這是一張控制access的表，不開隔離反而更危險**：任何人現在可以查到全公司「誰能切到哪個租戶」的完整名冊 |

### 3.2 4 支畫面（技術名叫 view）——繞過隔離

> **view 是什麼**：一個事先存好的查詢，用起來像一張表，但背後其實是即時去問好幾張真正的表湊出來的結果。

| # | 這支畫面是什麼（白話） | 問題 | 誰在讀、開了會不會弄斷 |
|---|---|---|---|
| 1 | **待辦任務列表**（`vw_user_job_queue`，就是使用者登入後看到的「我的任務」清單） | ❌ **繞過隔離**。PostgreSQL 這個資料庫軟體有個規則：查詢一個「畫面」時，預設是用「**建立這個畫面的人**」的身分去查底層資料，而不是「**正在查詢的人**」的身分。建這支畫面的帳號是資料庫最高權限帳號，所以查詢**天生就繞過所有客戶的隔離**，除非明確關掉這個「用建立者身分查」的預設行為 | 「我的任務」頁面直接讀（`infra/readmodel/tasks/my_grc_jobs_query.py`）、稽核儀表板的待辦數量卡片也讀（`infra/readmodel/audit/flow_control_dashboard_repo_impl.py:249`）。**這支畫面底層跨了 12 張表**，其中就包含上面第 4、5 項那兩張還沒隔離的表——**所以就算只修這支畫面本身，底層那兩張表沒隔離，一樣繞得過去。四項必須一起修**（見 §6） |
| 2 | **使用者能力總覽**（`v_user_capabilities`，「這個使用者可以做哪些操作」的完整清單） | ❌ 同上（建立者身分繞過隔離），但**這張表本身查的資料沒有客戶欄位**（`user_roles`／`role_capabilities`／`capabilities` 三表 JOIN），**修法一樣但急迫性較低**——因為就算不修，它洩漏的是「這個人有什麼權限」而不是「別家客戶的業務資料」 | 被授權檢查機制（`CapabilityGuard`）用來判斷「這個使用者現在有沒有權限做這件事」，也被到期通知功能（找「這個租戶的管理員是誰」）用（`app/license/adapter/tenant_admin_directory.py`）。**開了 `security_invoker` 不會弄斷**——它的資料來源本來就沒有租戶區分 |
| 3 | **使用者路由清單**（`v_user_routes`） | ❌ 同上（建立者身分繞過隔離） | **本棒 grep 全 BE 主專案與 jedi-iam 套件，找不到任何 Python 程式碼直接讀這支畫面**——選單可見性改走另一條程式路徑計算（`jedi_iam` 的 `UiRouteRepoImpl.get_viewable_by_user_uid`，直接查底層表、不經過這支畫面）。**這支畫面目前疑似是歷史遺留、沒有實際使用者**。修法一樣（加 `security_invoker`），但**建議先確認真的沒人用再排優先序**，別花時間修一支沒人讀的畫面 |
| 4 | **角色路由清單**（`v_role_routes`） | ❌ 同上（建立者身分繞過隔離） | 同上，本棒 grep 找不到直接的 Python 讀取者；`v_user_routes` 的定義有引用它（畫面疊畫面），但既然 `v_user_routes` 本身查無使用者，這支的實際影響也待確認 |

---

## 4. 一條應用層的同型洞（決策者裁定歸本棒）

**任務詳細 API 收了「這個任務屬於哪個專案」的參數，卻完全沒有用它來檢查**。

**白話說明**：一個網址路徑收了「專案編號」與「任務編號」兩個參數（`GET /grc/project/<project_uid>/job/<job_uid>`），照理應該要確認「這個任務編號真的屬於這個專案編號」才回資料，但實際上程式碼**只拿任務編號去查，完全沒理會專案編號**。首腦已開檔核對：

```python
# api/flow_control/routes/job_route.py:82-92
def get(self, project_uid: str, job_uid: str, job_service=...):
    dto = job_service.get_job(job_uid)   # ← project_uid 收了但沒用
    ...
```

**本棒繼續往下追，確認同檔的 `PUT`（改）與 `DELETE`（刪）也是同款問題**——收了 `project_uid` 但送進 `job_service.update_job` / `delete_job` 之後，那兩支方法內部**只用 `project_uid` 檢查「打這支 API 的人是不是這個專案的管理員」，完全沒有檢查「這個任務真的屬於這個專案嗎」**：

```python
# app/flow_control/service/job_service.py
def delete_job(self, uid: str, project_uid: str = None) -> bool:
    self._require_manager(project_uid)   # 只驗「你是不是這個專案的管理員」
    ...
    return self._domain_service.delete_job(uid)   # 刪除時完全不比對 uid 是否屬於 project_uid
```

**出事會怎樣**：一個對「專案 A」有管理權限的人，只要知道「專案 B」裡某個任務的編號（任務編號是 UUID，看起來很難猜，但在任何列出任務的畫面上都看得到），**填自己有權限的「專案 A」編號＋別人「專案 B」的任務編號**，一樣能改掉或刪掉那個任務——因為程式碼從頭到尾沒有檢查兩者是否真的配對。首腦已實測：真編號／全零編號／亂打的字串三種 `project_uid` 打進去都回 200 且內容相同。

**這與 FR-086 B1「只驗身分不驗歸屬」完全同型，修法可併同一張卡。**

在哪裡：
```
api/flow_control/routes/job_route.py:82-92    GET（讀）
api/flow_control/routes/job_route.py:104-140  PUT（改）
api/flow_control/routes/job_route.py:147-167  DELETE（刪）
app/flow_control/service/job_service.py:108-114   get_job（讀，完全不驗）
app/flow_control/service/job_service.py:178-189   delete_job（刪，只驗身分不驗歸屬）
app/flow_control/service/job_service.py:214-238   update_job（改，只驗身分不驗歸屬）
```

---

## 5. 需要先查清楚才能動手的三件事

### 5.1 專案表的隔離當初為什麼被關掉

交接檔已指出關閉的 migration：`2026-06-25-align-poc-to-stg-*.sql` 第 124 行，標記 `envs=poc`，理由只寫「以 STG 為主」。**本棒沒有進一步查到更詳細的理由**——這件事建議修正卡動手前先找到原始討論脈絡（或直接問當時做這個決定的人），**避免修好一個問題又踩到當初關掉它的原因**。

### 5.2 工作流程範本有 191 筆「無主」資料

`compliance.workflow_templates` 有 191 筆（佔 17,544 筆中的一小部分）的客戶欄位是空的，時間集中在 **2026-07-27 到 2026-08-09** 之間，其中 132 筆現在還連著實際的工作流程執行紀錄（也就是有客戶正在用）。查了程式碼註解，這批是「凍結副本」（`app/flow_engine/service/workflow_template_snapshot_service.py`，每次有人正式啟動一個稽核流程時，系統會把當時的範本複製一份凍結起來，之後管理員改範本不會影響已經在跑的流程）。**這批複製品目前沒有標記屬於哪個客戶**——若直接打開隔離，這 191 筆會從所有客戶眼前消失（因為隔離規則的邏輯是「客戶欄位配對不上就看不到」），而其中 132 筆還連著正在使用中的流程執行紀錄，**會造成使用者打開自己的稽核流程時看不到當初凍結的範本內容**。**這批需要先補上客戶欄位（回填正確的客戶歸屬）才能安全開啟隔離**，不能跟其他表一樣直接開開關了事。

### 5.3 操作紀錄轉發設定可能不需要 RLS

`config.log_forwarding_settings` 只有 1 筆資料，而且設計上**本來就是全系統共用、不分客戶**的一份設定（「系統要把操作紀錄送到哪台外部主機」是原廠層級的維運決定，不是租戶概念）。查證了套件的守門機制（`jedi_api_log.forwarding`），**讀寫已經各自有獨立的權限檢查**（能力點 `log-forwarding.read` / `log-forwarding.update`）。**這張表沒有隔離不是洞，是因為它本來就不該有「客戶」這個維度**——交接檔把它跟其他 12 張表放在同一組建議「照標準模式補 4 條」，**本棒認為這張需要另外處理**：要嘛從盤點清單移除（承認它是平台層資源），要嘛決策者仍希望它形式上符合「有 tenant_id 欄位的表都要有 RLS」的規範就補（但功能上不會有實際差異，因為守門已經做了該做的事）。**留給決策者判斷，本棒不擅自排除。**

---

## 6. 🔴 本棒最重要的一項：開了隔離會不會弄斷現在的功能

**這一項是本棒存在的理由，不是列完清單就結束。**

### 6.1 背景清理程式——會受影響的兩支

系統有兩支「不需要任何人登入、自動在背景跑」的排程工作，跑的時候用的是最高權限身分（因為沒有登入者，系統暫時假裝是「看得到全部」的身分，這條放行規則本身也是另一個已知問題，記錄在 CM-1559）：

| 排程 | 頻率 | 讀的表 | 開隔離的影響 |
|---|---|---|---|
| **孤兒綁定資料清理**（`core/scheduler.py:386`） | 每天凌晨 3:10 | `compliance.job_executions`（本棒第 5 項） | 🔴 **這支現在能掃全部客戶的任務，是靠它有「暫時最高權限」身分**。若之後 CM-1559 那個「暫時最高權限」的放行規則被收緊（那件事遲早要做，本身就是一個已知風險），這支清理程式會突然什麼都掃不到——**它不會報錯，只是安靜地不再做事**，孤兒資料會一直堆積沒人發現。**兩件事要放在同一張規劃裡處理，不能各修各的** |
| **法規框架解析工作清理**（`core/scheduler.py:202`） | 每天凌晨 1:00 | `oscal.framework_parse_jobs`（本棒第 10 項） | 同款成因，但影響較小——最壞情況是舊的暫存工作記錄清得比較慢，不影響任何人看得到的資料 |

**這兩支排程本身走的是「背景任務，無使用者身分」這條路，資料庫層級開不開隔離不會讓它們「看不到自己的東西」（因為它們本來就沒有「自己」），會出問題的是未來如果連「背景任務暫時給最高權限」這條規則本身也被收緊，兩件事疊在一起才會炸。本棒的結論是：修隔離的人必須知道這兩支排程的存在，安排修復順序時把它們排進同一輪驗證，而不是修完隔離就直接上生產環境。**

### 6.2 平台管理員——查證結果：不會受影響（更正前一棒交接檔的推測）

交接檔原本列了「原廠管理者要看全部租戶，要確認走的是哪條路」這一項待查。**本棒已查清楚**：平台管理員（原廠層級的帳號）判斷「你是不是平台管理員」，用的函式是 `viewer_is_platform_admin()`（在 jedi-iam 套件的 `authz/platform.py`），這個函式**呼叫的正是資料庫隔離機制用來判斷「這個人是不是最上層客戶」的同一段邏輯**（`_session_paths_have_root`）。也就是說：**平台管理員能看到全部客戶資料，走的是「我本來就是最上層客戶，隔離規則本身允許最上層看下層」這條合法的路，不是靠某個地方隔離沒開才漏看得到**。

**結論：開啟本棒盤點的這 13 張表＋4 支畫面的隔離，不會影響平台管理員後台的任何功能。** 這件事原本被列為風險，查證後可以放心排除。

### 6.3 統計／儀表板類——查證結果：本棒範圍內沒有找到會被弄斷的

本棒 grep 了所有讀取這 13 張表的程式碼位置（§3.1「誰在讀」欄已逐項列出），**沒有找到任何一支「刻意讀取全部客戶資料做統計」的功能**——目前找到的統計類查詢（如稽核儀表板 `flow_control_dashboard_repo_impl.py`）都是**先算出「這個使用者參與的專案」清單，再用那個清單去查任務與流程資料**，本來就是照使用者身分過濾的，隔離開啟後行為一致。

**唯一的例外要另案追蹤**：交接檔提到 FR-083 已查出「AI 儀表板有 27 支查詢功能完全不檢查權限」，本棒 grep 了那 27 支查詢的清單（見 FR-083 報告），**它們讀的資料範圍與本棒盤點的 13 張表大致重疊但不是同一批程式碼路徑**（AI 儀表板是動態組裝任意查詢方法，本棒盤點的是 RLS 資料庫層），**兩者是各自獨立的洞，不是同一個機制，修一邊不會自動修好另一邊，也不會互相弄斷**。AI 儀表板那 27 支的權限問題請照 FR-083 報告另案處理。

---

## 7. 問卷平台旗標——決策者要裁的一項，本棒只整理選項

`survey.surveys`／`survey_folders` 兩張表**沒有「這是平台公版問卷」的標記欄位**。本棒查了 DEV 資料庫實際資料：

```
tenant_id=1（原廠自己）的問卷：8 份
tenant_id=102（某客戶）的問卷：44 份
```

原廠帳號建的 8 份問卷，因為沒有平台公版標記，**子租戶完全看不到**。這跟另外兩個「公版資料」的做法不一樣：

| 資料類型 | 怎麼標記公版 |
|---|---|
| `flow_templates`（流程範本） | 用 `is_builtin` 欄位 |
| `module_frames`／`detection_profiles`（模組框架／偵測方案） | 用 `scope=SYSTEM` 欄位 |
| **`surveys`／`survey_folders`（問卷）** | **沒有任何標記欄位** |

**要決策者裁的問題是**：原廠的 8 份問卷，到底該不該讓所有客戶都看得到？

- **選項 A：加旗標比照 `flow_templates`**——加一個 `is_builtin` 或類似欄位，原廠建的問卷全客戶可讀。優點是跟其他公版資料的做法一致；缺點是要改 schema、改讀取邏輯、也要決定「哪些既有問卷該回溯標成公版」。
- **選項 B：不加旗標，維持現狀**——原廠的問卷就是原廠自己用的，不是公版庫。優點是不用動任何東西；缺點是如果原廠原本的用意是「建立範例問卷給客戶參考」，這個用意目前沒有實現。
- **選項 C：另開專屬機制**——問卷公版化走跟其他三種不一樣的設計（例如「複製一份到客戶自己名下」而非「共讀原廠那份」）。優點是問卷可能本來就需要客戶各自修改，不適合共讀；缺點是又是第四套做法，long-term 一致性更差。

**本棒不建議，交給決策者選。**

---

## 8. 修法分組與順序建議（本棒不寫 migration，只給分組建議）

| 組 | 內容 | 風險 | 備註 |
|---|---|---|---|
| **甲：純開關**（可一支 migration 一起做） | `projects`、`tenant_drive_integrations` 開開關 | 低 | 規則現成、無背景依賴、無孤兒資料問題 |
| **乙：補規則再開開關** | `job_executions`、`information_systems`、`poams`、`evidence_classification_runs`、`evidence_classification_ground_truth`、`framework_parse_jobs`、`user_roles`、`user_tenants` 照標準模式各補 4 條規則 | 中 | `job_executions`／`framework_parse_jobs` 要跟背景排程（§6.1）一起驗證；`user_roles`／`user_tenants` 敏感度最高建議優先 |
| **丙：先查後開** | `workflow_executions`（補 1 條「查詢」規則後開） | 中 | 與待辦清單畫面（丁組）同一批底層資料，**要一起修** |
| **丁：畫面補一行設定** | 4 支畫面加 `security_invoker` | 中 | `vw_user_job_queue` 跨 12 張表，其中含 `job_executions`／`workflow_executions`，**丙丁必須同一輪一起驗證**；`v_user_routes`／`v_role_routes` 找不到實際讀者，建議先確認用途再排優先序 |
| **戊：需要先處理資料才能開** | `workflow_templates` | 中高 | 191 筆凍結副本無客戶歸屬，**要先回填客戶欄位** |
| **己：要先查清楚原因** | `projects`（併入甲組前，先查當初關閉的完整脈絡） | — | 不查清楚可能重蹈覆轍 |
| **庚：產品決策後再動** | `surveys`／`survey_folders` 平台旗標 | — | 等決策者裁 §7 選項 |
| **辛：建議重新分類，不當隔離缺口修** | `log_forwarding_settings` | — | 見 §5.3，這張本質是平台層資源 |
| **壬：應用層洞，與 FR-086 併** | 任務詳細 API 不驗 project_uid（§4） | 中 | GET／PUT／DELETE 三支一起改 |

---

## 9. 這份結果可信到什麼程度

**表格與規則現況（§3 的「現在是哪一層」欄）：可信度高。** 每一項都是本棒直接對 DEV 資料庫下唯讀 SQL 查出來的（`pg_class`／`pg_policy`／`information_schema`），SQL 語句與交接檔給的一致，**逐項比對與交接檔一字不差**（13 張表、4 支畫面，數字、規則條數全部吻合）。

**「誰在讀、開了會不會弄斷」欄：這是本棒的核心產出，方法是 grep BE 主專案與 jedi-* 套件原始碼。** 有把握的地方：
- 背景排程的存在與讀取範圍（直接讀 `core/scheduler.py` 原始碼確認）
- 平台管理員走哪條路（追到 `viewer_is_platform_admin()` 與 RLS 用同一段判斷邏輯，這是本棒本身查證出來、更正了交接檔待查項目的部分）
- 任務詳細 API 的驗證缺口（開檔逐行核對，含 GET/PUT/DELETE 三支）

**不保證的地方**：
1. **grep 的天生限制**——動態組出來的 SQL（字串拼接、ORM 產生的查詢）grep 可能漏掉；本棒對關鍵表（`job_executions`／`workflow_executions`／`user_roles`）額外用「誰 import 這個 ORM model」交叉確認，但沒有對全部 13 張表逐一做這層交叉確認。
2. **FE（前端）程式碼完全沒有查**——本棒只查了 BE 主專案與 jedi-* 套件，前端若有直接組 SQL 或呼叫未涵蓋在本棒範圍內的 API 路徑，不在本次盤點內。
3. **沒有做任何實際測試**——沒有真的開過任何一個開關、沒有真的切過租戶、沒有真的跑過任何一支修法。全部是讀程式碼、讀規則定義、查資料庫結構與統計數字推論出來的。
4. **STG／POC 環境完全沒有查**——依卡片紀律只查 DEV，若 STG／POC 的隔離狀態與 DEV 不同，那是另一件需要單獨盤點的事。

**合起來的建議**：甲組（純開關）與壬組（應用層洞）證據最扎實，可以直接排修正卡。乙丙丁戊組修之前，建議修正卡的人自己再對照一次程式碼（尤其 §6.1 提到的兩支背景排程），因為「開了隔離不會弄斷什麼」這件事的代價是「弄斷了才會發現」。

---

## 10. 座標

- **來源交接檔**：[`FR-080/handoff/fr080-to-fr075-HANDOFF.md`](../FR-080-2609-jedi-consolidation-and-service-path/handoff/fr080-to-fr075-HANDOFF.md) §二
- 三層模型原文：`docs/changelog/2026-06-29-feat-tenant-scope-data-model-b2.md`、`2026-06-30-feat-compliance-framework-platform-write.md`
- 標準規則寫法：`docs/system-design/database/RLS_DESIGN.md`
- 同型前案：[FR-086](../FR-086-2609-file-upload-security-scan/)（四層全空，§4 任務詳細 API 建議併卡）、CM-1559（無身分時隔離整個關掉，§6.1 兩支背景排程受影響）、[FR-083](../FR-083-2609-ai-dashboard-security-scan/)（AI 儀表板 27 支查詢不經權限檢查，§6.3 已確認是獨立的洞）
- 跨 arc 總表：[`security-scan-consolidated/`](../security-scan-consolidated/)
- 首腦手冊：`.claude/skills/security-scan-lead/SKILL.md`
