# B3 掃描報告：SSP 六類附屬資料的範本側鏡射版 ＋ 範本匯出

- 卡號：CM-2081（FR-113 module_frame 掃描母案 CM-2073 第 B3 棒）
- 掃描範圍：15 支檔／1,904 行
- 掃描版本：`60cf754f61b9180b41aef6feca06179e85a20d88`（工作區有未提交改動，屬平行 session 的檔，與本棒無關）
- 掃描時間：2026-09-22 10:45 UTC
- 工具：Claude Code 官方 `claude-security` plugin 0.11.0，effort `low`
- 驗證章：**verified**（工具程式碼自行計票蓋章，非模型宣稱）

---

## 1. 一句話結論

**掃完了，找到 12 個問題，全部三票一致確認，11 條中等、1 條低。最嚴重的一件事是：專案那一側六組附屬資料的權限守得滴水不漏，而範本這一側的同一批六組——程式是從那邊複製過來改的——讀取權限六組全漏、歸屬檢查六組全無。**

這不是「六份裡有一份漏了」，是六份一起漏：複製當下就沒帶到，後來補權限時只補了寫入那半邊的角色檢查，讀取整排沒補、「這份範本是不是你家的」一支都沒問。

---

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

系統裡有兩個地方長得幾乎一樣：

- **專案那一側**：每個專案有一份「系統安全計畫」（SSP），底下掛著六類附屬資料——參與人員、系統元件、設備清單、繼承授權、參考資料、系統特性。
- **範本那一側**：合規範本也可以先填好這六類資料當預設值，將來開專案時複製過去。

程式碼是從專案那側複製到範本那側改出來的。專案那側先前已經掃過（FR-113 的 O5 那一棒），結論是**六組全部守齊、一支不漏**：讀取要「你是這個專案的人」，寫入要「你是這個專案的負責人」。這一棒就是拿那份結論當標準，逐組去比對範本側有沒有跟著做到。

**比對結果：範本側六組，讀取全部漏、寫入全部漏。** 不是「六份裡有一份漏了」，是六份一起漏——複製當下就沒帶到，之後補權限檢查時也只補了寫入那半邊的角色檢查，讀取整排沒補、歸屬檢查一支都沒有。

> **現況（2026-10-01）**：本棒各條後來的處理結果如下（過程紀錄保留，不改）。
>
> - 第一類 F1～F5、F12（六支讀取入口無權限檢查）＝M11 第 4、28 條，✅ 已修（CM-2186，commit `aafaa070d`，1.21.0 出貨）。
> - 第二類 F6～F11（六組寫入不查範本歸屬）＝M11 第 8 條，✅ 已修（CM-2187，commit `3f15886bb`，1.21.0 出貨）。

## 3. 掃到什麼：總覽表

### 第一類：讀取入口沒有檢查權限（6 條）

| # | 這是什麼問題 | 出事會怎樣 | 要先有什麼才打得到 | 在哪裡 | 怎麼修 |
|---|---|---|---|---|---|
| F1 | 查範本的**人員名單**時，只檢查有沒有登入，沒檢查有沒有資源庫權限 | 聯絡人的姓名、email、電話、住址整批被撈走 | 只要有任何一組能登入的帳號 | `api/module_frame/routes/module_frame_party_route.py:35` | 在 `get` 上加一行 `@require_capability("module-frame.read")` |
| F2 | 查範本的**設備清單**時，同上 | 資產編號、IP 位址、主機名稱外洩，等於送對方一張內網地圖 | 同上 | `api/module_frame/routes/module_frame_inventory_route.py:27` | 同上 |
| F3 | 查範本的**系統元件**時，同上 | 元件用途、通訊協定、開放的連接埠範圍外洩 | 同上 | `api/module_frame/routes/module_frame_components_route.py:27` | 同上 |
| F4 | 查範本的**繼承授權**時，同上 | 對外揭露公司向哪些雲端供應商繼承了哪些授權、授權日期 | 同上 | `api/module_frame/routes/module_frame_leveraged_route.py:38` | 同上 |
| F5 | 查範本的**系統特性**時，同上 | 系統名稱、系統編號、**資安敏感等級**、授權邊界說明外洩 | 同上 | `api/module_frame/routes/module_frame_system_characteristic_route.py:37` | 同上 |
| F12 | 查範本的**設備／資訊系統對照**時，同上 | 除了清單本身，還附帶真實資產主檔的內部編號，那些編號可以再拿去打其他端點 | 同上 | `api/module_frame/routes/module_frame_ssp_resources_route.py:42` | 同上 |

