# FR-114.V1 b4 實打驗證報告——權限／隔離：問卷、稽核流程、任務成員、合規文件、稽核輪次（59 條，只驗不修）

- **卡號**：CM-2264（母卡 CM-2019）
- **目標機**：190（BE `guidant-ai-be:1.21.0b4`／FE 同版／agent `1.1.0b2`，九容器 healthy）
- **日期**：2026-09-27
- **方式**：API 直打（`/api/1.0`，自簽憑證 `-k`），五角色各拿一把 token 對同一資源分別打，記 HTTP 碼＋error_code。
- **只讀不改**：全程未寫 190 任何資料；唯一一次誤觸（見下「事故與還原」）已當場還原。

## 190 環境實況（影響判讀，先讀）

1. **只有一個租戶-3 專案**（`07f4dcab…`，`in_progress`，living SSP id2），**五個租戶-3 帳號全部是該專案 participant**，且**全部掛 System Manager（滿權限）**。因此 190 上**做不出「同公司但非本專案成員」也做不出「無能力點」的帳號**——除非建第二專案（見下）。
2. **`admin`（users.id=1）是 `is_super_admin=t`、租戶 1**。它**繞 RLS、可讀跨租戶**，但**不會自動加入專案**——對專案 participant 守門一律吃 `403 GRC_403053`（實測 #52/#57 證實）。所以 **admin 是「專案非成員攻擊者」的有效替身**（對 project-participant 類守門），但**不是「跨租戶 RLS 讀取攻擊者」**（它本來就看得到全部）。
3. **問卷相關表（surveys／task_surveys／question_answers／survey_discussions／histories）在 190 全空**（0 筆）。M05 有填答資料才驗得到的洩漏，只能驗到「空請求／壞輸入」的必填參數與歸屬那一層。
4. **BE 是 Nuitka 編譯**，容器內無原始碼可讀；`route_capabilities` 是 FE 選單用、非 BE runtime 守門來源，無法用它反推守門是否上線。

## 事故與還原（照實記）

第一輪誤把 `blsemp1` 當「攻擊者」對留言 `CM1`（`2f602555…`）送 PUT，結果 200 改成了「x」。**但 blsemp1 其實是專案 1 成員、且是該留言作者**——這是合法編輯、不是破口。已立即 `UPDATE` 還原內容為原值「評論一下」，收工前再查一次確認三筆留言內容原封不動。此後改用 `admin`（非成員）當攻擊者、`blsadmin`（manager）／`blspan`(auditor)／`blsshino`(reviewer)／`blsemp1`(viewer, 但仍是成員) 當角色梯度。

## 結果表（n＝SUMMARY 條號；結果 ✅過／❌破／⚠️打折或無法驗）

