# 第 2 批材料（29 件）——機械抽取，供寫派工計畫用


## SUMMARY #3｜出自 M02-1／2／3／7｜⚠️ 部分修

任何一個登入帳號，知道檔案編號就能下載、刪除別人的稽核證據，還能換發一張不必登入就能下載的通行證傳到公司外面（同一個洞的四個出口）


### 模組頁列
M02 第 1 條（M02-file-upload.md:89）
| **1** | 拿到檔案編號就能下載別人的檔案 | 🟠 高 | 只驗登入、不檢查歸屬 | **同一家公司內任何有帳號的人，都能取走別部門的稽核證據**。資料庫那層補上之後，跨客戶已經擋住了，但**同一客戶內跨專案、跨部門仍然拿得到** | 取檔之前先查出這個檔掛在哪個業務物件上，再去問那個物件的權限（第 1、2、3、7 條同一套修法） | ⚠️ **部分修**——資料庫層已補（跨客戶擋住了），**應用程式層一行沒改** |


### 模組頁列
M02 第 2 條（M02-file-upload.md:90）
| **2** | 能替任何檔案換發一張免登入的下載通行證 | 🟠 高 | 只驗登入、不檢查歸屬 | 通行證的機制本身是紮實的（我們自己簽、只有 120 秒、綁死一個檔案與一種用途、改內容就失效），**漏掉的只有一問：換發的時候不問這個檔是不是你的**。所以同公司內任何有帳號的人，拿別部門的檔案編號也換得到 | 換發前補上同一道歸屬檢查（與第 1、3、7 條同一套） | ⬜ **未修** |


### 模組頁列
M02 第 3 條（M02-file-upload.md:91）
| **3** | 取檔案的那層只憑編號就交出檔案 | 🟠 高 | 只驗登入、不檢查歸屬 | 與第 1 條是同一件事的另一半。**這一層是已經把檔案找出來、最有條件去問「這是誰的」的地方**，該補檢查的就是這裡 | 同第 1 條，在這一層補上歸屬檢查 | ⚠️ **部分修**——同第 1 條 |


### 模組頁列
M02 第 7 條（M02-file-upload.md:95）
| **7** | 任何登入者可以永久刪除任何檔案 | 🟠 高 | 只驗登入、不檢查歸屬 | **別家客戶的稽核證據會被永久刪除**，而且沒有救回的機制。**對稽核產品來說，證據被取走還做得下去，證據被刪就做不下去了**，而且不可逆 | 同第 1 條的歸屬檢查，但要問的是「能不能刪」而不是「能不能看」；並改成可復原的刪除 | ⬜ **未修** |


## SUMMARY #4｜出自 M03-2｜⬜ 未修

弱點檢測設定頁的「測試連線」按鈕沒檢查權限，任何登入者按一下，系統就把整家公司的維運帳密解密、送到按的人自己指定的那台主機


### 模組頁列
M03 第 2 條（M03-detection.md:119）
| **2** | 🔴 **「測試連線」按鈕沒有檢查權限，只要能登入就能按——按下去系統會把掃描工具的帳密解密，送到按的人自己指定的那台主機** | 🟠 高 | 只驗登入、不檢查歸屬 | **一次拿走整家公司的維運帳密**——送出去的是連主機用的 SSH／Windows 遠端管理密碼，以及三套掃描工具的存取權杖。**這是漏掉不是刻意**：同一支檔案裡的新增、修改、重置每一支都有檢查權限，**真正的缺口只有「測試連線」這一支**（同樣沒掛的「查引用次數」是純查詢、不解密、不對外連線，沒掛是合理的）。⚠️ **既然第 6 條裁定不限制連到哪裡，這道權限檢查就是唯一的防線** | 補一行權限宣告，同一支檔案裡有三處現成寫法可以照抄 | ⬜ **未修** |


## SUMMARY #5｜出自 M05-1｜⬜ 未修

同一家公司內任何有帳號的人，送一個空白請求到問卷的「查填答歷史明細」，就能讀走全公司每一份問卷的每一版答案與稽核審核意見


### 模組頁列
M05 第 1 條（M05-survey.md:73）
| **1** | 查填答歷史明細完全不檢查資料歸屬，送空白請求就整包讀走 | 🟠 高 | 只驗登入、不檢查歸屬 | **同一家公司內任何有帳號的人，都能讀走全公司每一份問卷的每一版答案與審核意見**——包含受檢單位坦承的缺失、稽核人員的判斷。這是稽核過程本身外洩，不是一般資料外洩 | 問卷編號改成必填，拿到編號之後再確認呼叫者是不是該問卷所屬專案的參與者（現成的判斷已經有了，直接用）| ⬜ **未修** |


## SUMMARY #6｜出自 M05-2｜⬜ 未修

任何登入帳號拿一個問卷編號去讀答案，系統不問這份問卷是不是你的，別部門的作答內容、分數與審核意見全看得到