### 第二類：寫入路徑沒有檢查「這份範本是不是你家的」（6 條）

| # | 這是什麼問題 | 出事會怎樣 | 要先有什麼才打得到 | 在哪裡 | 怎麼修 |
|---|---|---|---|---|---|
| F6 | 新增／修改／刪除範本**人員**時，只確認範本存在，沒確認範本屬於你 | 子公司管理員可以改總公司分享下來的範本、甚至原廠公版範本；之後所有依這份範本開的專案全部繼承被改過的內容 | 要有資源庫的編輯權限（通常是租戶管理員），不是任何人都行 | `app/module_frame/service/module_frame_party_service.py:116` | 在 `_template_ssp_id()` 解出範本後呼叫 `assert_scope_writable(mf.tenant_id, mf.scope)` |
| F7 | 範本**元件**的寫入，同上 | 同上，改的是元件的協定／連接埠等描述 | 同上 | `app/module_frame/service/module_frame_components_service.py:94` | 同上 |
| F8 | 範本**設備清單**的寫入，同上 | 同上，可以塞假資產或刪掉真資產 | 同上 | `app/module_frame/service/module_frame_inventory_service.py:96` | 同上 |
| F9 | 範本**繼承授權**的寫入，同上 | 同上；而且刪掉後，指向它的元件連結會變成斷掉的孤兒 | 同上 | `app/module_frame/service/module_frame_leveraged_service.py:105` | 同上 |
| F10 | 範本**系統特性**的寫入，同上 | 這是一份資料不是一堆，所以**一次請求就整份蓋掉**別人的設定 | 同上 | `app/module_frame/service/module_frame_system_characteristic_service.py:87` | 同上 |
| F11 | 範本**設備／資訊系統對照**的寫入，同上 | 同上，連帶動到指向資產主檔的關聯 | 同上 | `app/module_frame/service/module_frame_ssp_resources_service.py:63` | 同上 |

---

## 4. 每條發現的詳述

### F1 — 範本人員名單（姓名、email、電話、住址）任何登入帳號都讀得到（中等，可信度高）

**出事會怎樣。** 合規聯絡人的個人資料——全名、電子郵件、電話號碼、通訊地址，外加對應到系統內哪個使用者——會流到完全沒有資源庫權限的帳號手上。而且資料庫這一層沒有第二道防線：`oscal.parties` 這張表**完全沒有開「每個客戶只能看自己資料」的隔離機制**（我逐張查過，五張存放這六類資料的表一張都沒開）；而範本主表的規則又明文允許大家都看得到原廠公版（SYSTEM）與上層分享下來（SHARED）的範本（`scripts/init/02-schema.sql:24323`）。兩件事疊起來，這是跨公司的外洩，不只是跨部門。

**在哪裡。** `api/module_frame/routes/module_frame_party_route.py:35`，`ModuleFramePartyListResource.get`

**怎麼回事。** 網址裡的範本編號是呼叫者自己填的，而這支讀取入口上面只掛了「有沒有登入」（`@jwt_required()`），沒有掛「有沒有資源庫的閱讀權限」。往下一層的 `service.list_parties(uid)` 也沒有補任何檢查，直接把整份人員資料回出去。同一個檔案裡的新增（第 42 行）、修改（第 64 行）、刪除（第 83 行）三支**都有掛權限檢查**，只有讀取這支沒有——所以這不是刻意設計，是漏掉。

**打得到的具體走法。** 一個只被加進某個專案當參與者的使用者（他登入後根本看不到「資源庫」這個選單，因為前端會依權限把它藏起來）拿自己正常的登入憑證去打 `GET /api/1.0/module-frames/menu`。這支列表入口同樣只驗登入（`api/module_frame/routes/module_frame_route.py:27`），會把範本編號列給他，包含所有人都看得到的原廠公版。他挑一個編號，再打 `GET /api/1.0/module-frame/<編號>/parties`，回來的就是完整人員名單。

**要先有什麼才打得到。**
- 有任何一組可以登入這套系統的帳號，不需要任何資源庫權限
- 知道一個範本編號——上面那支選單入口就會給，不必用猜的
- 目標範本裡有填過人員資料