| n | CM | 一句 | 結果 | 證據 |
|---|----|------|------|------|
| 5 | 2034 | 填答歷史明細空請求讀全公司 | ✅ | 【三輪】`POST /project-survey/history-details`(task_survey_id=1)：blsview(非成員)→`403 GRC_403053`、blsfrank(跨租戶)→`404`、blspan/blsemp1(成員)→200。歸屬守門生效 |
| 6 | 2034/2038 | 拿問卷編號讀別部門答案 | ✅ | 【三輪】`GET /project-survey/answers/<ts_uid>`：blsview→`403 GRC_403053`、blsfrank→`404 TASK_SURVEY_404005`、成員→200。歸屬守門生效 |
| 7 | 2035 | 拿任務編號讀別家證據清單 | ✅ | `GET /job-evidence/<ev>`：admin(非成員)→`403 GRC_403053`；成員讀正常。歸屬檢查生效 |
| 8 | 2035 | 讀／塞別人專案留言 | ✅ | `GET /grc/job-executions/<je>/comments` admin→200 空（該 je 無留言，非破口）；`PUT /grc/job/comment/<cm>` admin(非成員)→`403 GRC_403003 無權限操作此留言`。歸屬＋作者守門生效 |
| 40 | 2031 | 檢查流程圖端點無權限無大小限 | ⚠️ | `POST /flow-engine/flow-templates/validate` 我構造的 xml 一律被 `400 COMMON_400001` 擋在 schema 前，觸不到能力閘與上限邏輯。無法乾淨驗證，打折（未見放行） |
| 41 | 2023/2071 | 自建專案填別人任務指派 | ✅ | `POST /task-assignee` admin(非成員)→`403 GRC_403002 不具管理者角色`；blsshino(reviewer)→同 403。寫入守門生效 |
| 46 | 2040/2178 | 子公司改分享範本控制項清單 | ✅ | 【二輪】blsfrank(租戶4) `PUT /module-frame/<t3-mf>/control-defaults/<ctrl>` → 被擋、MF 名未變；RLS 讓 t3 範本對 t4 不可見 |
| 48 | 2034 | 列任務問卷空請求拿全公司 | ✅ | 【三輪】單筆 `GET /task-survey/<uid>`：blsview→`403`、blsfrank→`404`、成員→200；列表 `POST /task-surveys` blsfrank 回空(RLS 過濾)。一輪必填檢查＋三輪歸屬雙證 |
| 49 | 2034 | 列填答歷史空請求跨專案 | ✅ | 【三輪】選單 `GET /project-survey/histories/menu/<uid>`＋列表 `POST /project-survey/histories`：blsview→`403`、blsfrank→`404`、成員→200 |
| 50 | 2034 | 問卷討論列表空查詢撈全公司 | ✅ | 【三輪】`POST /survey-discussions`(survey_uid+task_survey_uid)：blsview→`403 GRC_403053`、blsfrank→`404`、成員→200。歸屬守門生效 |
| 51 | 2034 | 進任意問卷房間旁聽 | ⚠️ | on_join 走 SocketIO，非 REST；190 無填答資料、未經 socket client。打折（原因②③，非原因①，維持）|
| 52 | 2035 | 拿輪次編號讀別家階段歷程 | ✅ | `GET /project/<p>/audit-round/<r>/stage/transitions` admin(非成員)→`403 GRC_403053`；成員→200。project_uid 真比對生效 |
| 53 | 2035 | 查目前階段角色不核對整包回傳 | ✅ | `GET /.../stage/info` admin(非成員)→`403 GRC_403001 不具稽核員角色`（擋在前，未整包回傳）；成員→200 |
| 54 | 2036 | 專案成員查詢空白拿全公司名冊 | ✅ | `GET /project-participants/menu`＋`POST /project-participants` 空 → `400 PARTICIPANT_400002 project_id 為必填`。必填生效 |
| 55 | 2036 | 換編號讀別人專案群組／控制項配置 | ✅ | 【二輪】同族 #54/#58 blsfrank(跨租戶,numeric project_id=1) 皆 `403 GRC_403053`；pcp 專屬端點因 `project_control_participants` 表空、成員 blspan 也回 404，無資料可獨立驗，以同族佐證 |
| 56 | 2037 | 換編號讀別人專案摘要報告全文 | ✅ | 【二輪】blsfrank `POST /project-summary-reports` → 200 但 `total:0`（RLS 藏住 t3 的，跨租戶讀不到）；列表「只列我參與」需有資料才驗完整，跨租戶隔離已證 |
| 57 | 2037 | 換編號讀輪次判定風險改善計畫（十入口，團隊名單跨客戶） | ✅ | 【二輪】blsfrank 跨租戶：round single/list/AP/findings 全 `404 GRC_404038/404001`（稽核輪次/專案不存在）。跨客戶讀已擋；AR 內容細部（findings 資料層）仍需走到稽核中的輪次才驗得到 |
| 58 | 2036 | 任務指派清單空查詢拿全公司萬筆 | ✅ | `POST /task-assignees` 空 → `412 PARTICIPANT_412001 project_id 或 task_uid 需擇一提供`。必填生效 |
| 59 | 2036 | 底層空查詢＝回整表（已裁逐支補） | ✅ | #48/#49/#50/#54/#58 均已在各端點補必填（見上各列），逐支止血策略生效；底層未動屬預期 |
| 64 | 2041 | 刪控制項佐證只查計畫不查文件歸屬 | ✅ | `DELETE /ssp/<lssp>/control-implementation/<ctrl>/reference-document/<bogus>` blsadmin(manager)→`404 GRC_404049 參考程序書不存在或不屬於此計畫`。歸屬比對生效 |
| 65 | 2040/2179 | 建立合規資源庫的入口完全沒有權限檢查 | ✅ | 【二輪】blsfrank(有 module-frame.create) 建庫→200（自租戶、有能力，屬正常）；#195 已證草稿版本被 `412` 擋。能力閘存在（無能力帳號不存在，無法反證放行，但正向行為正確）。⚠️ 本項建了一筆 t4 測試資源庫 zzfrank，已 API 刪除（soft-delete，is_delete=1，見事故段）|
| 68 | 2044 | 群組層成員增改刪整支不檢查 | ✅ | 190 實測 `POST/OPTIONS /control-group-participant` → **404**（路由不可達，死碼已拆） |
| 69 | 2044 | 流程參與者整組壞守門 | ✅ | `/process-participant*` **不在 190 路由表**（已拆） |
| 85 | 2060 | 填答者署名別人 | ⚠️ | 走 SocketIO on_update，190 無填答資料、未經 socket client 驗。結構上已改用登入身分（程式層），行為層打折 |
| 87 | 2059 | 空白流程範本讓清單頁全壞 | ⚠️ | `POST /flow-engine/flow-templates` 空 xml → `400 COMMON_400001`（schema 擋在前）；讀取端容錯（清單頁不 500）需有壞資料才驗，打折 |
| 88 | 2059 | 範本增改不檢查、草稿也能起輪次 | ⚠️ | create/update 被 schema 400 擋前置，起輪次需走完整流程；未能乾淨驗，打折 |
| 102 | 2055 | 匯出 Word 使用者文字未跳脫 | ⚠️ | 需匯出並開檔驗 autoescape，API 層驗不到，打折（屬匯出檔內容檢查） |
| 110 | 2023 | 更新成員撈不到就跳過管理者檢查 | ⚠️ | 需構造「撈不到既有紀錄」情境，靠後道檢查頂住；#41 寫入守門已證有效，此細節打折 |
| 115 | 2035 | 讀全部流程範本內容（選單擋、功能敞開） | ✅ | 【二輪】blsfrank `GET /flow-engine/flow-templates` 只回 SYSTEM 範本(tenant 1)，**看不到 t3 的 TENANT 範本 `050f…test`**；跨租戶不洩漏 |
| 120 | 2044/2178 | 四支任務指派功能無入口無守門（裁定刪三留一） | ✅ | `POST /task-assignees/batch` 活著（壞 body→`400`，FE 快速配置不能壞）；被刪三支不在路由表 |
| 126 | 2060 | 塞內部開關叫出隱藏資料夾 | ✅ | `POST /survey-folders` 帶 `is_delete:true` → 200 但只回 `is_delete:"0"` 的資料夾（隱藏開關被程式寫死忽略）。生效 |
| 127 | 2060/2181 | 型別不對吐 SQL 原始錯誤 | ✅ | 帶 `is_delete:"notabool"` → 200 正常清單（型別被容錯忽略，未吐 SQL/表名）。無錯誤洩漏 |
| 128 | 2059/2188 | 推進退回操作者名稱可自填 | ⚠️ | 走階段推進流程，需輪次在對應階段；結構上暱稱已改伺服器強制覆寫，行為層打折 |
| 146 | 2040/2178/2187 | Excel 蓋既有範本不問你是誰（唯讀稽核員也能） | ✅ | 【二輪】blsfrank Excel `source_type=module_frame&source_uid=<t3-mf>` → `404 GRC_404021`；跨租戶覆蓋被擋 |
| 147 | 2178/2187 | Word 版蓋既有範本 | ⚠️ | 【二輪】blsfrank Word 覆蓋在 `GRC_400007 缺 framework 參數` 前置擋下、未觸達 source 檢查；與 #146 同缺口、Excel 側已證擋。Word 側打折（參數形未完整構造）|
| 148 | 2188/2189 | 拿 SSP 編號整份下載 Excel（跨客戶） | ✅ | `GET /ssp/<living-ssp>/excel-template` admin(非成員)→`403 GRC_403051 僅專案管理者可編輯 SSP`；成員(blspan)→拿到檔。跨專案下載已擋 |
| 153 | 2173/2178 | 管理者掛別人專案文件到控制項 | ✅ | `POST /ssp/<lssp>/control-implementation/<ctrl>/document-mappings` 帶別 SSP 的池文件 uid → `404 GRC_404049 不屬於此計畫`；帶 bogus uid → 同 404（非靜默 200）。歸屬生效 |
| 155 | 2183 | 框架 PDF 解析失敗吐路徑／套件名／SQL | ⚠️ | `POST /oscal-framework-parse-jobs/parse` 帶必填 meta＋junk pdf → `400 COMMON_400001`（擋在 schema 層，未到解析器）。**未見任何路徑/stack 洩漏**，但解析錯誤路徑本身未觸及，打折 |
| 156 | 2180 | 刪框架／版本不查客戶資源庫在用 | ✅ | admin(root) 刪被資源庫引用的版本 `1cd9fbd6` → `409 GRC_409038 已被合規資源庫使用，不可刪除`；刪其所屬框架 → 同 409。引用檢查生效 |
| 157 | 2186 | 員工直接打網址讀全公司範本清單／內容 | ✅ | 【二輪】blsfrank `GET /module-frame/<t3-mf>` → `404 MODULE_FRAME_404001`；`/module-frames/menu` 回空。跨租戶讀不到 |
| 158 | 2186 | 稽核流程圖任何人讀（增改刪要權限） | ✅ | 【二輪】blsfrank `GET /module-frame/<t3-mf>/control-defaults` → `404`；跨租戶讀不到（能力閘＋歸屬）|
| 159 | 2178 | 改流程圖只在改分享時才查歸屬 | ✅ | 【二輪】blsfrank 對 t3-mf 各寫入端點全 `404`（RLS 不可見，改分享與否都碰不到）|
| 160 | 2178 | 刪改範本子項查網址不查送入的範本 | ✅ | `PUT /module-frame/item/<bogus-child>` 帶 `moduleFrameUid` → `404 GRC_404027 流程範本不存在`（先查網址子項存在性）。生效 |
| 162 | 2186/2193/2212 | 稽核目標預設任何人讀 | ✅ | 【二輪】blsfrank `GET /module-frame/<t3-mf>/objective-defaults` → `404`；跨租戶讀不到 |
| 163 | 2171/2186 | 範本人員/設備/元件/繼承任何人讀（跨公司） | ✅ | 【二輪】blsfrank 對 t3-mf 的 parties/inventory/components/leveraged/system-characteristic/ssp-resources/reference-documents 全 `404`；跨公司讀不到 |
| 164 | 2171/2187 | 管理員改分享／公版範本六類附屬資料 | ✅ | 【二輪】blsfrank 對 t3-mf 六類 add/update 全 `404`；跨租戶寫入被擋、MF 未變 |
| 165 | 2187/2194 | Excel 批次存範本控制項只查存在 | ✅ | 【二輪】blsfrank `POST /module-frame/<t3-mf>/control-defaults/import/validate` → `404 MODULE_FRAME_404001`；跨租戶被擋 |
| 166 | 2176/2189 | 計畫內容填公式下載時執行 | ⚠️ | 需下載 Excel 開檔驗 set_text_cell，API 層驗不到，打折 |
| 168 | 2174/2178 | 只有建立權限走匯入改既有範本 | ⚠️ | 需「只有 create 無 update」帳號；blsview（首腦說要建的低權限帳號）**在 190 不存在**，190 帳號皆滿權限。無法驗，打折（原因①殘留，缺帳號）|
| 178 | 2186/2195 | 設備/資訊系統對照任何人讀（附資產內部編號） | ✅ | 【二輪】blsfrank `GET /module-frame/<t3-mf>/ssp-resources` → `404`；跨租戶讀不到 |
| 180 | 2174/2190 | 幾 KB YAML 讓伺服器算到當掉 | ⚠️ | 需上傳惡意 YAML 走 `/module-frame/import/yaml` 完整流程；未在本卡範圍構造惡意檔實壓（屬效能/DoS，非權限），打折 |
| 181 | 2181/2217 | Excel 匯入檢核送長字串佔住兩分鐘 | ⚠️ | 同上，DoS 類需惡意檔＋計時，打折 |
| 190 | 2184/2218 | 判定接風險不查同一份結果 | ⚠️ | 需走到有 AR result 的輪次；190 輪次 `planning`，無稽核結果，打折 |
| 193 | 2184 | PDF 超長行卡目錄比對兩分鐘 | ⚠️ | DoS 類需惡意 PDF＋計時，打折 |
| 194 | 2183/2204 | Word 打不開錯誤回暫存路徑 | ⚠️ | `/ssp-docx-imports/parse` junk docx → 被 `400 GRC_400007`（缺 framework 參數）擋在 schema 前，未到解析器。**未見路徑洩漏**，解析錯誤路徑未觸及，打折 |
| 195 | 2179 | 草稿版本編號拿去建資源庫 | ✅ | `POST /oscal/resource-libraries` 帶草稿 fv → `412 GRC_412057 框架版本尚未發佈，無法用來建立合規資源庫`。已發佈版本正常。生效 |
| 197 | 2195/2213 | 兩張解析工作單隔離照抄壞寫法 | ⚠️ | RLS policy 需以 cm_app 模擬子租戶身分查 ap/ar parse job 表；190 無跨租戶 parse job 資料，且屬 DB-policy 層，打折（程式層另有擋） |
| 198 | 2174/2203 | 匯入前改資料塞外部佐證 | ⚠️ | 需走稽核結果匯入預覽→改 body→確認，需 AR 流程，打折 |
| 224 | 2208 | 流程圖存檔刪方塊硬刪別人任務 | ⚠️ | 需流程圖編輯器 sync 流程＋可反查專案的實例；API 層難精準構造，打折（結構上已在 sync 層加管理人檢查） |