### 模組頁列
M05 第 2 條（M05-survey.md:74）
| **2** | 讀某份問卷的答案不檢查這份問卷是不是你的 | 🟠 高 | 只驗登入、不檢查歸屬 | 拿一個問卷編號（第 3 條那支功能就會給）就能讀走別部門的作答內容、分數與審核意見。**同一支服務裡另外三支寫入方法每一支都有檢查，只有這支讀取漏掉** | 取出問卷之後補一句「呼叫者是不是這個專案的參與者」，用現成的判斷，一行就好 | ⬜ **未修** |


## SUMMARY #7｜出自 M06-1｜⚠️ 部分修

任何登入帳號知道一個任務編號，就能讀走別家客戶的稽核證明清單（含檔案編號），拿到編號可以接著直接下載檔案本體


### 模組頁列
M06 第 1 條（M06-flow-engine.md:76）
| **1** | 知道一個任務編號就能讀走別家客戶的稽核證明清單 | 🟠 高 | 只驗登入、不檢查歸屬 | 拿得到檔名、描述、雲端硬碟連結、上傳者帳號、檔案指紋與**檔案編號**——**拿到檔案編號之後可以接著直接下載檔案本體**。這不只是一份清單，是一串通往檔案的鑰匙，**與[檔案上傳那塊](M02-file-upload.html)的第 1、3、7 條是同一條鏈**：那邊是「拿到編號就能下載」，這邊是「連編號都送給你」。**同一個檔案裡的新增、更新、刪除都有做檢查，只有讀取漏掉，明顯是漏寫不是設計** | **統一修法**——見[下方這一節](#principle-2) | ⚠️ **部分修**——任務紀錄那張表的資料庫隔離已補上（跨客戶擋住了），**但證明文件表仍然沒有隔離、沒有客戶欄位，應用程式層那道檢查一行沒加** |


## SUMMARY #8｜出自 M06-2｜⬜ 未修

任何登入帳號能讀走別人專案的整串稽核討論，也能往裡面塞留言——留言會即時推播給該專案全體成員，看起來就像同事發的


### 模組頁列
M06 第 2 條（M06-flow-engine.md:77）
| **2** | 流程留言的讀與寫都只檢查有沒有登入 | 🟠 高 | 只驗登入、不檢查歸屬 | **這是這一組五條裡唯一「能寫」的，其他四條只能讀。** 任何登入者能讀走別人專案的整串稽核討論，**也能往裡面塞留言——而留言會即時推播給該專案全體成員**。攻擊者拿別人的專案發一則留言，全體成員收到推播，**看起來就是同事發的**，可以拿來騙人點連結或交出資料。同一個功能模組裡的「完成任務」「退回任務」都有檢查，只有留言漏掉 | **統一修法**——見[下方這一節](#principle-2)；另外補留言的長度與筆數上限 | ⬜ **未修** |


## SUMMARY #46｜出自 掃描總表 120／67＋M10-11｜⬜ 未修

子公司的管理員在「資源庫」改範本時，可以把母公司分享下來的範本的適用控制項清單整個改掉或清空，底下實作記錄一併刪除——之後每個用這份範本開專案的人，以為在稽核範圍內的項目其實沒被評估。**修法**：後端在「改控制項清單」之前先確認這份範本是自己客戶的——把現有那道檢查從「只在改分享身分時」搬到「只要有寫入就檢查」


### 模組頁列
M10 第 11 條（M10-task-platform.md:105）
| **11** | **合規資源庫**：同一家公司內，有「修改資源庫」權限的人可以改掉別人建的那一份資源庫的控制項清單 | ⚪ 低 | 只驗登入、不檢查歸屬 | 同公司內部的資料被別的同事改掉——**拿這份資源庫開專案的人，以為在稽核範圍內的控制項其實沒被評估**。只限同一家公司內，且要有修改資源庫的權限 | 改之前先確認這份資源庫是不是你自己建的；不是就拒絕 | ⬜ **未修** |


### 掃描總表 §3.1 第 120 項
| 120 | 🔴 **一家公司的管理員，可以把另一家公司（或原廠公版）合規範本的「適用控制項」清單整個改掉甚至清空，並把底下所有「我們公司怎麼做到這條」的文字記錄整批刪除**——`update_applicable_controls()`（`app/oscal/service/resource_library_app_service.py:364`，呼叫入口在 `:412`）從頭到尾沒有問過一句「這份範本是不是你家的」，`@transaction` 之後直接 `get_session()` 下手寫 SQL。**出事會怎樣**：被害方不會收到任何通知、畫面上不會有錯誤，範本就這樣被改小或清空；打原廠公版的話，**之後每個用這份公版開新專案的客戶都會複製到被竄改過的範圍——客戶以為在範圍內的控制項其實根本沒被評估**。**要先有什麼才打得到**：①是自己公司的管理員（這是一般客戶開通時就有的角色，`scripts/init/04-seed-core.sql` 預設就配了範本編輯權）②系統裡存在一份「看得到但不屬於你」的範本——原廠公版（每家都看得到，這是設計如此）或母公司分享下來的。**為什麼資料庫沒擋下來——兩層防護同時失效，而且失效原因彼此獨立**：①資料庫那層的客戶隔離只設在範本主表 `compliance.module_frames` 上，而這次的請求**只送控制項清單、沒有動到那張表的任何欄位**，程式根本沒對它發出更新指令——沒發指令，規則就沒機會觸發；真正被改被刪的三張表（`oscal.profile_imports`／`oscal.ssp_implemented_requirements`／`oscal.catalog_control_parts`）**根本沒設這層防護**（runner `grep -c` 三張各回 0，首腦另以 `enable row level\|create policy` 搜全部 migration 重驗 `oscal.profile_imports` 命中數為 0）。②程式那層**其實已經有一支專門擋這件事的警衛 `common/authz/sharing.py` 的 `assert_scope_writable`**，它的說明甚至寫明了為什麼不能只靠資料庫——**但它只站在「使用者要改範本的分享身分」那個 if 裡面**（`app/module_frame/service/module_frame_service.py:110-118`），只送控制項清單、不送分享身分就完全繞過。**白話講就是「讀得到」被當成了「改得動」的通行證**。**與第 67 項的關係**：第 67 項講的是同一支函式，但當初只描述了「改原廠公版」這一條路徑、也沒查出程式層那支警衛存在卻站錯地方；**這一條是同一件事的完整現場，兩條要一起看、一張卡做完**（67 保留原始紀錄不動）。🔴 **2026-09-21 補（FR-113 O3a／O4）——本項與第 123、125 項是同一塊地被三條不同的路打穿，修的時候必須三條一起修**：本項是「編範本的控制項清單」這個入口、第 123 項是「Excel 匯入覆寫範本」、第 125 項是「Word 匯入覆寫範本」，**三條都寫同一批範本資料、三條都是『有人守了一半』**（本項是警衛站錯位置、123／125 是同一個判斷式裡別條守了這條沒守）。只補其中一條，另外兩條照樣進得去 | 🔴 高（3:0，24 票全投零漏投；首腦開檔核對屬實，並實查確認 `oscal.profile_imports` 全無資料庫層防護） | FR-113 O2 第 1 條（`app/oscal/service/resource_library_app_service.py:364/412`；警衛 `common/authz/sharing.py`；繞過點 `app/module_frame/service/module_frame_service.py:110-118`） | ✅ 報告裡有檔:行號＋具體修法，直接抄——動手寫任何東西之前先把範本那筆重新撈出來，明確擋兩件事：身分是原廠公版（SYSTEM）直接拒絕、範本所屬公司跟呼叫者不同直接拒絕，判斷方式照抄 `common/authz/sharing.py` 既有的。⚠️ **開卡時務必寫明兩句**：①**同一條路徑上的 `_init_ao_workflows`（處理新增控制項那半邊）要補同一道檢查**，只補一半等於沒補；②**不要想著靠資料庫補**——那三張表沒有防護，而且本來就設計成「跟著範本走」，補防護是第二道保險、不是這條的解法（要補歸 FR-094「要先改結構」那一級）|


### 掃描總表 §3.1 第 67 項
| 67 | **客戶的租戶管理員可以把「原廠公版」稽核範本的適用控制項清單整個改掉**——改範本時帶上控制項清單，程式會直接下兩句 SQL（改控制項清單、刪掉被拔掉控制項的實作說明），這兩張表**沒有資料庫隔離、連客戶欄位都沒有**，SQL 想改哪筆就改哪筆；而範本主表的讀取規則又明文放行所有客戶讀公版，所以租戶管理員列清單就能指到公版的編號。之後**每個用這份公版開新專案的客戶都會複製到被竄改過的範圍——客戶以為在範圍內的控制項其實根本沒被評估**。門檻是「改範本」這個功能權限，預設的租戶管理員開通時就有。⚠️ 掃完之後平行工作補的 5 行只守「改範本歸屬」這件事，控制項清單那條沒守。**修法**：`resource_library_app_service.py:update_applicable_controls` 開頭查這筆範本的歸屬——公版只允許平台管理員、不是自己客戶的就拒絕（`common/authz/sharing.py` 已有同款判斷，改成套在「這一筆」上）；長期把兩句 SQL 改走 ORM 讓隔離生效，並替 `oscal.profile_imports`／`ssp_implemented_requirements` 補隔離（**這兩張連客戶欄都沒有，歸 FR-094「要先改結構」那一級**） | 中等（三位檢查員一位評次嚴重兩位評中等、取中等，首腦同意；可信度中：主表更新在隔離規則下會拋錯還是靜默 0 列沒有實測，決定攻擊能不能走到 SQL 那一步） | FR-095 H1 H1-4（工具查到，三位檢查員一致通過；首腦已開檔核對＋DEV 唯讀實查兩張表隔離全關） | ✅ 報告裡有檔名行號與修法，直接抄；**程式層守門與資料庫層隔離分兩張、不要排同一輪**。🔴 **2026-09-21 補（FR-113 O2）**：**第 120 項是這一條的完整現場，開卡時兩條一起看、一張卡做完**——本項當初只描述了「改原廠公版」這一條路徑，O2 查出還有「改別家公司分享下來的範本」同樣打得通，而且查出**程式層其實已經有一支對的警衛 `common/authz/sharing.py` 的 `assert_scope_writable`、只是站錯位置**（只站在「改分享身分」那個 if 裡，只送控制項清單就繞過），修法因此比本項當初寫的更明確；本項當初標「可信度中」的那個未實測疑點（主表更新在隔離規則下會拋錯還是靜默 0 列）**O2 已經回答**：請求根本沒對主表發出更新指令，所以不是拋錯也不是靜默 0 列，是**連觸發機會都沒有**。本項維持原始紀錄不動 |


## SUMMARY #47｜出自 M03-12｜⬜ 未修

同一家客戶裡任何成員只要知道任務編號，就讀得到別人專案的掃描執行紀錄——同一個服務裡另外八個功能都有做這道檢查，只有這一支漏掉


### 模組頁列
M03 第 12 條（M03-detection.md:129）
| **12** | **查掃描執行紀錄少了「你是不是這個專案的人」這道檢查** | 🟡 中 | 只驗登入、不檢查歸屬 | **同一家客戶裡任何成員知道任務編號，就讀得到別人專案的掃描歷史**。同一個服務裡另外八個吃任務編號的功能每一個都先做了這道檢查，**只有這一支漏掉** | 補上同一個服務裡其他八處已經在用的那行檢查；任務不存在時回報找不到，不要回空清單 | ⬜ **未修** |


## SUMMARY #48｜出自 M05-3｜⬜ 未修

任何登入帳號送空白請求到「列出任務問卷」，就拿到整個客戶全部問卷的編號、所屬專案、狀態、受檢設備與部門


### 模組頁列
M05 第 3 條（M05-survey.md:75）
| **3** | 列出任務問卷不限範圍，送空白請求就回整個客戶的全部 | 🟡 中 | 只驗登入、不檢查歸屬 | 是第 2 條的入場券。外洩的是全公司問卷的編號、所屬專案、狀態、受檢設備與部門 | 專案編號改成必填，再確認呼叫者是不是該專案的參與者 | ⬜ **未修** |


## SUMMARY #49｜出自 M05-4｜⬜ 未修

任何登入帳號送空白請求到「列出填答歷史」，就能看到全公司跨所有專案誰在什麼時候改了哪份問卷


### 模組頁列
M05 第 4 條（M05-survey.md:76）
| **4** | 列出填答歷史不限範圍 | 🟡 中 | 只驗登入、不檢查歸屬 | 可以看出全公司跨所有專案裡誰在什麼時候改了哪份問卷；同時提供第 1 與第 6 條需要的版本編號 | 問卷編號改成必填，再確認呼叫者是不是該問卷所屬專案的參與者 | ⬜ **未修** |


## SUMMARY #50｜出自 M05-5｜⬜ 未修

任何登入帳號送空查詢到「問卷討論列表」，就把全公司的討論串撈回來——裡面是稽核過程的缺失細節與審查意見


### 模組頁列
M05 第 5 條（M05-survey.md:77）
| **5** | 問卷討論列表送空查詢就把全公司討論撈回來 | 🟡 中 | 只驗登入、不檢查歸屬 | **討論串裡是稽核過程的缺失細節與審查意見**，含你完全沒參與的專案 | 兩個查詢條件改必填，再確認呼叫者是不是該專案的參與者（現成的判斷已經有了）| ⬜ **未修** |


## SUMMARY #51｜出自 M05-7｜⬜ 未修

多人同時填問卷的「房間」想進哪間就進哪間，可以旁聽別專案正在編輯中的問卷並抓到當下的答案快照


### 模組頁列
M05 第 7 條（M05-survey.md:79）
| **7** | 多人同時填問卷的「房間」想進哪間就進哪間 | 🟡 中 | 只驗登入、不檢查歸屬 | 可以旁聽別專案正在編輯中的問卷，還能抓到當下的答案快照 | 進房間前先確認呼叫者是不是該問卷所屬專案的參與者，讀答案快照那條路要用同一道檢查守住 | ⬜ **未修** |


## SUMMARY #52｜出自 M06-5｜⬜ 未修

任何登入帳號知道一個稽核輪次編號，就能讀走別家客戶的階段歷程，含退回理由的完整內容——那是稽核人員親筆寫的「哪裡有問題」


### 模組頁列
M06 第 5 條（M06-flow-engine.md:80）
| **5** | 知道一個稽核輪次編號就能讀走別家客戶的階段歷程 | 🟡 中 | 只驗登入、不檢查歸屬 | 拿得到每次推進或退回的操作者帳號、暱稱、從哪一階段到哪一階段，以及**退回理由的完整內容**——那是稽核人員親筆寫的「哪裡有問題」。網址上明明帶了專案編號，**程式從頭到尾沒拿來用** | **統一修法**——見[下方這一節](#principle-2) | ⬜ **未修** |


## SUMMARY #53｜出自 M06-6｜⬜ 未修

「查目前在哪個階段」用你自己填的專案編號查角色、用你自己填的輪次編號撈資料，兩者不核對，角色不符也只是把「能不能推進」標成否、其他資料照樣整包回傳


### 模組頁列
M06 第 6 條（M06-flow-engine.md:81）
| **6** | 「查目前在哪個階段」用你填的專案編號查角色、用你填的輪次編號撈資料，兩者不核對 | 🟡 中 | 只驗登入、不檢查歸屬 | 角色不符也不會擋下來，只是把「能不能推進」標成否、**其他資料照樣整包回傳**。**連帶問題**：「推進」與「退回」兩支寫法完全一樣、都沒檢查，**現在沒事是靠運氣、不是靠設計**——只是剛好被下游套件的另一道檢查擋住 | **統一修法**——見[下方這一節](#principle-2)；並把推進與退回一併補上，**不要繼續依賴下游套件擋** | ⬜ **未修** |


## SUMMARY #54｜出自 M10-1｜⬜ 未修

同一家公司裡任何能登入的人，對專案成員查詢送一個連專案編號都不填的空白查詢，一次拿到公司內全部專案的成員名冊


### 模組頁列
M10 第 1 條（M10-task-platform.md:95）
| **1** | **專案成員名冊**：同一家公司裡任何登入的人，送一個連專案編號都不填的空白查詢，一次拿到公司內全部專案的成員名冊 | 🟡 中 | 只驗登入、不檢查歸屬 | **連「要查哪個專案」都不必知道**，一次拿到公司內所有專案的編號、誰是管理者、每位成員的登入帳號與暱稱（開發環境實測是兩千多筆）。跨客戶已被資料庫擋住，但**在同一家公司內這就是一份完整名冊**——外洩的登入帳號可以拿去做針對性的密碼攻擊，「誰是哪個專案的管理者」是挑社交工程目標用的名單 | 兩支查詢開頭都補上「你是不是這個專案的成員」，專案編號改成必填、沒帶就拒絕 | ⬜ **未修** |


## SUMMARY #55｜出自 M10-2｜⬜ 未修

同公司裡不是這個專案的人，在網址上換一個編號送出查詢，就讀到別人專案在群組層與控制項層的完整人員配置


### 模組頁列
M10 第 2 條（M10-task-platform.md:96）
| **2** | **專案裡的群組與控制項成員配置**：同公司裡不是這個專案的人，在網址上換一個編號送出查詢，就讀到別人專案的人員配置 | 🟡 中 | 只驗登入、不檢查歸屬 | **用連續猜測的編號，就能讀走同公司任何一個專案在群組層與控制項層的完整人員配置**，而且會順便把專案層的成員一起撈出來——一次請求拿到一個專案三個層級的人員與角色全貌 | 兩支查詢補上同一道成員檢查＋專案編號必填 | ⬜ **未修** |


## SUMMARY #56｜出自 M10-3｜⬜ 未修

同公司裡不是這個專案的人，直接開專案摘要報告就讀到稽核結論全文與歷史版本（歷史版本常保留後來被刪掉的稽核發現）


### 模組頁列
M10 第 3 條（M10-task-platform.md:97）
| **3** | **專案摘要報告（稽核結論）**：同公司裡不是這個專案的人，直接開報告就讀得到結論全文，包含歷史版本 | 🟡 中 | 只驗登入、不檢查歸屬 | **不是這個專案的同事也能讀走稽核結論全文**——歷史版本常保留後來被刪掉的稽核發現。同一個檔案的新增、修改、刪除、還原版本都有檢查，**只有讀取漏掉** | 五支讀取功能開頭補上同檔已有的成員檢查；**清單頁不能改必填**（它本來就要列出「我看得到的所有報告」），改成只列出你有參與的專案的報告 | ⬜ **未修** |


## SUMMARY #57｜出自 M10-4｜⬜ 未修

同公司裡不是這個專案的人，在網址上換成別人的專案編號，就看到那個專案全部稽核輪次的名稱、狀態、起訖日與人員


### 模組頁列
M10 第 4 條（M10-task-platform.md:98）
| **4** | **稽核輪次選單**：同公司裡不是這個專案的人，在網址上換成別人的專案編號，就看到那個專案的全部稽核輪次 | 🟡 中 | 只驗登入、不檢查歸屬 | **看到別人專案的稽核輪次名稱、狀態、起訖日、建立與修改者**，可以推敲別人專案的稽核進度與人員編制。同模組的改專案、刪專案都有檢查，只有這支漏掉 | 開頭先由專案編號查出專案，再呼叫現成的成員檢查；同檔的統計功能一起補 | ⬜ **未修** |


## SUMMARY #58｜出自 M10-5｜⬜ 未修

同一家公司裡任何能登入的人，對任務指派清單送一個空白查詢，一次拿到公司內全部一萬多筆任務指派紀錄


### 模組頁列
M10 第 5 條（M10-task-platform.md:99）
| **5** | **任務指派清單**：同公司裡任何登入的人，送一個空白查詢，一次拿到公司內全部的任務指派紀錄 | 🟡 中 | 只驗登入、不檢查歸屬 | **一次拿到一萬多筆任務指派**，每筆含專案、任務、被指派人的登入帳號與暱稱、誰是審核者。**也可以只帶一個人的編號，反查這個人手上有哪些任務，鎖定目標** | 專案編號或任務編號二擇一必填、兩個都沒帶直接拒絕；帶任務編號的由任務反查專案，再檢查成員身分 | ⬜ **未修** |


## SUMMARY #59｜出自 M10-9｜📋 裁定做法

全系統共用的取資料底層有一條「查詢條件全沒填就不過濾」的規則，送一個空白查詢就等於回整張表（已裁定逐支補、底層不動）


### 模組頁列
M10 第 9 條（M10-task-platform.md:103）
| **9** | **「送空白查詢就回整張表」是所有模組共用的底層行為**——第 1、5 條背後的同一個病灶 | 🟡 中 | 要先做產品決策 | **已經在三個不同模組各放大成一次大量外洩**（檔案清單、成員名冊、任務指派）。不在底層處理，**同型問題會在下一支新功能再長出來一次** | **決策者已裁定**：底層不動，只修有洞的那 3 支（就是第 1、5 條的修法）。**反悔條件：新功能第四次撞到同一件事，就改底層** | ✅ **已裁定做法**（逐支補，底層不動） |


## SUMMARY #60｜出自 M15-2｜⬜ 未修

AI 儀表板呼叫的那 26 支查詢完全不問「你能不能看這筆資料」，基層員工因此拿得到整份組織架構、誰有什麼權限、公司有哪些客戶


### 模組頁列
M15 第 2 條（M15-ai-dashboard.md:94）
| **2** | 那 26 支查詢完全不檢查「你能不能看這筆資料」 | 🟡 中 | 只驗登入、不檢查歸屬 | **拿得到整份組織架構與權限配置**（誰在哪個部門、誰的權限最大、公司有哪些客戶）。這是對內釣魚與挑選攻擊目標的起點；同一份資料走正常畫面是要權限的 | 在儀表板呼叫查詢之前補一道檢查：每支查詢多標明「用這支要什麼權限」，沒標明的直接拒絕 | ⬜ **未修**（已裁定修法，待統一安排） |


## SUMMARY #61｜出自 M15-3｜⬜ 未修

AI 儀表板查專案清單時把呼叫者當成管理員處理，不是專案成員的人也能列出該客戶底下全部稽核專案


### 模組頁列
M15 第 3 條（M15-ai-dashboard.md:95）
| **3** | 專案清單有一道「視同管理員」的後門 | 🟡 中 | 只驗登入、不檢查歸屬 | **不是專案成員的人，也能列出該客戶底下全部專案**。稽核專案的清單本身就是敏感資訊（哪家客戶正在被查什麼） | 已裁定：儀表板只能看到自己參與的專案，那道後門拿掉。與第 2 條同一套修法、同一個位置 | ⬜ **未修**（已裁定修法，待統一安排） |


## SUMMARY #62｜出自 M17-2｜⬜ 未修

任何登入帳號叫出 AI 儀表板說一句「列出所有公告」，就拿到整個客戶底下全部公告（含別部門的、停用草稿、未到發布時間的），前三筆還原文送到外部 AI


### 模組頁列
M17 第 2 條（M17-bulletin.md:70）
| **2** | AI 儀表板這條路繞過守門直接讀公告 | 🟡 中 | 只驗登入、不檢查歸屬 | **任何一個登入帳號叫出 AI 儀表板、說一句「列出所有公告」，就拿到整個客戶底下的全部公告**——含別部門的、已停用的草稿、還沒到發布時間的。而且**前三筆內容會原文送到外部 AI 服務**。<br>⚠️ **這條路現在會當場出錯、暫時走不通，但守門缺失一點都沒變**（原因見下方詳述） | 這條要和 AI 儀表板那塊一起修（見該塊報告第 1 條） | ⬜ **未修**（決策者裁定：與 AI 儀表板那塊一起修，本條不另外裁） |


## SUMMARY #63｜出自 M20-3｜⚠️ 部分修

任何登入的人在任務列表上看到一筆別人專案的任務編號，就點得開它的完整內容——入口雖然要填專案編號，但填真的、全零、亂打一串字三種都查得到同一筆


### 模組頁列
M20 第 3 條（M20-tenant-isolation.md:96）
| **3** | **查詢任務詳細資料的功能，收了專案編號卻從頭到尾沒用它** | 🟡 中 | 只驗登入、不檢查歸屬 | **任何登入的人在任務列表上看到一筆別人專案的任務編號，就點得開它的完整內容**。入口雖然要填專案編號，但不管填真的、全零、還是亂打一串字，**三種都查得到同一筆資料**——「這筆任務屬不屬於你說的那個專案」從來沒被檢查過 | 與檔案下載那一組（同樣的病、不同位置）合併成同一張工單處理 | ⚠️ **部分修**（改與刪補上了守門，**查詢那支一行未改**；且補上的守門仍不核對「這筆任務到底屬不屬於那個專案」——見下方說明） |


## SUMMARY #64｜出自 掃描總表 118／119｜⬜ 未修

刪除控制項底下的佐證文件、刪除文件庫文件時，系統檢查的是「你管不管網址上那份計畫」，刪掉的卻是「你另外給的那份文件編號」，兩者從不核對——後者還會連帶刪關聯、可能把實體檔一起永久銷毀


### 掃描總表 §3.1 第 118 項
| 118 | **刪除控制項底下的佐證文件時，系統檢查的是「你管不管網址上那份計畫」，刪掉的卻是「你另外給的那份文件編號」，兩者從頭到尾沒有互相核對**——`delete_reference_document()` 整個方法只有兩行：第一行呼叫守門 `self._perm.require_manager(ssp_uid)`，**連回傳值都沒接**，第二行就 `self._ref_doc_ds.delete_by_uid(doc_uid)` 直接刪。**出事會怎樣**：任何管理一個專案的人，可以刪掉任何其他專案、**任何其他公司**的佐證文件連結；被害者只會看到文件從控制項底下無聲消失，沒有錯誤訊息，紀錄上也查不到是誰做的。**要先有什麼才打得到**：①任何登入帳號 ②自己開一個專案（**開的人自動變該專案負責人，不需任何人核准**）③知道目標文件編號（當該專案的唯讀成員就看得到）。**為什麼資料庫沒擋下來**：存這些文件的那張表沒有客戶欄位、也沒設資料庫隔離，上層放行之後底下沒有第二道關卡。**同檔上方 `add_reference_documents()` 寫對了**（有接 `ctx = ...` 並用 `ctx.ssp.id`）——同一個檔案裡兩支方法一嚴一鬆，是漏掉不是設計 | 中等（3:0，15 票全投零漏投） | FR-113 O1 第 1 條（`app/oscal/service/ssp_control_implementation_service.py:888-890`） | ✅ 報告裡有 檔:行號 ＋ 具體修法，直接抄——**同 repo 已有一支寫對的可照抄**：`app/module_frame/service/module_frame_reference_document_service.py:147-151`（先撈出來比對歸屬，不符就回「找不到」）。與第 119 項同一個修法、建議同一張卡做完 |


### 掃描總表 §3.1 第 119 項
| 119 | **文件庫的刪除有一模一樣的病，而且更嚴重——會連帶刪掉所有關聯，還可能把實體檔案一起永久銷毀**——`delete_from_pool()` 第 123 行**確實**用 `self._pool_query.list_pool(ctx.ssp.id)` 限定範圍去找，**看起來**像在核對歸屬，但那次查找的產出只有檔案編號（用來判斷要不要刪實體檔），**找不到就填 `None` 繼續往下跑、不會擋**；第 125 行已經把真正那筆紀錄抓在手上了，**卻沒問一句「它屬於哪份計畫」**；第 129 行照樣 `delete_by_uid(doc_uid)`。整段從頭到尾沒有一個 `if` 擋在刪除前面。**出事會怎樣**：同第 118 項，外加該文件底下所有「掛在哪些控制項／查核項目」的關聯全部連鎖刪除；而且引用計數 `count_by_file_id` 是算在**受害者那個檔案**上，算完判定沒人在用，**實體檔案就被永久刪除**。**要先有什麼才打得到**：同第 118 項，但編號更容易拿——有一支查詢**任何登入者都打得到**、不檢查權限 | 中等（3:0，15 票全投零漏投） | FR-113 O1 第 2 條（`app/oscal/service/ssp_document_pool_service.py:123-129`） | ✅ 報告裡有 檔:行號 ＋ 具體修法，直接抄。**修時務必寫明**：檔案編號要從「已驗證過歸屬的那一筆」上取，不要再走另一條查詢，否則兩邊哪天又會各走各的。與第 118 項同一張卡 |


## SUMMARY #65｜出自 掃描總表 121｜⬜ 未修

建立合規資源庫的入口完全沒有權限檢查、只驗有沒有登入，而隔壁做同樣事情的入口是有要求權限的


### 掃描總表 §3.1 第 121 項
| 121 | **建立資源庫的入口完全沒有權限檢查，只驗有沒有登入**——`api/oscal/routes/resource_library_route.py:50`，而隔壁做同樣事情的入口（`ModuleFrameRoute.post`）**是有要求權限的**。**出事會怎樣**：①公司內部任何角色（含唯讀帳號、一般稽核員）都能建範本庫，這件事該不該開放從沒被決定過；②**每建一次就複製一整套框架控制項目錄、每個查核項目各產一張流程範本**——這不是一筆小寫入，而系統沒有裝任何流量限制，**重複呼叫就是用很小的代價把資料庫灌大**。**要先有什麼才打得到**：任何登入帳號。**為什麼資料庫沒擋下來**：這條不是跨客戶問題——建立時一定會綁呼叫者自己的公司、碰不到別人家的資料（檔案裡那段註解說的這一半是對的），資料庫隔離本來就不負責回答「你有沒有資格建」。缺的是功能權限與用量上限這兩件註解沒回答的事 | 中等（3:0，24 票全投零漏投） | FR-113 O2 第 3 條（`api/oscal/routes/resource_library_route.py:50`；正確範本 `ModuleFrameRoute.post`） | ✅ 直接抄。⚠️ **開卡時務必寫明**：**Excel 匯入、Word 匯入那兩條路也會建資源庫，三個入口要一起補**，只補這一支還有其他路進得來 |


## SUMMARY #113｜出自 M03-8｜⬜ 未修

租戶管理員在權限矩陣把某個權限取消勾選，前端選單確實藏起來了，但後端八支讀取功能照樣回全部資料——那一格勾選其實不生效，管理員被騙了


### 模組頁列
M03 第 8 條（M03-detection.md:125）
| **8** | **權限矩陣騙了管理員——取消勾選後前端選單藏起來了，後端八支讀取功能照樣回全部** | ⚪ 低 | 只驗登入、不檢查歸屬 | ⚠️ **這條原本評中風險，重新定性後調降為低。** 原本的寫法會讓人以為「任何登入者都拿得到」，**但這個功能本來就只開給租戶管理員使用**，拿得到的人本來就拿得到。**真正的問題是管理員被騙了**：他在權限矩陣裡取消勾選，前端選單確實藏起來，但該帳號直接對系統送請求，後端八支讀取功能（基準清單、下拉選單、單筆詳情、版本歷史、主檔使用狀況、版本使用狀況、規則清單、抽取狀態）一支都沒檢查，照樣回全部。**也就是說那一格勾選其實不生效** | 八支讀取功能補掛權限檢查，**並保留那顆權限點**以維持未來細分的彈性（例如日後讓稽核人員「看得到但改不了」）。🔴 **補的時候要一併把程式裡那句「讀取端刻意不掛」的說明文字改掉**，否則下一個人看到又會當成設計、把它拿掉。🔴 **這個功能有公版機制，不要改壞**——看得到哪些資料是三種用「或」連起來（公版人人看得到＋自己建的＋上游分享下來的），補權限是加在「能不能進這個功能」那一層，不動「看得到哪些」那一層；**補完要驗四件事**：管理員看得到公版、看得到自己的、下游看得到上游分享的、沒勾權限的被擋掉。**「改壞」的長相是「有些本來看得到的人突然看不到了」** | ⬜ **未修**（⚠️ **與設備清冊那塊同形，要一起裁**） |


## SUMMARY #114｜出自 M04-12｜⬜ 未修

共用的「資料轉成回覆內容」工具資料上有什麼就吐什麼，呼叫的人忘了先篩選，畫面就會拿到不該看到的內部項目


### 模組頁列
M04 第 12 條（M04-common.md:84）
| **12** | 共用的「資料轉成回覆內容」工具，資料上有什麼就吐什麼、完全不過濾 | ⚪ 低 | 回應夾帶不該送的欄位 | 呼叫的人只要忘了自己先篩選，畫面就會拿到不該看到的內部項目。**已經真的出過事**——[AI 儀表板那塊](M15-ai-dashboard.html)的回覆夾帶密碼加密用的鹽值，走的正是這支工具 | 修法已在 AI 儀表板那塊裁定（見[下方](#serializer)） | ⬜ **未修**（修法已裁定，待統一安排） |


## SUMMARY #115｜出自 M06-11｜⬜ 未修

同一家客戶內沒被授權的帳號可以讀走全部流程範本與系統內建範本的完整內容（階段設計、角色指派、判斷條件）——前端選單擋住了，但功能本身是敞開的


### 模組頁列
M06 第 11 條（M06-flow-engine.md:86）
| **11** | 流程範本的列表與單筆讀取沒有掛上權限檢查 | ⚪ 低 | 只驗登入、不檢查歸屬 | **前端選單擋住了看不到的人，但功能本身是敞開的**——同一家客戶內沒被授權的帳號可以讀走全部流程範本與系統內建範本的完整內容（階段設計、角色指派、判斷條件）。跨客戶讀不到（那張表的資料庫隔離有開，已實際查證） | **統一修法**——見[下方這一節](#principle-2)；或者產品決定「人人可看」就把權限與選單綁定一起拿掉，不要前後端規則不一致 | ⬜ **未修** |