**怎麼修。** 在 `@jwt_required()` 和 `@inject` 中間插一行 `@require_capability("module-frame.read")`，寫法照同資料夾的 `module_frame_control_default_route.py:51`。這個檔案開頭已經 import 過 `require_capability` 了，加一行就好。

**檢查結果。** 三位檢查員三票全數確認。

---

### F2 — 範本設備清單（資產編號、IP、主機名稱）任何登入帳號都讀得到（中等，可信度高）

**出事會怎樣。** 範本裡登錄的資產清單會外洩，包含資產編號、IPv4 位址、主機名稱，以及每台設備對應到哪些系統元件。對攻擊者來說這是現成的踩點資料。存放這些資料的 `oscal.ssp_inventory_items` 同樣沒有客戶隔離機制，所以原廠公版與分享範本的內容會跨公司流出。

**在哪裡。** `api/module_frame/routes/module_frame_inventory_route.py:27`，`ModuleFrameInventoryListResource.get`

**怎麼回事。** 與 F1 同一個形狀：網址上的範本編號由呼叫者決定，入口只驗登入，下層服務不補檢查。同檔案的新增／修改／刪除三支都掛了權限檢查。

**打得到的具體走法。** 低權限帳號先從選單入口拿到範本編號，再打 `GET /api/1.0/module-frame/<編號>/inventory`，拿到帶 IP 與主機名稱的設備清單。

**要先有什麼才打得到。**
- 任何一組可登入的帳號
- 一個範本編號（同樣從沒守門的選單入口取得）
- 目標範本有填過設備資料

**怎麼修。** 讀取那支加上 `@require_capability("module-frame.read")`。

**檢查結果。** 三位檢查員三票全數確認。

---

### F3 — 範本系統元件清單任何登入帳號都讀得到（中等，可信度高）

**出事會怎樣。** 元件的名稱、用途說明、使用的通訊協定與**開放的連接埠範圍**、以及安全認證相關設定會外洩。這等於把受評系統的對外暴露面直接交出去。`oscal.ssp_components` 沒有客戶隔離機制。

**在哪裡。** `api/module_frame/routes/module_frame_components_route.py:27`，`ModuleFrameComponentsListResource.get`

**怎麼回事。** 同上：編號來自網址，入口只驗登入，服務層不補。同檔的三支寫入都有守。

**打得到的具體走法。** 沒有資源庫權限的帳號打 `GET /api/1.0/module-frame/<編號>/components`，讀到含協定與連接埠範圍的元件清單。

**要先有什麼才打得到。**
- 任何一組可登入的帳號
- 一個範本編號

**怎麼修。** 讀取那支加上 `@require_capability("module-frame.read")`。

**檢查結果。** 三位檢查員三票全數確認。

---

### F4 — 範本繼承授權清單任何登入帳號都讀得到（中等，可信度高）

**出事會怎樣。** 「我們有哪些安全控制是繼承自外部供應商的」這份清單會外洩，包含供應商名稱、授權日期、用途與狀態說明。對外揭露的是公司的雲端供應鏈結構。`oscal.ssp_leveraged_authorizations` 沒有客戶隔離機制。

**在哪裡。** `api/module_frame/routes/module_frame_leveraged_route.py:38`，`ModuleFrameLeveragedListResource.get`

**怎麼回事。** 同上。同檔的新增（42 行）／修改（62 行）／刪除（79 行）三支都掛了權限檢查，讀取沒有。

**打得到的具體走法。** 低權限帳號拿範本編號打 `GET /api/1.0/module-frame/<編號>/leveraged`，看到公司從哪些外部供應商繼承授權。

**要先有什麼才打得到。**
- 任何一組可登入的帳號
- 一個範本編號

**怎麼修。** 讀取那支加上 `@require_capability("module-frame.read")`。

**檢查結果。** 三位檢查員三票全數確認。

---

### F5 — 範本系統特性任何登入帳號都讀得到，但同一份資料的修改要權限（中等，可信度高）

**出事會怎樣。** 系統名稱、系統唯一識別碼、**資安敏感等級分類**、以及授權邊界的描述會外洩。這是整份合規文件的抬頭資料，等於告訴外人「這套系統多重要、範圍到哪裡」。`oscal.ssp_system_characteristics` 沒有客戶隔離機制。