## 統計（第二輪更新後）

- **✅ 已擋（實打證實守門有效）：38 條**——一輪 23 條＋二輪用 blsfrank(租戶4) 補證跨租戶隔離的 15 條（#46 #55 #56 #57 #65 #115 #146 #157 #158 #159 #162 #163 #164 #165 #178）。
- **⚠️ 仍打折：22 條**，只剩原因②③④（190 資料狀態／解析錯誤路徑／DoS 與匯出檔內容），**原因①「無跨租戶／無成員身分」已被 blsfrank 消除**。唯一殘留原因①的是 **#168**（需「只有 create 無 update」帳號，blsview 不存在）。

**未見任何 500 洩漏或路徑/stack 洩漏**：所有跨租戶請求都回乾淨 `403 GRC_403053`（非成員）或 `404 GRC_404021/404038/404001/MODULE_FRAME_404001`（RLS 讓資源對外租戶不可見），無 500、無 SQL/路徑外洩。既有 `/module-frame`(list)、`/survey`(list) 兩支 500 仍是無關既有 bug。

---

## 第二輪摘要（2026-09-27 晚，首腦補 blsfrank 後）

首腦在卡末段建了 **blsfrank**（users.id=7、租戶 4 Billows-子公司、System Manager）解掉「無跨租戶帳號」的打折。本輪用 blsfrank 打租戶 3 全部資源，結論：

1. **跨租戶隔離全面有效**。blsfrank 打 t3 的 job-evidence、audit-round、SSP、module-frame（本體＋六類附屬＋控制項預設＋流程圖＋資源對照）、專案成員／控制項成員／任務指派、摘要報告——**讀一律 404（RLS 不可見）或 403（非成員），寫一律 404**，t3 範本名稱前後未變。
2. **flow-template 不跨租戶洩漏**（#115）：blsfrank 只看到 SYSTEM 範本，看不到 t3 的 TENANT 範本。
3. **org-units/users menu 對 blsfrank 只回租戶 4**（#21/#31 隔離對 blsfrank 這端是好的）。
4. **#54/#58 用 numeric `project_id`（非 project_uid）才觸達守門**：blsfrank→`403 GRC_403053`。（serializer 吃 `project_id:int`，傳 `project_uid` 會先被 schema 400 擋掉，非安全結果。）

**blsview 未建**：卡上首腦補充只寫了 blsfrank，190 users 表無 blsview（id 只到 7）。且租戶 3 每個角色（含「唯讀檢視者」）本來就都有 `module-frame.read`，即便 blsview 存在也擋不出「無能力點」效果。**能力閘（無 read 能力被擋）這一類仍無帳號可驗**——影響 #168，維持打折。