**在哪裡。** `api/module_frame/routes/module_frame_system_characteristic_route.py:37`，`ModuleFrameSystemCharacteristicResource.get`

**怎麼回事。** 這條的證據最直接：同一個檔案裡，緊接在讀取下面的修改（第 45 行）掛著 `@require_capability("module-frame.update")`，讀取（第 28 行）只有登入檢查。同一份資料，改要權限、看不用——這就是漏掉，不是設計。

**打得到的具體走法。** 沒有資源庫權限的帳號打 `GET /api/1.0/module-frame/<編號>/system-characteristic`，讀到另一家公司合規文件的敏感等級與授權邊界。

**要先有什麼才打得到。**
- 任何一組可登入的帳號
- 一個範本編號

**怎麼修。** 讀取那支加上 `@require_capability("module-frame.read")`。

**檢查結果。** 三位檢查員三票全數確認。

---

### F6 — 範本人員、元件、設備等寫入路徑從不確認範本屬於哪家公司，原廠公版與分享範本都寫得進去（中等，可信度中等）

**出事會怎樣。** 子公司的管理員可以在**原廠公版範本**或**總公司分享下來的範本**裡面新增、修改、刪除人員、元件、設備、繼承授權與系統特性。範本主表的讀取規則刻意讓這些範本對下游可見，而底下五張存實際資料的表一張都沒有客戶隔離機制，所以寫入會真的落到別家公司的資料上。更嚴重的是**之後每一個依這份範本開的專案都會繼承被改過的基準**——這是稽核證據的完整性問題，不只是資料被動。同樣的缺口在本棒範圍內的另外五支服務都在（F7～F11）。

**在哪裡。** `app/module_frame/service/module_frame_party_service.py:116`，`ModuleFramePartyService.add_party`

**怎麼回事。** 網址帶進來的範本編號一路傳到 `_template_ssp_id()`（第 159 行）→ `_require_mf()`（第 172 行），而 `_require_mf` 只做一件事：**查得到就過**（`if mf is None: raise NotFound`）。它沒有問「這份範本是不是你家的」。相比之下同專案的 `ModuleFrameService.update_module_frame`（`app/module_frame/service/module_frame_service.py:112`）就有呼叫 `assert_scope_writable`。資料庫那層也補不了，因為 `oscal.parties` 沒有隔離機制。

**打得到的具體走法。** 子公司管理員打 `GET /module-frames/menu`，回來的清單包含原廠公版與總公司分享下來的範本。他挑一個，打 `POST /api/1.0/module-frame/<那個編號>/parties`。角色檢查會過——因為他在**自己公司**確實有這個權限；範本查得到——因為讀取規則允許他看到分享範本；寫入落到 `oscal.parties`——因為那張表沒有隔離機制。總公司與所有兄弟公司之後讀到的都是被改過的範本。

**要先有什麼才打得到。**
- 有資源庫的新增／修改／刪除權限（通常是租戶管理員），**不是任何登入帳號都行**——這是它比 F1～F5 難打的地方
- 系統裡存在原廠公版或上層分享下來的範本
- 知道該範本編號（選單入口會給）

**怎麼修。** 在 `_template_ssp_id()` 解出範本物件後，呼叫 `assert_scope_writable(mf.tenant_id, mf.scope)`（`common/authz/sharing.py:25`），寫法照 `module_frame_service.py:112`。同樣的一行要補在元件／設備／繼承授權／系統特性四支服務。長期解是給這五張 OSCAL 子表加上依 SSP 推導的客戶邊界，讓程式忘了檢查時資料庫還能擋——現在這五張表是「程式漏了就全開」。

**檢查結果。** 三位檢查員三票全數確認。

---

### F7 — 範本元件的寫入用網址上的編號找範本，不檢查歸屬（中等，可信度中等）

**出事會怎樣。** 分享範本或原廠公版範本裡的系統元件紀錄（類型、標題、說明、用途、狀態、協定與連接埠、安全認證設定）可以被下游公司建立、修改或刪除，污染其他公司與所有衍生專案讀到的基準。

**在哪裡。** `app/module_frame/service/module_frame_components_service.py:94`，`ModuleFrameComponentsService.add_item`

**怎麼回事。** 與 F6 同一個形狀：`_template_ssp_id` 只驗存在不驗歸屬，寫入落到沒有隔離機制的 `oscal.ssp_components`。