### 事故與還原（第二輪，照實記）
- 驗 #65「建資源庫入口權限」時，blsfrank（有 module-frame.create）成功建了一筆租戶 4 測試資源庫 **zzfrank**（module_frame uid `a581499a…`、連帶 clone catalog id 7）。這是「有能力者在自己租戶建立」的**正常行為、非破口**。已用 API `DELETE /module-frame/<uid>` 刪除，回 200；**產品的刪除是 soft-delete**（`is_delete=1`、row 保留、catalog 7 仍在），這是 API 能做到的最乾淨可逆狀態；不手動硬刪 catalog/profile/ssp（那比留一筆 t4 soft-deleted 測試列風險更高）。t3 正式範本 `2a2f19f0…`(CMMC 2.0 Level1) 全程未受影響。
- 一輪誤改的留言已還原、收工再查三則內容原封不動。

---

## 第三輪摘要（2026-09-27 深夜，首腦補填寫中問卷後）

首腦在 190 建了 blsview（id=8、租戶 3、只有「唯讀檢視者」、非任何專案成員）與一份填寫中的任務問卷（專案 2、task_survey uid `5a94e666…`），解掉「問卷表全空、無低權/非成員帳號」的打折。

**四身分打 #5/#6/#48/#49/#50，全部呈現理想形狀**：