**打得到的具體走法。** 拿一個分享範本的編號，子公司管理員打 `DELETE /api/1.0/module-frame/<編號>/components/<元件編號>`：角色檢查過、範本查得到、元件在別家公司的範本裡被找到、刪除執行——資料庫沒有任何規則攔下來。

**要先有什麼才打得到。**
- 在自己公司有資源庫的新增／修改／刪除權限
- 存在一份原廠公版或分享範本，且對他可見
- 知道範本編號

**怎麼修。** 在 `_template_ssp_id` 內（或每支對外寫入方法開頭）加 `assert_scope_writable(mf.tenant_id, mf.scope)`。

**檢查結果。** 三位檢查員三票全數確認。

---

### F8 — 範本設備清單的寫入信任網址上的編號，不檢查歸屬（中等，可信度中等）

**出事會怎樣。** 分享範本的資產清單（資產編號、IP 位址、主機名稱，以及指向資產主檔的關聯）可以被跨公司新增、竄改或刪除，污染其他公司依賴的合規基準。

**在哪裡。** `app/module_frame/service/module_frame_inventory_service.py:96`，`ModuleFrameInventoryService.add_item`

**怎麼回事。** 同 F6 形狀，寫入落到沒有隔離機制的 `oscal.ssp_inventory_items`。

**打得到的具體走法。** 子公司管理員打 `POST /api/1.0/module-frame/<分享範本編號>/inventory` 塞一筆假資產。權限檢查過、範本讀得到、寫入無人攔阻。

**要先有什麼才打得到。**
- 有資源庫的新增／修改／刪除權限
- 存在可見的原廠公版或分享範本
- 知道範本編號

**怎麼修。** `_template_ssp_id` 解出範本後加 `assert_scope_writable(mf.tenant_id, mf.scope)`。

**檢查結果。** 三位檢查員三票全數確認。

---

### F9 — 範本繼承授權的寫入用網址上的編號找範本，不檢查歸屬（中等，可信度中等）

**出事會怎樣。** 分享範本裡的繼承授權紀錄（標題、授權單位、授權日期、供應商、狀態、分類）可以被下游公司偽造或刪除。而且這筆資料的識別碼是元件用來指向它的關聯依據，**刪掉會讓指向它的元件連結悄悄變成斷掉的孤兒**，畫面上不會報錯。

**在哪裡。** `app/module_frame/service/module_frame_leveraged_service.py:105`，`ModuleFrameLeveragedService.add_item`

**怎麼回事。** 同 F6 形狀，寫入落到沒有隔離機制的 `oscal.ssp_leveraged_authorizations`。

**打得到的具體走法。** 子公司管理員打 `PUT /api/1.0/module-frame/<分享範本編號>/leveraged/<項目編號>`，改掉總公司基準上的授權日期。權限檢查過、無歸屬檢查、寫入落地。

**要先有什麼才打得到。**
- 有資源庫的新增／修改／刪除權限
- 存在可見的原廠公版或分享範本
- 知道範本編號

**怎麼修。** `_require_mf` 之後補 `assert_scope_writable(mf.tenant_id, mf.scope)`。

**檢查結果。** 三位檢查員三票全數確認。

---

### F10 — 範本系統特性的覆寫用網址上的編號找範本，不檢查歸屬（中等，可信度中等）

**出事會怎樣。** 分享範本或原廠公版範本那份唯一的系統特性紀錄（系統名稱、說明、系統識別碼、資安敏感等級、授權邊界、狀態）可以被下游公司整份蓋掉。**這是一對一的紀錄，所以一次請求就換掉整份**，不像其他五組是多筆資料要一筆一筆動。這條的破壞速度最快。

**在哪裡。** `app/module_frame/service/module_frame_system_characteristic_service.py:87`，`ModuleFrameSystemCharacteristicService.update`

**怎麼回事。** 同 F6 形狀，覆寫落到沒有隔離機制的 `oscal.ssp_system_characteristics`。

**打得到的具體走法。** 子公司管理員打 `PUT /api/1.0/module-frame/<分享範本編號>/system-characteristic`，送 `{"security_sensitivity_level": "low", "scope_description": "..."}`。角色檢查過、範本可見、既有紀錄被整筆覆蓋。

**要先有什麼才打得到。**
- 有資源庫的修改權限
- 存在可見的原廠公版或分享範本
- 知道範本編號