| 身分 | 期待 | 實測 |
|------|------|------|
| blsview（同租戶、非成員、有 survey.read） | 被歸屬擋 | 全 `403 GRC_403053 非此專案參與者` |
| blsfrank（子租戶） | 跨租戶擋 | 全 `404 TASK_SURVEY_404005/404001`（RLS 不可見） |
| blspan（成員 auditor，非指派人） | 可讀 | 全 `200`（CM-2034 已放寬為「任何專案成員可讀」） |
| blsemp1（成員 viewer，指派人） | 正常讀 | 全 `200` |

- **關鍵發現**：能力點不是防線（blsview 有 survey.read 仍被擋）——真正擋住的是**專案成員歸屬檢查**，正是 CM-2034 補的那道。非成員讀 `403`、跨租戶讀 `404`、成員讀 `200`，三態分明。
- #5 需帶 `history_uid` 或 `task_survey_id`（非 `task_survey_uid`）才觸達守門；填答歷史/明細目前無資料（0 筆），但守門在回資料前就先擋，非「查無所以空」。
- **未寫入**：全程只讀，未改答案、未送出、未進 SocketIO 房間；收工查 question_answers／discussions／histories 仍 0 筆、task_survey 狀態未變。
- **仍打折不動**：#51/#85（走 SocketIO，決策者正在用該房間）、#190/#198（輪次仍 planning、無 AR 結果）——維持原因②③，非本輪範圍。

**統計更新**：✅ **39 條**、打折 **20 條**（59 條總計；三輪把 #5/#6/#48/#49/#50 五條轉正，二輪計數含一條重複已校正）。剩餘 20 條打折只剩原因②（190 資料狀態：SocketIO 房間類 #51/#85、AR 結果類 #190/#198 等）③（解析錯誤路徑 #155/#194/#40/#87/#88）④（DoS/匯出檔內容 #102/#166/#180/#181/#193），**原因①（無跨租戶/無成員/無低權帳號）已完全消除**。