**怎麼修。** 解出範本後加 `assert_scope_writable(mf.tenant_id, mf.scope)`。

**檢查結果。** 三位檢查員三票全數確認。

---

### F11 — 範本設備／資訊系統對照的寫入用網址上的編號找範本，不檢查歸屬（中等，可信度中等）

**出事會怎樣。** 掛在分享範本上的設備與資訊系統項目可以被下游公司建立、編輯或刪除，包含指向資產主檔的關聯欄位。

**在哪裡。** `app/module_frame/service/module_frame_ssp_resources_service.py:63`，`ModuleFrameSspResourcesService.add_item`

**怎麼回事。** `_require_mf`（第 134 行附近）與 `_require_ssp`（第 91 行）都只確認東西存在，之後就把事情交給下層去做，寫入落到沒有隔離機制的 `oscal.ssp_components`。

**打得到的具體走法。** 子公司管理員打 `DELETE /api/1.0/module-frame/<分享範本編號>/ssp-resources/items/<項目編號>`，刪掉屬於上層公司範本的一筆元件。

**要先有什麼才打得到。**
- 有資源庫的新增／修改／刪除權限
- 存在可見的原廠公版或分享範本
- 知道範本編號

**怎麼修。** `_require_mf` 之後，在三支寫入入口都加 `assert_scope_writable(mf.tenant_id, mf.scope)`。

**檢查結果。** 三位檢查員三票全數確認。

---

### F12 — 範本設備／資訊系統對照清單任何登入帳號都讀得到（低，可信度高）

**出事會怎樣。** 掛在範本上的設備與資訊系統清單會外洩。比前面幾條多一層：回傳的內容經過加工，**帶上了資產主檔裡真實紀錄的名稱與內部編號**（`SspResourcesContextService._device_to_dict` 會補上 `matched_device_name` / `matched_device_uid` / `matched_info_system_name` / `matched_info_system_uid`），那些編號可以再拿去打其他資產相關的端點。

**在哪裡。** `api/module_frame/routes/module_frame_ssp_resources_route.py:42`，`ModuleFrameSspResourcesResource.get`

**怎麼回事。** 同 F1 形狀：編號來自網址，入口只驗登入。同檔的三支寫入都掛了權限檢查。

**打得到的具體走法。** 低權限帳號打 `GET /api/1.0/module-frame/<編號>/ssp-resources`，拿到設備／資訊系統清單以及它們對應的內部資產編號。

**要先有什麼才打得到。**
- 任何一組可登入的帳號
- 一個範本編號
- 範本上掛過設備／資訊系統（通常是走 Excel 匯入那條路填的）

**怎麼修。** 讀取那支加上 `@require_capability("module-frame.read")`。

**檢查結果。** 三位檢查員三票全數確認。研究員原本評中等，三位檢查員投票後降為低（投出的等級是中、低、低），理由是外洩內容比前五條有限、且要範本先掛過這類資料。報告採用檢查員的最終等級。

---

## 5. 掃描範圍與覆蓋

**掃了什麼。** 15 支檔案、1,904 行，全部讀完，沒有任何檔案被跳過或截斷。包含：六類附屬資料的網址入口（六支路由）、範本匯出入口、範本 SSP 查詢入口、一支序列化定義，以及六支商業邏輯服務。

**沒往下追什麼（刻意的，範圍界定如此）。**
- `mf_ssp_export_route.py` 底下的匯出服務——在 FR-113 O2 那一棒的範圍內，本棒只核對路由本身。核對結果：這支**守齊了**，同時掛了登入檢查與 `@require_capability("module-frame.read")`（第 51-52 行），是本棒六支讀取入口裡唯一正確的一支。
- `app/oscal/service/resource_library_app_service.py` 本體——已在 O2 掃過。本棒只看 `module_frame_template_ssp_route.py` 怎麼呼叫它（見下）。
- 專案那一側的同樣六組——已在 O5 掃過，不重複。

**這個強度的限制。** `low` 是單一研究員通掃全範圍加三人投票，**不做元件拆分、不做威脅建模、不做廣度複掃**。所以「這 12 條存在嗎」可信度高（每條都三票一致，我也逐條開檔核對過行號與說法）；「只有這 12 條嗎」則不能保證——這是快速一輪，不是窮盡式閱讀。

**卡片特別點名的那支檔案（`api/oscal/routes/module_frame_template_ssp_route.py`）。** 這支 38 行的檔案本來因為「出現在 O2 的清單裡」被當成掃過而排除，但 O2 報告寫的是「掃描沒有針對這支提出發現」——那是「工具沒報」，不是「查過沒事」。本棒當成從沒查過的檔完整讀了。**結果：工具沒有對它出報告。** 我自己核對的狀況是：它只掛 `@jwt_required()`，沒有角色檢查，呼叫的 `get_template_ssp_ref(mf_uid)` 回的是 `{ssp_uid, exists}`——只有一個識別碼和一個布林值，沒有實質內容。它的形狀確實與 F1～F5、F12 同一類（讀取入口沒有權限檢查），只是回傳的東西沒有敏感資料，所以三人面板沒有讓它成案。**我的判斷是這支仍應一併補上權限檢查**，理由有二：一是它回的識別碼正是後續 SSP 相關端點的入場券，二是同一批六支讀取入口全部要補，漏掉這一支就又留下一個「六份裡有一份沒跟上」的種子。這一點不是工具的發現，是我核對後的建議，請決策者定奪。

---

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

**「這些問題存在嗎」——可信度高。** 12 條每一條都是三位獨立檢查員三票一致確認，沒有一條是勉強過關的。我另外自己做了三件核對：① 逐支開檔確認引用的行號與程式碼相符（全部對上）；② 實際查資料庫結構檔，確認那五張表真的沒有客戶隔離機制（`oscal.parties`、`ssp_components`、`ssp_inventory_items`、`ssp_leveraged_authorizations`、`ssp_system_characteristics`，五張全部是 0 筆隔離設定）；③ 把專案那一側六支服務的守門逐支列出來比對。

**第③項是本棒最有力的證據。** 專案那側六支服務，每一支的每一個對外方法開頭都有一行：讀取是 `self._perm.require_participant(ssp_uid)`（你要是這個專案的人），寫入是 `self._perm.require_manager(ssp_uid)`（你要是這個專案的負責人）——六支全中、一支不漏。範本這側六支服務，同樣位置**一支都沒有**，只有 `_require_mf()` 的「查得到就過」。兩邊放在一起看，這不是「可能有風險」，是同一套邏輯在一側做了、在另一側整排沒做。

**另外還有一個形狀上的觀察。** 卡片要我特別比對「檢查 A、動手改 B」那個病。專案那側的做法是先用權限檢查回傳的 SSP 編號去撈清單、再比對子物件編號。範本這側的查找函式（各服務的 `_resolve`）形狀其實是對的——都是先解出範本的 SSP 編號、再在那個範圍內比對子物件編號，動手的對象確實是從前面解出來的東西推出來的，**沒有踩到「改使用者另外送來的那個編號」那個坑**。問題不在查找的形狀，在於前面那一步「解出範本」時根本沒問歸屬。所以這六條的病因是「該問的沒問」，不是「問了 A 卻動了 B」。

**「只有這些嗎」——不保證。** `low` 強度是一輪快掃，不做威脅建模與廣度複掃。而且本棒只看 15 支檔案，其他入口（例如 Excel 與 Word 匯入那幾條路）不在範圍內。

**沒有任何程式被執行過。** 全部結論都來自讀程式碼，沒有跑測試、沒有實際發動攻擊、沒有驗證過概念性攻擊程式。

---

## 7. 執行概況（數字）

| 項目 | 數值 |
|---|---|
| 掃描版本 | `60cf754f`（工作區有其他未提交改動） |
| 範圍 | 15 支檔案、1,904 行 |
| 強度 | `low`，聚焦正式程式碼（不看測試與第三方複製品） |
| 耗時 | 約 2 小時 47 分 |
| 派出的 agent | 41 個，全部完成，0 個失敗、0 個被跳過、0 個回空 |
| 研究員 | 派出 2 位、回報 2 位 |
| 候選問題 | 21 條，去掉重複後 13 條 |
| 投票 | 13 條各由 3 位檢查員投票，**39 票全數投出，沒有漏投** |
| 成案 | 12 條（1 條被三位檢查員一致否決） |
| 等級調整 | 1 條（F12 由中等降為低） |
| 未驗證候選 | 0 |
| 驗證輪次 | 1 輪跑完，沒有待續 |

三人面板完整跑完、票數齊全，所以這份報告帶得動驗證章。
