---
title: A01 存取控制失效
eyebrow: Guidant AI 資安檢視總報告 · OWASP Top 10（2025）
h1: A01 存取控制失效（Broken Access Control）
lede: 命中這一類的 **119 件**，還原成模組頁的 **127 條原始條目**。每一條用白話寫出：在哪裡、攻擊面、信任邊界、元件端點、駭客怎麼打、得手什麼、修正的做法、狀態。
chips:
  - { text: "🔴 1", kind: crit }
  - { text: "🟠 24", kind: warn }
  - { text: "🟡 62", kind: accent }
  - { text: "⚪ 40", kind: plain }
  - { text: "已修 108／拆除 13／不修 5／SaaS 前 1", kind: ok }
---

## 這一類是什麼

**該擋的人沒擋**——使用者做到了權限以外的事：讀、改、刪不該碰的資料，或執行超出角色的功能。OWASP Top 10:2025 排第一；2025 版把「伺服器替人去連任意網址」也併進這一類。判定依據見[分類報告](../SUMMARY-owasp-stride.html)頁首：未檢查授權、授權判斷錯誤、用使用者可控的編號存取別人資料、路徑穿越、伺服器替人連任意位址、敏感資訊外露，都歸這一類。

這次掃描它佔將近一半，而且 127 條幾乎是同一個形狀：**系統只問「你有沒有登入」，沒問「這筆資料是不是你的」**。駭客要做的常常只是——換個編號、清空查詢條件、或在欄位裡填自己的機器。

在本專案裡，這個形狀反覆以四種樣子出現：

| 樣子 | 典型條目 | 修法的共同做法 |
|---|---|---|
| **讀取漏守門**：同一支檔案的新增、修改、刪除都有權限檢查，只有讀取漏掉 | M05-2、M06-1、M11-14、M19-1 | 讀取補「任一參與者」或對應能力點，用既有守門、不另造 |
| **驗了人、沒驗物**：檢查「你是網址上那個專案的人」，動手的卻是另外送來的編號 | M10-6、M11-11、M20-3、M06-18 | 先反查物件真正屬於哪個專案，再與網址比對，不符一律回「找不到」 |
| **空查詢等於全拿**：查詢條件可以全空，底層看到全空就不過濾 | M05-1、M10-1、M10-5 | 關鍵編號改必填，再補歸屬檢查（底層不動，逐支補） |
| **資料庫那道牆沒立或立反**：客戶隔離規則沒開、沒寫，或方向寫反讓子單位看到母單位 | M20-1、M03-7、M18-8 | 補標準四條規則、改成前綴比對，並加守衛測試防止舊寫法回來 |

修法收斂在 `common/authz/` 這一套守門與全系統唯一的「任務屬於哪個專案」判斷（輪次鏈），不再各自寫一份。

> **為什麼是 127 條不是 119 件**：[總結](../SUMMARY.html)問題總表把「同一套修法、同一支程式」的併成一件（例如檔案的下載、刪除、換發連結、底層取檔四個出口併成 #3）。本頁拆回模組頁原始條目，一個出口一條。每條「嚴重度」欄寫了總表編號，可回去對照。
>
> **資訊深淺**：任務與成員管理那一塊（M10）的模組頁沒有逐條明細表，那 16 條只能依總表一句描述展開「駭客怎麼打」，已在該欄註明；元件端點與修正做法仍逐條查過程式碼與修正 commit。

## 條目清單

| # | 條目 | 嚴重度 | 出自 | 狀態 |
|---:|---|---|---|---|
| 1 | [M01-1 代理程式五條通訊管道全部不檢查對方是誰](#m01-1) | 🔴 最嚴重 | [遠端代理程式](../M01-remote-agent.html) | ✅ 已修 |
| 2 | [M02-1 拿到檔案編號就能下載別人的檔案](#m02-1) | 🟠 高 | [檔案上傳下載](../M02-file-upload.html) | ✅ 已修 |
| 3 | [M03-2 「測試連線」按鈕沒檢查權限，按一下帳密送到自己機器](#m03-2) | 🟠 高 | [弱點檢測整合](../M03-detection.html) | ✅ 已修 |
| 4 | [M05-1 送空白請求就讀走全公司每一版問卷答案與審核意見](#m05-1) | 🟠 高 | [問卷](../M05-survey.html) | ✅ 已修 |
| 5 | [M05-2 拿一個問卷編號就讀走別部門的答案與分數](#m05-2) | 🟠 高 | [問卷](../M05-survey.html) | ✅ 已修 |
| 6 | [M06-1 一個任務編號讀走別人的稽核證明清單](#m06-1) | 🟠 高 | [稽核流程](../M06-flow-engine.html) | ✅ 已修 |
| 7 | [M06-2 讀走別人專案的稽核討論並冒充同事塞留言](#m06-2) | 🟠 高 | [稽核流程](../M06-flow-engine.html) | ✅ 已修 |
| 8 | [M07-1 舊版「預覽證據檔」填一個雲端檔案編號就把檔案抓回來](#m07-1) | 🟠 高 | [證據自動分類](../M07-evidence-classification.html) | 🗑️ 已拆除 |
| 9 | [M02-5 查設定就拿到雲端儲存帳密明文，可繞過系統直連](#m02-5) | 🟠 高 | [檔案上傳下載](../M02-file-upload.html) | ✅ 已修 |
| 10 | [M22-2 一個請求就拿到檔案儲存的帳密與寄信、帳號目錄位址](#m22-2) | 🟠 高 | [系統設定](../M22-system-core.html) | ✅ 已修 |
| 11 | [M09-1 任一客戶管理員改日誌轉送位址，全客戶活動紀錄送往他的機器](#m09-1) | 🟠 高 | [系統日誌](../M09-log.html) | ✅ 已修 |
| 12 | [M22-1 一個客戶管理員改掉全公司登入規則，並把帳號目錄指到自己機器](#m22-1) | 🟠 高 | [系統設定](../M22-system-core.html) | ✅ 已修 |
| 13 | [M18-8 被停權客戶用一個子單位帳號刪掉母公司停權紀錄、整棵樹復權](#m18-8) | 🟠 高 | [授權管理](../M18-license.html) | ✅ 已修 |
| 14 | [M20-1 13 張標有客戶歸屬的表隔離沒生效，看得到別家資料](#m20-1) | 🟠 高 | [客戶資料隔離（跨模組專項）](../M20-tenant-isolation.html) | ✅ 已修 |
| 15 | [M02-6 檔案紀錄表連客戶歸屬欄位都沒有，資料庫擋不住跨客戶](#m02-6) | 🟠 高 | [檔案上傳下載](../M02-file-upload.html) | ✅ 已修 |
| 16 | [M04-2 沒有登入身分的路徑被當成最高權限，一次看到全部客戶](#m04-2) | 🟠 高 | [共用基礎](../M04-common.html) | ✅ 已修 |
| 17 | [M13-4 在個人資料頁按儲存，把自己改成管理員](#m13-4) | 🟠 高 | [登入與權限](../M13-iam.html) | ✅ 已修 |
| 18 | [M20-2 「我的任務」等四個畫面用最高權限帳號查，看到別家待辦](#m20-2) | 🟠 高 | [客戶資料隔離（跨模組專項）](../M20-tenant-isolation.html) | ✅ 已修 |
| 19 | [M10-4 換成別人的專案編號看稽核判定、風險評等與改善計畫](#m10-4) | 🟠 高 | [任務與成員管理](../M10-task-platform.html) | ✅ 已修 |
| 20 | [M12-1 十支讀取入口換個網址看別人專案的稽核判定](#m12-1) | 🟠 高 | [稽核輪次管理](../M12-compliance-audit.html) | ✅ 已修 |
| 21 | [M11-2 用 Excel 蓋掉既有範本，從頭到尾不問你是誰](#m11-2) | 🟠 高 | [合規文件核心](../M11-oscal.html) | ✅ 已修 |
| 22 | [M11-3 Word 版覆蓋既有範本只查存不存在](#m11-3) | 🟠 高 | [合規文件核心](../M11-oscal.html) | ✅ 已修 |
| 23 | [M11-1 知道計畫編號就把整份系統安全計畫下載成 Excel](#m11-1) | 🟠 高 | [合規文件核心](../M11-oscal.html) | ✅ 已修 |
| 24 | [M24-2 把別家專案重建到自己的雲端硬碟、還能摻假證據](#m24-2) | 🟠 高 | [主系統自己的程式](../M24-host.html) | ✅ 已修 |
| 25 | [M22-4 別家客戶的「總部」管理員改得動全站唯一的寄信與帳號目錄設定](#m22-4) | 🟠 高 | [系統設定](../M22-system-core.html) | ✅ 已修 |
| 26 | [M03-14 只能看的成員能完成別人的任務，還能對正式機器發掃描](#m03-14) | 🟡 中 | [弱點檢測整合](../M03-detection.html) | ✅ 已修 |
| 27 | [M06-7 只能看的成員完成或退回別人的稽核任務](#m06-7) | 🟡 中 | [稽核流程](../M06-flow-engine.html) | ✅ 已修 |
| 28 | [M05-6 還原歷史版本時指定別部門的版本編號，把別人答案複製進來](#m05-6) | 🟡 中 | [問卷](../M05-survey.html) | ✅ 已修 |
| 29 | [M05-8 有問卷權限就能竄改別人留言、改完仍掛原作者名](#m05-8) | 🟡 中 | [問卷](../M05-survey.html) | ✅ 已修 |
| 30 | [M06-4 「檢查流程圖」沒權限檢查，還能丟超大內容進來](#m06-4) | 🟡 中 | [稽核流程](../M06-flow-engine.html) | ✅ 已修 |
| 31 | [M10-6 自己開專案當管理者，指派時「專案填自己的、任務填別人的」](#m10-6) | 🟡 中 | [任務與成員管理](../M10-task-platform.html) | ✅ 已修 |
| 32 | [M17-1 一個部門的編輯者改掉或永久刪掉別部門的公告](#m17-1) | 🟡 中 | [公告](../M17-bulletin.html) | ✅ 已修 |
| 33 | [M19-1 權限被關掉的人把網址貼上去照樣看整張設備清冊](#m19-1) | 🟡 中 | [設備與資訊系統清冊](../M19-asset.html) | ✅ 已修 |
| 34 | [M23-2 一般員工改／刪別人的意見回饋，連帶關掉開發團隊的問題單](#m23-2) | 🟡 中 | [意見回饋](../M23-issue.html) | ✅ 已修 |
| 35 | [M11-5 子公司管理員清空母公司範本的適用控制項清單](#m11-5) | 🟡 中 | [合規文件核心](../M11-oscal.html) | ✅ 已修 |
| 36 | [M10-11 子公司管理員清空母公司範本的控制項清單（與 M11-5 同案）](#m10-11) | 🟡 中 | [任務與成員管理](../M10-task-platform.html) | ✅ 已修 |
| 37 | [M03-12 知道任務編號就讀到別人專案的掃描執行紀錄](#m03-12) | 🟡 中 | [弱點檢測整合](../M03-detection.html) | ✅ 已修 |
| 38 | [M05-3 送空白請求「列出任務問卷」拿到全客戶問卷清單](#m05-3) | 🟡 中 | [問卷](../M05-survey.html) | ✅ 已修 |
| 39 | [M05-4 送空白請求「列出填答歷史」看到全公司誰改過哪份問卷](#m05-4) | 🟡 中 | [問卷](../M05-survey.html) | ✅ 已修 |
| 40 | [M05-5 送空查詢「問卷討論列表」撈回全公司缺失細節與審查意見](#m05-5) | 🟡 中 | [問卷](../M05-survey.html) | ✅ 已修 |
| 41 | [M05-7 想進哪間多人填答房間就進哪間，旁聽並抓答案快照](#m05-7) | 🟡 中 | [問卷](../M05-survey.html) | ✅ 已修 |
| 42 | [M06-5 一個稽核輪次編號讀走別人的階段歷程與退回理由](#m06-5) | 🟡 中 | [稽核流程](../M06-flow-engine.html) | ✅ 已修 |
| 43 | [M06-6 「查目前在哪個階段」兩個編號不核對，角色不符照樣整包回傳](#m06-6) | 🟡 中 | [稽核流程](../M06-flow-engine.html) | ✅ 已修 |
| 44 | [M10-1 送不填專案編號的空白查詢，一次拿到公司全部專案的成員名冊](#m10-1) | 🟡 中 | [任務與成員管理](../M10-task-platform.html) | ✅ 已修 |
| 45 | [M10-2 換一個專案編號就讀到別人專案的群組層與控制項層人員配置](#m10-2) | 🟡 中 | [任務與成員管理](../M10-task-platform.html) | ✅ 已修 |
| 46 | [M10-3 直接開別人專案的摘要報告，讀稽核結論全文與歷史版本](#m10-3) | 🟡 中 | [任務與成員管理](../M10-task-platform.html) | ✅ 已修 |
| 47 | [M10-5 送空白查詢拿到公司內全部一萬多筆任務指派紀錄](#m10-5) | 🟡 中 | [任務與成員管理](../M10-task-platform.html) | ✅ 已修 |
| 48 | [M10-9 取資料底層「條件全沒填就不過濾」，空查詢等於回整張表](#m10-9) | 🟡 中 | [任務與成員管理](../M10-task-platform.html) | ✅ 已修 |
| 49 | [M15-2 AI 儀表板 26 支查詢不問權限，基層員工拿到整份組織架構](#m15-2) | 🟡 中 | [AI 儀表板](../M15-ai-dashboard.html) | ✅ 已修 |
| 50 | [M15-3 AI 儀表板查專案清單把呼叫者當管理員，列出全部專案](#m15-3) | 🟡 中 | [AI 儀表板](../M15-ai-dashboard.html) | ✅ 已修 |
| 51 | [M17-2 對 AI 儀表板說「列出所有公告」拿到全客戶公告](#m17-2) | 🟡 中 | [公告](../M17-bulletin.html) | ✅ 已修 |
| 52 | [M20-3 任務詳情收了專案編號卻不用，填真的、全零、亂打都查得到同一筆](#m20-3) | 🟡 中 | [客戶資料隔離（跨模組專項）](../M20-tenant-isolation.html) | ✅ 已修 |
| 53 | [M11-11 刪佐證文件時查「你管不管這份計畫」，刪的卻是另外給的文件編號](#m11-11) | 🟡 中 | [合規文件核心](../M11-oscal.html) | ✅ 已修 |
| 54 | [M11-13 建立合規資源庫的入口完全沒權限檢查](#m11-13) | 🟡 中 | [合規文件核心](../M11-oscal.html) | ✅ 已修 |
| 55 | [M07-2 舊版查結果／查報表不檢查成員，查不到就退回直接讀雲端硬碟](#m07-2) | 🟡 中 | [證據自動分類](../M07-evidence-classification.html) | 🗑️ 已拆除 |
| 56 | [M07-3 舊版工作清單不檢查成員，進度資料全系統共用不分客戶](#m07-3) | 🟡 中 | [證據自動分類](../M07-evidence-classification.html) | 🗑️ 已拆除 |
| 57 | [M10-7 群組層成員的增／改／刪因漏接一條線完全不檢查權限](#m10-7) | 🟡 中 | [任務與成員管理](../M10-task-platform.html) | 🗑️ 已拆除 |
| 58 | [M10-8 流程參與者增／改／刪把關寫在永遠跑不到的位置](#m10-8) | 🟡 中 | [任務與成員管理](../M10-task-platform.html) | 🗑️ 已拆除 |
| 59 | [M03-6 「測試連線」目標主機由呼叫者自己填，代理程式變成內網跳板](#m03-6) | 🟡 中 | [弱點檢測整合](../M03-detection.html) | ✅ 已修 |
| 60 | [M15-4 儀表板整包回傳，夾帶密碼鹽值與超管旗標](#m15-4) | 🟡 中 | [AI 儀表板](../M15-ai-dashboard.html) | ✅ 已修 |
| 61 | [M03-7 資料庫隔離規則方向寫反，子單位看得到還改得到母單位資料](#m03-7) | 🟡 中 | [弱點檢測整合](../M03-detection.html) | ✅ 已修 |
| 62 | [M20-5 SaaS 版合規文件整塊沒有最後一道防線](#m20-5) | 🟡 中 | [客戶資料隔離（跨模組專項）](../M20-tenant-isolation.html) | 🕓 SaaS 前必做 |
| 63 | [M16-1 「寄測試信」把存著的郵件密碼送到操作者指定的主機](#m16-1) | 🟡 中 | [寄信與通知](../M16-notification.html) | 🚫 裁定不修 |
| 64 | [M20-4 兩支半夜清理程式靠「找不到身分就給最高權限」才跑得動](#m20-4) | 🟡 中 | [客戶資料隔離（跨模組專項）](../M20-tenant-isolation.html) | ✅ 已修 |
| 65 | [M11-12 把別人的程序書掛到自己控制項再下載](#m11-12) | 🟡 中 | [合規文件核心](../M11-oscal.html) | ✅ 已修 |
| 66 | [M11-27 刪整個合規框架不檢查有沒有客戶在用](#m11-27) | 🟡 中 | [合規文件核心](../M11-oscal.html) | ✅ 已修 |
| 67 | [M11-14 沒被授權的員工直接打網址讀走範本清單與全文](#m11-14) | 🟡 中 | [合規文件核心](../M11-oscal.html) | ✅ 已修 |
| 68 | [M11-15 任何人讀走範本的稽核流程圖](#m11-15) | 🟡 中 | [合規文件核心](../M11-oscal.html) | ✅ 已修 |
| 69 | [M11-6 改範本流程圖只有順便改「分享設定」才檢查歸屬](#m11-6) | 🟡 中 | [合規文件核心](../M11-oscal.html) | ✅ 已修 |
| 70 | [M11-7 檢查網址上的子項、動手改的卻是另外送來的範本](#m11-7) | 🟡 中 | [合規文件核心](../M11-oscal.html) | ✅ 已修 |
| 71 | [M11-16 列出範本掛的程序書清單再拿編號下載](#m11-16) | 🟡 中 | [合規文件核心](../M11-oscal.html) | ✅ 已修 |
| 72 | [M11-17 任何人讀走範本的稽核目標預設內容](#m11-17) | 🟡 中 | [合規文件核心](../M11-oscal.html) | ✅ 已修 |
| 73 | [M11-4 拿一個範本編號讀走範本裡的人員、設備、系統清冊](#m11-4) | 🟡 中 | [合規文件核心](../M11-oscal.html) | ✅ 已修 |
| 74 | [M11-8 改原廠或母公司範本底下六類附屬資料不問歸屬](#m11-8) | 🟡 中 | [合規文件核心](../M11-oscal.html) | ✅ 已修 |
| 75 | [M11-9 用 Excel 批次存範本控制項清單不查歸屬](#m11-9) | 🟡 中 | [合規文件核心](../M11-oscal.html) | ✅ 已修 |
| 76 | [M11-10 只有「建立」權限的人走匯入改掉既有範本](#m11-10) | 🟡 中 | [合規文件核心](../M11-oscal.html) | ✅ 已修 |
| 77 | [M06-17 規劃頁任務清單不問你是不是這個專案的人](#m06-17) | 🟡 中 | [稽核流程](../M06-flow-engine.html) | ✅ 已修 |
| 78 | [M06-18 匯入任務 Excel 的「確認」步驟改掉別專案的任務](#m06-18) | 🟡 中 | [稽核流程](../M06-flow-engine.html) | ✅ 已修 |
| 79 | [M06-19 把別專案卡住的任務「強制開始」](#m06-19) | 🟡 中 | [稽核流程](../M06-flow-engine.html) | ✅ 已修 |
| 80 | [M07-13 把 AI 服務位址填成自己的機器騙走原廠金鑰](#m07-13) | 🟡 中 | [證據自動分類](../M07-evidence-classification.html) | ✅ 已修 |
| 81 | [M24-3 上傳的檔案整個資料夾開在網站公開路徑下](#m24-3) | 🟡 中 | [主系統自己的程式](../M24-host.html) | ✅ 已修 |
| 82 | [M24-8 雲端硬碟資料夾設成「知道連結誰都能編輯」](#m24-8) | 🟡 中 | [主系統自己的程式](../M24-host.html) | 🚫 裁定不修 |
| 83 | [M24-4 兩家客戶接同一個 Google 帳號，同步會互相打到對方證據](#m24-4) | 🟡 中 | [主系統自己的程式](../M24-host.html) | ✅ 已修 |
| 84 | [M10-20 摘要報告 PDF 插圖白名單放行 SVG，讓伺服器替你連內網](#m10-20) | 🟡 中 | [任務與成員管理](../M10-task-platform.html) | ✅ 已修 |
| 85 | [M10-21 還原報告歷史版本不檢查那版本是不是這份報告的](#m10-21) | 🟡 中 | [任務與成員管理](../M10-task-platform.html) | ✅ 已修 |
| 86 | [M24-7 診斷指令用猜得到的暫存夾、不檢查捷徑](#m24-7) | 🟡 中 | [主系統自己的程式](../M24-host.html) | ✅ 已修 |
| 87 | [M02-12 刪原廠共用檔，檔案本體先被刪、紀錄刪不掉，全客戶受害](#m02-12) | 🟡 中 | [檔案上傳下載](../M02-file-upload.html) | ✅ 已修 |
| 88 | [M10-10 寫入群組／控制項層成員時不比對該物件是否屬於這個專案](#m10-10) | ⚪ 低 | [任務與成員管理](../M10-task-platform.html) | 🗑️ 已拆除 |
| 89 | [M10-12 更新專案成員撈不到既有紀錄就整段跳過管理者檢查](#m10-12) | ⚪ 低 | [任務與成員管理](../M10-task-platform.html) | ✅ 已修 |
| 90 | [M17-3 用網址直開公告什麼都不檢查，調部門後舊網址照樣打得開](#m17-3) | ⚪ 低 | [公告](../M17-bulletin.html) | ✅ 已修 |
| 91 | [M17-4 沒被分配部門的帳號打公告列表，整個不過濾全部給看](#m17-4) | ⚪ 低 | [公告](../M17-bulletin.html) | ✅ 已修 |
| 92 | [M19-2 只有「修改」權限，送一個「停用」就等於刪掉資訊系統](#m19-2) | ⚪ 低 | [設備與資訊系統清冊](../M19-asset.html) | ✅ 已修 |
| 93 | [M03-8 權限矩陣取消勾選，後端八支讀取照樣全給](#m03-8) | ⚪ 低 | [弱點檢測整合](../M03-detection.html) | ✅ 已修 |
| 94 | [M04-12 共用的回覆工具有什麼吐什麼](#m04-12) | ⚪ 低 | [共用基礎](../M04-common.html) | ✅ 已修 |
| 95 | [M06-11 沒被授權的人讀走全部流程範本](#m06-11) | ⚪ 低 | [稽核流程](../M06-flow-engine.html) | ✅ 已修 |
| 96 | [M06-14 三支直接拿路徑開檔的閒置程式](#m06-14) | ⚪ 低 | [稽核流程](../M06-flow-engine.html) | 🗑️ 已拆除 |
| 97 | [M06-16 兩組沒人用、也沒權限檢查的備用服務](#m06-16) | ⚪ 低 | [稽核流程](../M06-flow-engine.html) | 🗑️ 已拆除 |
| 98 | [M10-18 四支沒有把關的任務指派程式](#m10-18) | ⚪ 低 | [任務與成員管理](../M10-task-platform.html) | 🗑️ 已拆除 |
| 99 | [M21-2 空白查詢撈走全站成員名冊](#m21-2) | ⚪ 低 | [意見回饋重新檢視（跨模組專項）](../M21-issue-rescan.html) | 🗑️ 已拆除 |
| 100 | [M05-11 問卷資料夾清單叫出被隱藏的資料夾](#m05-11) | ⚪ 低 | [問卷](../M05-survey.html) | ✅ 已修 |
| 101 | [M22-3 儲存設定頁把共用密鑰明文送到瀏覽器](#m22-3) | ⚪ 低 | [系統設定](../M22-system-core.html) | ✅ 已修 |
| 102 | [M04-4 運作紀錄表沒有客戶隔離](#m04-4) | ⚪ 低 | [共用基礎](../M04-common.html) | 🚫 裁定不修 |
| 103 | [M04-5 完整出錯內容寫進沒隔離的紀錄表](#m04-5) | ⚪ 低 | [共用基礎](../M04-common.html) | 🚫 裁定不修 |
| 104 | [M17-5 公告與部門的關聯表沒有客戶隔離](#m17-5) | ⚪ 低 | [公告](../M17-bulletin.html) | 🚫 裁定不修 |
| 105 | [M11-28 任何人讀走範本的「設備／資訊系統對照」清單](#m11-28) | ⚪ 低 | [合規文件核心](../M11-oscal.html) | ✅ 已修 |
| 106 | [M11-31 下載「空白範本」夾帶全公司資產與人員清冊](#m11-31) | ⚪ 低 | [合規文件核心](../M11-oscal.html) | ✅ 已修 |
| 107 | [M06-20 匯出任務 Excel 時檢查的專案和實際匯出的可以是兩個](#m06-20) | ⚪ 低 | [稽核流程](../M06-flow-engine.html) | ✅ 已修 |
| 108 | [M06-21 匯入任務的「驗證」兩步不檢查身分、可試探存在性](#m06-21) | ⚪ 低 | [稽核流程](../M06-flow-engine.html) | ✅ 已修 |
| 109 | [M06-22 查別專案任務「卡在哪幾條分支」](#m06-22) | ⚪ 低 | [稽核流程](../M06-flow-engine.html) | ✅ 已修 |
| 110 | [M05-14 沒買問卷模組的客戶照樣能用「填答」那一半](#m05-14) | ⚪ 低 | [問卷](../M05-survey.html) | ✅ 已修 |
| 111 | [M12-4 任何人看任何專案的「任務設定樹」](#m12-4) | ⚪ 低 | [稽核輪次管理](../M12-compliance-audit.html) | 🗑️ 已拆除 |
| 112 | [M12-5 非成員問得出專案「目前的 SSP」編號與狀態](#m12-5) | ⚪ 低 | [稽核輪次管理](../M12-compliance-audit.html) | 🗑️ 已拆除 |
| 113 | [M12-6 專案經理改寫、再刪掉稽核員的「改善建議」](#m12-6) | ⚪ 低 | [稽核輪次管理](../M12-compliance-audit.html) | ✅ 已修 |
| 114 | [M11-29 把稽核「判定」接到別份的「風險」上（地雷）](#m11-29) | ⚪ 低 | [合規文件核心](../M11-oscal.html) | ✅ 已修 |
| 115 | [M11-30 拿草稿版本編號複製平台管理員還沒公開的控制項](#m11-30) | ⚪ 低 | [合規文件核心](../M11-oscal.html) | ✅ 已修 |
| 116 | [M12-7 兩張「解析工作單」表照抄了五月就判定壞掉的隔離寫法](#m12-7) | ⚪ 低 | [稽核輪次管理](../M12-compliance-audit.html) | ✅ 已修 |
| 117 | [M12-9 確認匯入稽核結果時塞一個不屬於這輪的佐證](#m12-9) | ⚪ 低 | [稽核輪次管理](../M12-compliance-audit.html) | ✅ 已修 |
| 118 | [M18-13 授權過期的唯讀有兩個縫](#m18-13) | ⚪ 低 | [授權管理](../M18-license.html) | ✅ 已修 |
| 119 | [M24-10 雲端硬碟授權連結沒綁發起的瀏覽器](#m24-10) | ⚪ 低 | [主系統自己的程式](../M24-host.html) | ✅ 已修 |
| 120 | [M24-12 驗證雲端硬碟憑證只查是不是平台管理員](#m24-12) | ⚪ 低 | [主系統自己的程式](../M24-host.html) | ✅ 已修 |
| 121 | [M06-24 流程圖存檔刪方塊，連底下證據硬刪別人專案的任務](#m06-24) | ⚪ 低 | [稽核流程](../M06-flow-engine.html) | ✅ 已修 |
| 122 | [M10-22 換個專案編號看別人專案的證據蒐集進度](#m10-22) | ⚪ 低 | [任務與成員管理](../M10-task-platform.html) | ✅ 已修 |
| 123 | [M24-18 診斷資料照索引檔路徑讀檔、沒限制在暫存目錄內](#m24-18) | ⚪ 低 | [主系統自己的程式](../M24-host.html) | ✅ 已修 |
| 124 | [M24-9 即時通知有一個頻道不驗身分、誰都能加入任意房間](#m24-9) | ⚪ 低 | [主系統自己的程式](../M24-host.html) | 🗑️ 已拆除 |
| 125 | [M11-40 人員對帳收了公司編號卻沒用、拿全客戶資料比對](#m11-40) | ⚪ 低 | [合規文件核心](../M11-oscal.html) | ✅ 已修 |
| 126 | [M11-41 兩張解析工作單表資料庫新增規則寫成「一律允許」](#m11-41) | ⚪ 低 | [合規文件核心](../M11-oscal.html) | ✅ 已修 |
| 127 | [M15-7 AI 儀表板生成只查有沒有買模組、不查角色權限](#m15-7) | ⚪ 低 | [AI 儀表板](../M15-ai-dashboard.html) | ✅ 已修 |

---

## M01-1 🔴 代理程式五條通訊管道全部不檢查對方是誰 {#m01-1}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🔴 最嚴重（總表 #1；STRIDE：冒充身分、資料外洩、竄改資料、權限提升） |
| **在哪裡** | 遠端代理程式模組：代理程式與後端之間的五條通道——報到、領工作、確認收到、回報結果、下載檔案 |
| **攻擊面位置** | 後端對外開放的「代理程式控制面」API。這一面設計上就**不帶使用者登入**（代理程式是機器、不是人），所以任何連得到後端的人都打得到，不需要帳號 |
| **信任邊界位置** | **客戶機房 ↔ 我們後端**。應該在後端入口用「簽過章的身分憑證」確認對方是哪一台；當時是**讀請求裡自報的編號就相信**，等於邊界沒有守衛 |
| **元件端點位置** | `jedi_remote_agent/api/routing.py`：`POST /agents/register`（報到）、`/agents/heartbeat`（領工作）、`/agents/tasks/{uid}/ack`（確認收到）、`/agents/tasks/{uid}/result`（回報結果）＋檔案下載通道；處理在 `app/service/agent_task_service.py`、`agent_control_auth_service.py` |
| **駭客怎麼打** | ① 在任何連得到後端的位置，不需要帳號；② 照代理程式平常的請求格式，發一個「我是代理程式 X，有工作嗎」；③ 後端不驗證「你真的是 X」，直接回一整套稽核用的高權限鑰匙與主機帳密；④ 同一招打回報結果通道，可以替 X 回報「掃描全部正常」或抹掉結果，打下載通道則拿走客戶上傳來掃的源碼；⑤ 管理員在畫面按「撤銷」只斷了一條通道，其他四條照用 |
| **得手什麼** | 客戶整套主機的明文帳密、冒充任一家客戶的機器、竄改或抹除稽核證據 |
| **修正的做法** | ① **報到時多發一張「控制面通行證」**：用後端既有的簽章私鑰簽，內含機器編號、客戶編號、用途標記（讓同一把鑰匙簽的其他短效票不能拿來冒充）、效期；② **其餘四條通道每個請求都要帶這張證**：驗簽 → 只認證裡的編號 → **每個請求都查一次資料庫確認這台沒被撤銷、沒被停用**，身分與客戶歸屬從證裡推、不再讀請求內容；③ **路由表改成三級守門**（管理員／代理程式／報到），認不得的等級在啟動時就報錯，不會有端點靜默變成不設防；④ **確認收到／回報結果**：交易內再驗一次撤銷狀態，並比對「這張單子是不是派給這台」，不符回「找不到」；⑤ **領工作**：拿掉「沒帶編號就用指紋跨客戶撈第一筆」的退路；⑥ **報到**：被撤銷的列一律拒絕、不再洗回正常，帶舊編號重新登記必須附「用舊私鑰簽這次請求」的持有證明 |
| **狀態** | ✅ 已修（CM-2052，套件 commit `c4acc687`＋`9c049540`）。驗證：套件 148 測試＋新增 12 案，DEV 實跑 12 項 |

## M02-1 🟠 拿到檔案編號就能下載別人的檔案 {#m02-1}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟠 高（總表 #3；STRIDE：資料外洩、竄改資料） |
| **在哪裡** | 檔案上傳下載模組：下載功能 |
| **攻擊面位置** | 已登入使用者可打的檔案 API。任何最低權限的帳號都構成攻擊面——**不需要是任何專案的成員** |
| **信任邊界位置** | **使用者 ↔ 檔案服務**。檔案服務收到「下載編號 X」時，應該在交出檔案前先問「X 掛在哪個專案，呼叫者在那裡有沒有權限」；當時只檢查了「有登入」。跨客戶那一段後來由資料庫隔離擋住，但**同一客戶內跨專案、跨部門**這段邊界仍是空的 |
| **元件端點位置** | `jedi_file_upload/api/routing.py`：`GET /file/download/{uid}`（`UploadFileDownloadRoute`）→ `upload_file_service.get_upload_file(uid)`，沒有歸屬檢查 |
| **駭客怎麼打** | ① 公司內任一有帳號的員工；② 在自己看得到的頁面記下檔案編號的樣子；③ 把下載網址的編號換成別人專案的檔案編號；④ 系統只驗登入，檔案就下來了 |
| **得手什麼** | 別部門、別專案的稽核證據檔案 |
| **修正的做法** | 四個出口共用一套：① **套件不懂業務，改成「問登記表」**——新增一個介面，各業務模組（專案證據、SSP 文件、意見回饋附件…）各自登記「這個檔是不是我的、這個人能不能碰」的答案；② **三條定死規則**：沒有任何模組認領的孤兒檔**一律拒絕**（不是放行），認領者說不行就不行，拒絕一律回「找不到」而不是「無權限」（後者等於告訴對方這個編號有效、可以逐一試）；③ **讀與寫分開問**：下載／預覽／換發問「能不能看」，刪除問「能不能刪」——同專案看得到證據的人遠多於刪得掉的人；④ 下載路由掛守門，底層取檔那一層也掛一道（雙保險）；⑤ 留一條平台管理員例外口給大批歷史孤兒檔，免得維運只能改資料庫 |
| **狀態** | ✅ 已修（CM-2033，套件 commit `07563773`）。驗證：四種身分 × 六種檔案的矩陣對 DEV 實跑 |

## M03-2 🟠 「測試連線」按鈕沒檢查權限，按一下帳密送到自己機器 {#m03-2}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟠 高（總表 #4；STRIDE：資料外洩、權限提升） |
| **在哪裡** | 弱點檢測模組：掃描工具設定頁的「測試連線」按鈕 |
| **攻擊面位置** | 已登入使用者可打的檢測設定 API。任何能登入的帳號都構成攻擊面，不必是管理員、不必持有任何掃描設定權限 |
| **信任邊界位置** | **使用者 ↔ 檢測服務**，以及**後端 ↔ 客戶內網主機**。按鈕會把存著的帳密解密後往外送，檢查「你能不能動這組設定」應該在解密之前守住；當時同檔的新增、修改、重置都掛了權限，唯獨這支漏掛 |
| **元件端點位置** | `jedi_detection/api/routes/detection_tool_route.py`：`POST /api/1.0/detection-tools/configs/{uid}/test-connection`（`TenantDetectionToolConfigTestConnectionRoute.post`）→ `detection_tool_service.py` |
| **駭客怎麼打** | ① 公司內任一有帳號的員工；② 打開弱點檢測設定，挑一組現成的掃描工具設定；③ 在測試連線的請求裡把目標主機填成自己的機器，按下「測試連線」；④ 系統沒問他有沒有改設定的權限，就把存著的 SSH／Windows 遠端管理密碼與三套掃描工具的權杖解密，連向他指定的機器 |
| **得手什麼** | 整家公司連主機用的維運帳密與掃描工具權杖 |
| **修正的做法** | ① 在「測試連線」補上與同檔新增／修改／重置同一顆的能力點守門（`@capability_required(plugin_update_capability)`），沒有改設定權限的人按不下去；② 旁邊的「查引用次數」確認是純計數、不解密、不對外連線，刻意維持不掛；③ 目標主機位址不設白名單（決策者裁定由客戶自管），連到哪裡的收斂另見 M03-6 |
| **狀態** | ✅ 已修（CM-2042，套件 commit `5a9baf2b`） |

## M05-1 🟠 送空白請求就讀走全公司每一版問卷答案與審核意見 {#m05-1}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟠 高（總表 #5；STRIDE：資料外洩） |
| **在哪裡** | 問卷模組：「查填答歷史明細」功能 |
| **攻擊面位置** | 已登入使用者可打的問卷 API。同一家公司內任一帳號即可，不必是任何專案的成員 |
| **信任邊界位置** | **使用者 ↔ 問卷服務**。服務應該先問「這份問卷屬於哪個專案、你是不是參與者」再交資料；當時這支服務連判斷歸屬的零件都沒有，查詢條件又全部可以留空，留空時底層不過濾 |
| **元件端點位置** | `jedi_survey/api/routes/question_answer_history_detail_route.py`：`POST /api/1.0/project-survey/history-details`（`QuestionAnswerHistoryDetailsRoute`）→ `question_answer_history_detail_service.py` |
| **駭客怎麼打** | ① 公司內任一有帳號的員工；② 對「查填答歷史明細」送一個什麼條件都不填的請求；③ 系統不要求任何編號、也不檢查歸屬，底層看到條件全空就不過濾；④ 全公司每一份問卷的每一版答案與稽核審核意見整包回來 |
| **得手什麼** | 全公司問卷的歷次答案與稽核人員的判斷 |
| **修正的做法** | ① 歷史編號與問卷編號改成「二擇一必填」，空白請求直接回 400；② 由歷史紀錄反查它屬於哪份問卷，兩者同時帶就必須一致，查完把問卷編號強制寫回查詢條件，不讓另帶條件繞出去；③ 讀取門檻定在「該專案任一參與者」（`assert_task_survey_reader`），寫入仍維持被指派人或管理者；④ 主專案補上反查需要的依賴（`4a5650c5d`）；⑤ 後續把「任務屬於哪個專案」改由主專案注入全系統唯一判斷，修正還沒指派人的任務問卷被誤擋（CM-2114） |
| **狀態** | ✅ 已修（CM-2034，套件 `83fc34e5`＋BE `4a5650c5d`；CM-2114 BE `49cbe0b7b`／套件 `034eef0e`） |

## M05-2 🟠 拿一個問卷編號就讀走別部門的答案與分數 {#m05-2}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟠 高（總表 #6；STRIDE：資料外洩） |
| **在哪裡** | 問卷模組：讀取某份問卷答案 |
| **攻擊面位置** | 已登入使用者可打的問卷 API。任一帳號即可，問卷編號從 M05-3 那支清單就拿得到 |
| **信任邊界位置** | **使用者 ↔ 問卷服務**。同一支服務三支寫入方法都會問「你是不是這個專案的人」，唯獨讀取這支取出問卷後直接回傳，邊界在讀這一側沒有守衛 |
| **元件端點位置** | `jedi_survey/api/routes/question_answer_route.py`：`GET /api/1.0/project-survey/answers/{uid}`（`TaskSurveysAnswersRoute.get`）→ `question_answer_service.get_answer_list_by_task_survey_uid` |
| **駭客怎麼打** | ① 公司內任一有帳號的員工；② 先用空白請求打「列出任務問卷」拿到全公司問卷編號；③ 把別部門問卷的編號填進讀答案的網址；④ 系統取出問卷後不問歸屬，作答內容、分數與審核意見整份回來 |
| **得手什麼** | 別部門問卷的完整作答與審查結果 |
| **修正的做法** | ① 取出問卷之後補一道「呼叫者是不是該問卷所屬專案的參與者」，用的是同服務寫入端已在用的反查鏈；② 反查不到專案時一律擋（往嚴的方向倒）；③ 任務歸屬判斷後來改由主專案注入的唯一判斷（CM-2114），未指派任務的問卷不再被誤擋 |
| **狀態** | ✅ 已修（CM-2034，套件 `83fc34e5`；CM-2114 `49cbe0b7b`） |

## M06-1 🟠 一個任務編號讀走別人的稽核證明清單 {#m06-1}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟠 高（總表 #7；STRIDE：資料外洩） |
| **在哪裡** | 稽核流程模組：讀取某任務底下的佐證（稽核證明）清單 |
| **攻擊面位置** | 已登入使用者可打的佐證 API。任一帳號即可，只要知道一個任務執行編號 |
| **信任邊界位置** | **使用者 ↔ 流程服務**。新增、更新、刪除佐證時服務都會先確認「你是這個專案的人」；讀取這一側沒有，等於讀的那扇門沒裝鎖 |
| **元件端點位置** | `api/flow_engine/routes/job_evidence_route.py`：`GET /api/1.0/job-evidences?job_execution_uid=…`（`JobEvidencesRoute.get`）→ `app/flow_engine/service/job_evidence_service.py` 的 `get_job_evidences_by_job_execution_uid` |
| **駭客怎麼打** | ① 任一登入帳號；② 在自己看得到的頁面記下任務編號的樣子，換成別人專案的；③ 呼叫佐證清單查詢，系統只驗登入；④ 回來的清單含檔名、雲端硬碟連結、上傳者帳號與**檔案編號**，接著拿檔案編號去打下載（M02-1）就拿到檔案本體 |
| **得手什麼** | 別人專案的稽核證明清單，以及通往檔案本體的編號 |
| **修正的做法** | ① 兩支讀取方法補上 `assert_project_participant`，走的是同檔新增／更新／刪除已在用的同一條通道，沒有另立新守門；② 後續任務歸屬改讀輪次鏈（CM-2119），還沒指派人的任務不再被誤擋成 403 |
| **狀態** | ✅ 已修（CM-2035，BE commit `e2c32258d`；CM-2119 `e8445ae16`）。模組頁原記 CM-2059，實際修正 commit 標的是 CM-2035 |

## M06-2 🟠 讀走別人專案的稽核討論並冒充同事塞留言 {#m06-2}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟠 高（總表 #8；STRIDE：資料外洩、竄改資料、冒充身分） |
| **在哪裡** | 稽核流程模組：流程討論留言的讀取與發送 |
| **攻擊面位置** | 已登入使用者可打的流程留言 API。任一帳號即可，這是同組五條裡唯一「能寫」的 |
| **信任邊界位置** | **使用者 ↔ 流程服務**。路由層直接讀寫流程變數、自己組 JSON 再寫回，中間沒有任何服務層的歸屬檢查，也沒有長度上限 |
| **元件端點位置** | `api/flow_engine/routes/flow_engine_route.py`：`GET／PUT /api/1.0/flow-engine/process/comments/{id}`（`WorkflowExecutionCommentRoute`，修正後改由 `app/flow_engine/service/workflow_comment_app_service.py` 承接；此端點已於 CM-2373 整條拔除） |
| **駭客怎麼打** | ① 任一登入帳號；② 用別人專案的流程編號讀走整串稽核討論；③ 往同一串送一則留言，例如「請點這個連結補件」；④ 系統把留言即時推播給該專案全體成員，看起來就像同事發的 |
| **得手什麼** | 別人專案的稽核討論內容，外加一個冒充同事發話的管道 |
| **修正的做法** | ① 新開 app service 承接留言讀寫，進場先解流程實例再問 `assert_project_participant`；② 留言補上限：非空、單則 2000 字、單一流程 500 則（留言存在 JSON 陣列裡，沒有資料庫欄位長度可擋）；③ 之後查出整條流程討論前後端都沒人在用，決策者裁定拔除：留言 API、通知頻道、前端元件一併刪掉（CM-2373） |
| **狀態** | ✅ 已修（CM-2035，BE commit `e2c32258d`）；端點後續整條拔除（CM-2373，BE `99beceb26`） |

## M07-1 🟠 舊版「預覽證據檔」填一個雲端檔案編號就把檔案抓回來 {#m07-1}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟠 高（總表 #9；STRIDE：資料外洩） |
| **在哪裡** | 證據自動分類模組（舊線）：「預覽證據檔」入口 |
| **攻擊面位置** | 已登入使用者可打的分類 API。任一能登入的帳號即可，不必是任何專案的成員 |
| **信任邊界位置** | **使用者 ↔ 分類服務 ↔ Google 雲端硬碟**。服務拿公司授權的 Google 帳號去抓檔，應先確認檔案屬於這次分類、呼叫者是成員；當時直接信任網址上的雲端檔案編號，而授權申請的是最寬的那一檔權限 |
| **元件端點位置** | `jedi_evidence_classification/api/routing.py`（已拆）：`GET /api/1.0/classification-run/{run_folder_id}/file/{file_drive_id}/preview` → `evidence_classification_service.py` |
| **駭客怎麼打** | ① 任一登入帳號；② 在預覽網址填任何一個雲端硬碟檔案編號；③ 系統用公司授權的 Google 帳號去抓，不檢查這個檔是不是證據資料夾裡的；④ 那個帳號摸得到的任何檔案（含別人分享給他的人事檔、合約）都抓得回來 |
| **得手什麼** | 授權帳號所能觸及的全部雲端檔案 |
| **修正的做法** | 拆除了什麼：① 舊線以雲端資料夾編號定址的九支網址整組拆掉（觸發、工作清單、狀態、封存、預覽、兩份報表、摘要）；② 服務層只留「匯入標準答案」一支，其餘觸發、背景工作、雲端讀寫、報表全部刪除（1,247 行剩 70 行）；③ 網址契約測試新增「已退役清單」，誰把這九支掛回來測試就變紅；④ 前端舊線入口同步退場 |
| **狀態** | 🗑️ 已拆除（隨舊線退場，1.21.0 出貨；CM-2222，套件 `87fd5439`／BE `ccc5795f6`／前端 `2537ea5`） |

## M02-5 🟠 查設定就拿到雲端儲存帳密明文，可繞過系統直連 {#m02-5}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟠 高（總表 #14；STRIDE：資料外洩、竄改資料） |
| **在哪裡** | 檔案上傳模組依賴的系統設定：查詢雲端檔案儲存設定 |
| **攻擊面位置** | 已登入使用者可打的系統設定讀取 API。任一登入帳號即可 |
| **信任邊界位置** | **後端 ↔ 瀏覽器**。儲存空間的存取密鑰應該只留在後端給上傳服務用，不該跨出到前端；當時讀設定的回應原樣帶著密鑰明文 |
| **元件端點位置** | `jedi_system_core/api/routes/system_config_route.py`：`GET /api/1.0/system/configs/{group}`、`GET /api/1.0/system/config/{uid}`；主專案 `api/system_config/routes/system_config_route.py`：`GET /api/1.0/system/config/{group}/{key}`；處理在 `app/system_config/service/guarded_system_config_service.py` |
| **駭客怎麼打** | ① 任一登入帳號；② 呼叫讀 `STORAGE_CONFIG` 這組設定；③ 回應裡直接有物件儲存的存取帳號與密鑰明文；④ 拿這組帳密用標準工具直連儲存空間，列出、下載、覆蓋、刪除檔案，完全不經過我們的系統 |
| **得手什麼** | 檔案儲存空間的管理帳密；落地版一家一套，那組就是那家的全部檔案 |
| **修正的做法** | ① 先補安全網：存檔時這次沒帶密鑰就沿用原值，前端送回來的遮罩旗標剝掉不落地，避免改個欄位就把密鑰洗成空；② 服務層新增 `_mask_storage`：`STORAGE_CONFIG` 讀出去一律拿掉兩個密鑰欄、換成「有沒有設」的旗標，不靠遮罩名單；③ 上傳服務另開一個能看到真值的實例，只注入上傳服務，對外那支維持遮罩；④ 前端密鑰欄改成遮罩顯示、沒改就不送；⑤ 套件遮罩名單也補上這組（CM-2054，`16a3e41c`）當第二層 |
| **狀態** | ✅ 已修（CM-2063，BE `7c904eb6e`／前端 `756783b`） |

## M22-2 🟠 一個請求就拿到檔案儲存的帳密與寄信、帳號目錄位址 {#m22-2}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟠 高（總表 #14；STRIDE：資料外洩、竄改資料） |
| **在哪裡** | 系統設定模組：讀取系統設定的三個入口 |
| **攻擊面位置** | 已登入使用者可打的系統設定讀取 API。不必是管理員、不必有任何權限、不必知道任何編號 |
| **信任邊界位置** | **使用者 ↔ 系統設定服務**。同檔的寫入都會問能力點，讀取只驗登入；而每個客戶那一列裡裝的是同一把複製來的密鑰，資料庫的客戶隔離擋不住 |
| **元件端點位置** | `jedi_system_core/api/routes/system_config_route.py`：`GET /api/1.0/system/configs/{group}`（`SystemConfigListRoute`）、`GET /api/1.0/system/config/{uid}`（`SystemConfigDetailRoute`）；主專案 `api/system_config/routes/system_config_route.py`：`GET /api/1.0/system/config/{group}/{key}`（`SystemConfigGroupRoute`） |
| **駭客怎麼打** | ① 任一登入帳號；② 對三個讀設定入口任一支送請求；③ 系統只驗登入，整組設定照吐；④ 拿到檔案儲存密鑰，外加寄信伺服器與員工帳號目錄的位址、埠號、登入帳號 |
| **得手什麼** | 檔案儲存鑰匙，以及寄信與帳號目錄的連線資訊 |
| **修正的做法** | 實際修法與報告建議不同：① 沒有在三個讀取入口加權限檢查，改成服務層對**所有人**都不回密鑰真值（`_mask_storage` 換成「有沒有設」的旗標，見 M02-5）；② 遮罩名單補上 `STORAGE_CONFIG` 與兩種密鑰欄名（套件 `16a3e41c`）；③ 寄信與帳號目錄那幾組本來就在遮罩名單內，密碼欄不回；④ 寄信、帳號目錄、登入政策三組的讀取補上總部層級守門（CM-2401）：`GuardedSystemConfigService` 三條讀取路徑比照寫入呼叫 `require_tenant_hq()`，非總部 403；清單查詢把這三組濾掉；安全政策頁讀取同樣補上。⚠️ 其他各客戶自己一份的設定（如儲存位址、桶名）的讀取入口仍只驗登入 |
| **狀態** | ✅ 已修（CM-2063，BE `7c904eb6e`；遮罩 CM-2054 套件 `16a3e41c`；三組共用設定的讀取守門 CM-2401，BE `779837919`、FE `afb45a2`） |

## M09-1 🟠 任一客戶管理員改日誌轉送位址，全客戶活動紀錄送往他的機器 {#m09-1}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟠 高（總表 #19；STRIDE：資料外洩） |
| **在哪裡** | 系統日誌模組：「系統設定 → 日誌轉送設定」頁 |
| **攻擊面位置** | 持有「日誌轉送設定」能力點的客戶管理員。這個能力點發給每一家客戶的管理員，含子公司 |
| **信任邊界位置** | **租戶 ↔ 平台**。轉送設定全系統只有一份，改它等於改所有客戶；應該在寫入時問「你站的層級夠不夠改全站那一份」，當時只問能力點 |
| **元件端點位置** | `jedi_api_log/forwarding/api/routing.py`：`GET／PUT /api/1.0/log-forwarding`、`POST /api/1.0/log-forwarding/test`；寫入守門接在主專案 `core/plugins/api_log.py` 的 `_forwarding_guard` |
| **駭客怎麼打** | ① 任一家客戶（含子公司）的管理員；② 打開日誌轉送設定，填自己的一台伺服器位址；③ 按儲存，系統只確認他有這顆能力點；④ 從此所有客戶的系統活動紀錄持續送往那台機器，沒有人察覺 |
| **得手什麼** | 所有客戶的系統活動紀錄副本 |
| **修正的做法** | ① 新增軸⑦「總部層」判定（`common/authz/tenant_hq.py`），日誌轉送的寫入與測試發送都要先過能力點、再過總部層，讀取不卡；② 首腦驗收後調整順序為能力點在前，讓「沒權限」與「層級不夠」錯誤碼分得出來（`a25933de8`）；③ 「誰算總部」再收緊：SaaS 只有平台管理員，落地版只有平台管理員與安裝精靈建的那家客戶（CM-2202） |
| **狀態** | ✅ 已修（CM-2054，BE `e90d8e3cb`／`a25933de8`；CM-2202 `6cfcb116e`，1.21.0 出貨） |

## M22-1 🟠 一個客戶管理員改掉全公司登入規則，並把帳號目錄指到自己機器 {#m22-1}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟠 高（總表 #22；STRIDE：權限提升、冒充身分） |
| **在哪裡** | 系統設定模組：登入政策、寄信設定、員工帳號目錄（LDAP）設定 |
| **攻擊面位置** | 持有對應設定能力點的客戶管理員。能力點是發給各租戶管理員的，開一個新客戶就多一個人 |
| **信任邊界位置** | **租戶 ↔ 平台**。這三組設定實體只有最上層一份，改了就是改所有人；應在寫入時多問一句「你的層級有沒有資格改全公司那份」，當時只問能力點 |
| **元件端點位置** | 主專案 `api/system_config/routes/security_policy_route.py`：`PUT /api/1.0/system/security-policy`；`PUT /api/1.0/system/config/{group}/{key}`；套件 `jedi_system_core` 的 `PUT／DELETE /api/1.0/system/config/{uid}`、`POST /api/1.0/system/config`；守門在 `core/plugins/system_core.py` 的 `assert_config_capability`、`guarded_system_config_service.py`、`security_policy_app_service.update_policy` |
| **駭客怎麼打** | ① 任一家客戶（含子公司）的管理員；② 改登入政策：關掉雙因子、帳號永不鎖定、登入憑證效期從五分鐘延到一個月；③ 或把員工帳號目錄的位址改成自己的機器；④ 系統只看能力點就存進全站那一份，所有人之後登入都經過他的機器 |
| **得手什麼** | 全公司的登入控制權 |
| **修正的做法** | ① 新增軸⑦ `require_tenant_hq`，名單化「全公司共用一份」的設定（`common/constant/company_wide_config.py`：寄信、第三方登入、登入政策）；② 守門放在服務層，六條寫入路徑與登入政策專屬端點都掛，讀取不卡；③ 子公司管理員改這三組一律 403 `GRC_403066`；④ 後續把「誰算總部」收緊成落地版只認安裝精靈建的客戶、SaaS 只認平台（見 M22-4） |
| **狀態** | ✅ 已修（CM-2054，BE `e90d8e3cb`；CM-2202 `6cfcb116e`） |

## M18-8 🟠 被停權客戶用一個子單位帳號刪掉母公司停權紀錄、整棵樹復權 {#m18-8}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟠 高（總表 #23；STRIDE：權限提升、竄改資料、事後無法追查） |
| **在哪裡** | 授權管理模組：授權檔、授權異動紀錄、停權紀錄三張資料表 |
| **攻擊面位置** | 被停權客戶底下任一子單位帳號，經資料庫直連或任何會寫這三張表的路徑。設計上不暴露，但資料庫是最後一道防線 |
| **信任邊界位置** | **子租戶 ↔ 母租戶**（資料庫隔離規則）。規則應該讓子單位「看得到」上層授權、但「改不到」；當時寫法把租戶路徑拆成陣列比對，方向反了，而且同一條規則連改、刪一起放行 |
| **元件端點位置** | 資料表 `tenant_licenses`、`tenant_license_events`、`tenant_license_suspensions` 的隔離規則；套件 `jedi_license_runtime/migrations/002-license-rls.sql`、`003-tenant-license-suspensions.sql`；主線 `scripts/sql/2026-09-28-fr114-rls-tenant-prefix-unify.sql`、`2026-10-01-cm2369-license-tables-ancestor-read.sql` |
| **駭客怎麼打** | ① 被停權客戶手上的一個子單位帳號；② 刪掉母公司頭上那筆停權紀錄（停權表設計是「有一列就停權中」）；③ 資料庫規則看子單位的路徑包含母公司編號就放行，整棵樹當場復權；④ 順手改掉授權到期日與模組清單，再刪掉授權異動紀錄抹掉痕跡 |
| **得手什麼** | 自己替自己復權、改大授權範圍並湮滅紀錄 |
| **修正的做法** | ① 九張表（含授權三張）的規則從「路徑拆陣列」改成前綴比對 `app_tenant_allowed_for_session`，子單位改、刪不到上層；② 套件 migration 同步改，但已裝機要靠主線 migration 重建（套件檔撞同名會跳過）；③ 新增守衛測試：全庫不准再有拆陣列寫法；④ 出貨後發現子單位因此讀不到上層授權、被判成沒買，1.21.1 再對三張表加一條「祖先可讀」的唯讀規則，改刪仍維持擋 |
| **狀態** | ✅ 已修（CM-2271，BE `6c74ff0e3`／套件 `6e0d0d4b`，1.21.0；讀取開放 CM-2369，BE `3574033a6`／套件 `ba14e128`，1.21.1）。驗證：子讀上層＝1、子改刪上層＝0 等 12 條實庫測試 |

## M20-1 🟠 13 張標有客戶歸屬的表隔離沒生效，看得到別家資料 {#m20-1}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟠 高（總表 #24；STRIDE：資料外洩） |
| **在哪裡** | 客戶資料隔離：13 張標有客戶歸屬欄位的業務資料表 |
| **攻擊面位置** | 任一家客戶的登入使用者，透過任何會讀到這些表的正常功能。不需要特殊操作 |
| **信任邊界位置** | **租戶 ↔ 租戶**（資料庫隔離牆）。客戶歸屬欄位都在，牆卻沒立：3 張規則寫好但開關沒開、1 張規則不完整、9 張連規則都沒有 |
| **元件端點位置** | 資料表 `compliance.projects`、`tenant_drive_integrations`、`job_executions`、`information_systems`、`poams`、`evidence_classification_runs`、`evidence_classification_ground_truth`、`framework_parse_jobs`、`user_roles`、`user_tenants`、`workflow_executions`、`workflow_templates`、`log_forwarding_settings` 的隔離規則；migration 在 `scripts/sql/2026-09-14-fr094-cm17*.sql` |
| **駭客怎麼打** | ① 任一家客戶的使用者；② 照常打開專案清單、待辦、任務執行紀錄；③ 資料庫對這些表沒有隔離，查詢結果不分客戶；④ 畫面上混進別家客戶的專案、任務與稽核資料（當時實測：一把指向不存在客戶的鑰匙查得到 213 筆專案、11,065 筆任務執行紀錄） |
| **得手什麼** | 別家客戶的專案、待辦、任務執行與稽核資料 |
| **修正的做法** | 分組處理：① 只需開開關的兩張（專案、雲端硬碟連動），專案先補「平台管理員」分支再開（CM-1768）；② 先補規則再開的八張，套四條標準規則（CM-1769、CM-1770）；③ 工作流程執行紀錄補查詢規則、拿掉部門條件（CM-1771）；④ 流程範本先回填歸屬再開（CM-1772）；⑤ 日誌轉送設定重新歸類為平台層設定，靠寫入權限收到只給平台管理員（見 M09-1）。重測四個數字全部歸零 |
| **狀態** | ✅ 已修（1.21.0 出貨，12 張已補；BE `0ca61108c`／`197951723`／`b5ca22bd1`／`b502b0b47` 等 FR-094 系列） |

## M02-6 🟠 檔案紀錄表連客戶歸屬欄位都沒有，資料庫擋不住跨客戶 {#m02-6}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟠 高（總表 #25；STRIDE：資料外洩） |
| **在哪裡** | 檔案上傳模組：存放檔案紀錄的那張資料表 |
| **攻擊面位置** | 任一客戶的使用者，透過任何會讀到檔案紀錄的功能（下載、預覽、清單） |
| **信任邊界位置** | **租戶 ↔ 租戶**（資料庫隔離牆）。表上沒有客戶歸屬欄位，牆沒有東西可以依據；原本靠「掛它的表」間接保護，但還沒掛上任何業務的暫存檔就完全沒人擋 |
| **元件端點位置** | 資料表 `upload_files`；套件 `jedi_file_upload/migrations/004-upload-files-tenant-owner-rls.sql`、`infra/models/upload_file.py`；主專案 `app/upload_file/service/managed_file_upload_service.py` |
| **駭客怎麼打** | ① 任一客戶的帳號；② 拿到別家的檔案編號（例如從別的回應夾帶出來）；③ 打檔案相關功能，資料庫不分客戶照查；④ 在應用層也沒擋的那段時間，檔案就跨客戶下來了 |
| **得手什麼** | 跨客戶的檔案紀錄與檔案本體 |
| **修正的做法** | ① 加 `tenant_id`（必填）與 `owner_user_id` 欄位，回填既有資料後收成必填；② 開隔離並套四條標準規則；③ 寫入時由共用掛鉤依當下身分自動帶租戶；④ 背景上傳與系統公版上傳原本沒有租戶身分，各包一層具名租戶身分（背景用工作的租戶、公版釘最上層），否則必填欄會讓背景上傳整條掛掉；⑤ 加守衛測試。同客戶內跨專案另由 M02-1 的歸屬檢查擋 |
| **狀態** | ✅ 已修（CM-1850，套件 `651e33c2`／BE `a48b41cc0`；2,718 筆全數補上客戶歸屬） |

## M04-2 🟠 沒有登入身分的路徑被當成最高權限，一次看到全部客戶 {#m04-2}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟠 高（總表 #27；STRIDE：權限提升、資料外洩） |
| **在哪裡** | 共用基礎：每次開資料庫交易前設定「現在是誰在查」的那段 |
| **攻擊面位置** | 不需要登入身分的入口：登入流程本身、雲端硬碟通知回呼、任何漏掛認證的路由或沒還原身分的背景執行緒。部分入口設計上就對外暴露 |
| **信任邊界位置** | **應用程式 ↔ 資料庫**。資料庫的隔離規則靠應用程式告訴它「誰在查」；當時查不到身分就自動設成最高權限，等於邊界在「缺值」時整個打開 |
| **元件端點位置** | `jedi_common/session/database/db.py` 的 `session_scope()`；`jedi_common/session/auth/system_context.py`、`tenant_context.py`；主專案 `core/scheduler.py`、雲端硬碟 webhook／OAuth 回呼服務 |
| **駭客怎麼打** | ① 從不需要登入身分的入口進來，例如雲端硬碟通知回呼；② 這條路徑沒有使用者身分；③ 系統把「沒身分」當成最高權限的系統管理員，對資料庫宣告「看全部」；④ 那段程式的每一次查詢與寫入都橫跨所有客戶，任何小錯都從影響一家放大成影響全部 |
| **得手什麼** | 繞過客戶隔離、橫跨全部客戶的讀寫面 |
| **修正的做法** | ① 新增具名系統身分 `system_context(name)`，排程要跨客戶必須明說並留下具名警告（CM-1767）；② 拿掉「租戶欄位空值＝超級管理員」，簽章短票改填真實身分（CM-1787）；③ 完全沒有身分時改成什麼都看不到，並留一行警告指出是哪裡沒帶身分（CM-1788）；④ 雲端硬碟回呼等機器端點改用具名「租戶身分」（只看得到該租戶）；⑤ 九支排程全部接上具名身分，守衛測試涵蓋 |
| **狀態** | ✅ 已修（FR-094：套件 `2bd93918`／`9a0b4893`／`fc1cadb9`，BE `3af065133`／`1125ceee1`／`41e367f85`） |

## M13-4 🟠 在個人資料頁按儲存，把自己改成管理員 {#m13-4}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟠 高（總表 #30；STRIDE：權限提升、竄改資料） |
| **在哪裡** | 登入與權限模組：「個人資料」頁的儲存 |
| **攻擊面位置** | 任一已登入的一般員工。個人資料頁人人都能開，這是門檻最低的一條升權路 |
| **信任邊界位置** | **使用者 ↔ 帳號服務**。自助頁只該改暱稱、電話這類欄位；當時收件時接受任何多送的欄位，角色、客戶、部門、狀態都照單收下 |
| **元件端點位置** | `jedi_iam/api/routes/user_route.py`：`PUT /api/1.0/user-profile/{uid}`（`UserProfileRoute.put`）；`jedi_iam/api/serializers/user.py` |
| **駭客怎麼打** | ① 任一般員工；② 打開個人資料頁按儲存，攔下請求；③ 在內容裡多塞一個角色欄位，填管理員角色的編號；④ 系統直接拿它整批覆蓋自己的角色關聯，下次登入就是管理員（開發環境實測確認） |
| **得手什麼** | 把自己提權為管理員，或清空自己的客戶／部門歸屬 |
| **修正的做法** | ① 新增 `UserProfileRequest`，白名單只收自助頁真的會送的欄位，其他一律丟掉；② 就算是已知欄位也不讓前端決定：角色、客戶、部門、狀態四項一律從資料庫查現況重建後才交給更新；③ 管理端 `PUT /api/1.0/user/{uid}` 照舊走 `user.update` 能力點，不受影響；④ 新增四個測試，經突變測試證明斷言有效 |
| **狀態** | ✅ 已修（CM-1576，套件 commit `7cf3cf2b`） |

## M20-2 🟠 「我的任務」等四個畫面用最高權限帳號查，看到別家待辦 {#m20-2}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟠 高（總表 #36；STRIDE：資料外洩） |
| **在哪裡** | 客戶資料隔離：「我的任務」清單、權限總覽，以及兩份選單清單共四個資料庫畫面（view） |
| **攻擊面位置** | 任一登入的人，打開「我的任務」頁就會撞到，不需要任何操作 |
| **信任邊界位置** | **應用程式 ↔ 資料庫**。資料庫畫面預設用「建立者」的身分查底層表，建立者是最高權限帳號，隔離牆對它們不存在 |
| **元件端點位置** | 資料庫畫面 `public.vw_user_job_queue`、`public.v_user_capabilities`、`v_user_routes`、`v_role_routes`；使用端 `GET /api/1.0/flow-engine/task/queue`（`UserJobQueueRoute`）；定義在 `scripts/sql/view/vw_user_job_queue.sql` |
| **駭客怎麼打** | ① 任一登入帳號；② 打開「我的任務」；③ 底下的畫面用最高權限身分查；④ 清單列出別家客戶的待辦（應該 0 筆，當時看到 39 筆），儀表板的待辦數量卡片也跟著錯 |
| **得手什麼** | 別家客戶的待辦與權限配置 |
| **修正的做法** | ① 兩支在用的畫面加上「用查詢者自己的身分執行」（`security_invoker = true`），並寫進定義正本；② 兩支查不到任何地方在用的選單畫面直接刪除；③ 同一批把工作流程執行紀錄表補查詢規則並開隔離 |
| **狀態** | ✅ 已修（CM-1771，BE commit `b502b0b47`；2026-09-16 複查確認） |

## M10-4 🟠 換成別人的專案編號看稽核判定、風險評等與改善計畫 {#m10-4}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟠 高（總表 #57；STRIDE：資料外洩） |
| **在哪裡** | 任務與成員管理（同 M12-1）：稽核輪次相關的十支讀取入口 |
| **攻擊面位置** | 已登入使用者可打的稽核輪次 API。同公司裡不是這個專案的人即可；團隊名單那支跨客戶也打得到 |
| **信任邊界位置** | **使用者 ↔ 稽核服務**。服務應先確認「你是這個專案的參與者」；當時守門參數設計成「有傳才檢查」，而路由從沒傳 |
| **元件端點位置** | 主專案 `api/project/routes/audit_round_route.py`：`GET /api/1.0/projects/{project_uid}/audit-rounds`、`GET /api/1.0/audit-round/{round_uid}`、`GET /api/1.0/ap/{ap_uid}/parties`、`GET /api/1.0/audit-round/{round_uid}/ar/findings`、`/ar/risks`、`/poam-items`、`/poam-item/{item_uid}`；`GET /api/1.0/grc/project/{project_uid}/assessment-plans/menu`、`/ap/{ap_uid}/dashboard`；套件 `jedi_compliance_audit` 的 `audit_round_app_service`、`assessment_result_app_service`、`poam_app_service` |
| **駭客怎麼打** | ① 同公司裡不是這個專案的人（資訊不足，僅依總表描述展開）；② 在網址上把專案或輪次編號換成別人專案的；③ 十支入口都沒確認成員；④ 看到稽核輪次、判定、風險評等、改善計畫與負責人姓名，團隊名單那支因為底層表沒客戶隔離，連別家客戶的都拿得到 |
| **得手什麼** | 別人專案的稽核判定、風險與改善計畫，團隊名單甚至跨客戶 |
| **修正的做法** | ① 八支讀取都補「任一參與者」守門，路由改傳目前使用者編號啟用；② 摘要報告類順手補齊（見 M10-3）；③ 首腦退回後補：守門參數改成必填，以後新呼叫端忘了傳會直接報錯、不會安靜放行；④ 團隊名單改成「查不到輪次就回找不到」，堵住跨客戶時整段檢查被跳過；⑤ 稽核計畫選單與儀表板新開 `project_read_guard.py` 補成員檢查與「輪次屬於網址專案」比對 |
| **狀態** | ✅ 已修（1.21.0 出貨；CM-2037 BE `cd9223592`／套件 `f513ae88`；CM-2172 BE `0808b3c8a`／套件 `7df95248`） |

## M12-1 🟠 十支讀取入口換個網址看別人專案的稽核判定 {#m12-1}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟠 高（總表 #57；STRIDE：資料外洩） |
| **在哪裡** | 稽核輪次管理模組：稽核輪次、稽核計畫關係人、稽核發現、風險、改善計畫的讀取入口 |
| **攻擊面位置** | 已登入使用者可打的稽核輪次 API。同公司非成員即可；團隊名單（含 Email、電話）那支跨客戶也打得到 |
| **信任邊界位置** | **使用者 ↔ 稽核服務**，以及**租戶 ↔ 租戶**。團隊名單原寫法是「查得到輪次才檢查成員」，別家輪次被資料庫藏起來時查回空，檢查整段被跳過 |
| **元件端點位置** | 同 M10-4；團隊名單 `GET /api/1.0/ap/{ap_uid}/parties` → `app/flow_control/service/assessment_plan_app_service.py` 的 `list_ap_parties`（改走 `resolve_ap_and_check_participant`） |
| **駭客怎麼打** | ① 同公司裡不是這個專案的人；② 從自己看得到的頁面記下網址樣子，換成別人的專案、輪次或稽核計畫編號；③ 十支入口都不問成員；④ 讀到稽核判定、風險評等、改善計畫；別家客戶的團隊名單也因檢查被跳過而照樣回來 |
| **得手什麼** | 別人專案的稽核判定內容與團隊聯絡資訊 |
| **修正的做法** | 同 M10-4：① 套件六支讀取與主專案路由一起補參與者守門；② 守門參數改必填，套件內部漏傳的三處同步補上；③ 團隊名單：稽核計畫不存在回 404、查不到輪次回 404、非成員 403；④ 選單與儀表板補成員檢查，並擋「A 專案網址塞 B 專案輪次」 |
| **狀態** | ✅ 已修（1.21.0 出貨；CM-2037 `cd9223592`／`b047d16e3`／套件 `f513ae88`；CM-2172 `0808b3c8a`／套件 `7df95248`）。驗證：成員 200、同租戶非成員 403、跨租戶 404 |

## M11-2 🟠 用 Excel 蓋掉既有範本，從頭到尾不問你是誰 {#m11-2}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟠 高（總表 #146；STRIDE：竄改資料） |
| **在哪裡** | 合規文件核心：合規資源庫用 Excel「蓋掉一份既有範本」 |
| **攻擊面位置** | 已登入使用者可打的範本匯入 API。任一登入帳號，連唯讀的稽核員都算 |
| **信任邊界位置** | **租戶 ↔ 租戶**，以及**客戶 ↔ 原廠公版**。範本內容放在沒有客戶欄位、沒有資料庫隔離的表裡，歸屬只記在一張連結表上，只能在程式裡擋；當時上傳與確認兩步都沒問歸屬也沒問修改權限 |
| **元件端點位置** | 主專案 `api/oscal/__init__.py`：`POST /api/1.0/ssp-excel-imports/parse`、`POST /api/1.0/ssp-excel-import/{parse_uid}/confirm` → `app/oscal/service/ssp_excel_import_app_service.py` 的 `_confirm_update_module_frame`；守門在 `app/oscal/service/resource_library_overwrite_guard.py` |
| **駭客怎麼打** | ① 任一登入帳號；② 拿一個範本編號（別家公司或原廠公版的）；③ 上傳自己的 Excel 選「覆蓋既有範本」並確認；④ 系統只查範本存不存在就整份清空重寫，之後每個用這份範本開專案的客戶都複製到被改過的內容 |
| **得手什麼** | 別家公司或原廠公版範本被整份覆蓋 |
| **修正的做法** | ① 新增共用 `assert_resource_tenant_writable`：資源不是自己公司的就拒絕，平台管理員豁免（CM-2040）；② 新檔 `resource_library_overwrite_guard.py`：覆蓋要「範本修改權限」＋歸屬兩道；③ 上傳端與確認端都跑同一支守門，確認時不信上傳當下的判定；④ 錯誤碼全部沿用既有的，避免鏡像碼表分歧 |
| **狀態** | ✅ 已修（FR-114 CM-2178，BE `abf1cf8a4`；前半 CM-2040 `60015232f`，1.21.0 出貨） |

## M11-3 🟠 Word 版覆蓋既有範本只查存不存在 {#m11-3}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟠 高（總表 #147；STRIDE：竄改資料） |
| **在哪裡** | 合規文件核心：匯入 Word 的「蓋掉既有範本」去向 |
| **攻擊面位置** | 已登入使用者可打的 Word 匯入 API。任一登入帳號 |
| **信任邊界位置** | **租戶 ↔ 租戶／原廠公版**。匯入 Word 三個去向裡，寫進專案要負責人、建新範本要建立權限，唯獨覆蓋只查存在；確認步驟還允許臨時換一個要覆寫的目標 |
| **元件端點位置** | 主專案 `api/oscal/__init__.py`：`POST /api/1.0/ssp-docx-imports/parse`、`GET /api/1.0/ssp-docx-import/{parse_uid}`、`POST /api/1.0/ssp-docx-import/{parse_uid}/confirm` → `app/oscal/service/ssp_docx_import_app_service.py` 的 `confirm_import` |
| **駭客怎麼打** | ① 任一登入帳號；② 上傳 Word，目標選別家或原廠的範本；③ 預覽會先回傳目標範本現有的內容（上傳沒擋等於讀取漏洞）；④ 確認時再把目標換成另一份，系統照蓋 |
| **得手什麼** | 別家公司或原廠公版範本被整份覆蓋，順帶先讀到它的內容 |
| **修正的做法** | ① 上傳端同 Excel 跑覆蓋守門（修改權限＋歸屬）；② 確認端：解析單已綁目標時，送來的目標編號不同就 403，不准確認時換目標；③ 解析單沒綁目標（「用 Word 建新範本」流程）時，只有建立權限的人目標必須是本人剛建的空範本，已有內容就 403，不讓只有建立權的人改寫舊範本 |
| **狀態** | ✅ 已修（FR-114 CM-2178，BE `abf1cf8a4`，1.21.0 出貨） |

## M11-1 🟠 知道計畫編號就把整份系統安全計畫下載成 Excel {#m11-1}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟠 高（總表 #148；STRIDE：資料外洩） |
| **在哪裡** | 合規文件核心：專案系統安全計畫（SSP）的「下載 Excel」 |
| **攻擊面位置** | 已登入使用者可打的範本下載 API。角色帶「看資源庫」即可，不必是那個專案的成員；跨客戶也打得到 |
| **信任邊界位置** | **租戶 ↔ 租戶**。計畫內容存在沒有客戶欄位的表，資料庫擋不到；隔壁「匯出」那支早就檢查參與者，這支沒有 |
| **元件端點位置** | 主專案 `api/module_frame/routes/ssp_import_template_route.py`：`GET /api/1.0/ssp/{ssp_uid}/excel-template?mode=blank或filled`（`SspScopedImportTemplateResource`）→ `app/module_frame/service/ssp_import_template_app_service.py` 的 `generate_for_ssp` |
| **駭客怎麼打** | ① 任一帶「看資源庫」權限的帳號；② 從別的功能回應裡拿到一份系統安全計畫編號；③ 填進下載網址；④ 一個請求就拿到九張工作表：人員姓名、email、電話，設備主機名、IP、MAC，每條控制項的實作說明 |
| **得手什麼** | 另一家公司的整份稽核底稿 |
| **修正的做法** | ① 服務第一行 `require_participant(ssp_uid)`，照隔壁匯出那支的做法；② 權限檢查元件沒注入時直接 403，不照舊寫法「有注入才檢查」，避免接線漏掉就靜默放行；③ DI 補接 `ssp_permission_checker`；④ 範本版下載維持靠範本表的資料庫隔離，不自創規則 |
| **狀態** | ✅ 已修（FR-114 CM-2188，BE `82618a297`，1.21.0 出貨）。驗證：非成員 403、別家客戶 404 |

## M24-2 🟠 把別家專案重建到自己的雲端硬碟、還能摻假證據 {#m24-2}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟠 高（總表 #200；STRIDE：竄改資料、資料外洩） |
| **在哪裡** | 主系統雲端硬碟整合：「重建專案資料夾」 |
| **攻擊面位置** | 已登入使用者可打的雲端硬碟管理 API。任一家客戶的任一登入帳號 |
| **信任邊界位置** | **租戶 ↔ 租戶**，以及**請求 ↔ 背景工作**。網址上的專案編號原封不動排進背景工作，背景用系統身分查專案、不看公司，等於把使用者給的編號帶進了最高權限的地方 |
| **元件端點位置** | 主專案 `api/cloud_integration/routes/google_drive_sync_route.py`：`POST /api/1.0/integrations/google-drive/projects/{project_uid}/init-folders`（`DriveSyncProjectInitFoldersRoute`）→ `drive_sync_admin_service.trigger_init_project_folders`；背景 `project_tree_loader.py`、`handlers/import_drive_file_handler.py` |
| **駭客怎麼打** | ① 客戶 A 的任一帳號；② 知道客戶 B 的專案編號，呼叫「重建專案資料夾」；③ 背景以系統身分把 B 的整個專案樹建進 A 的雲端硬碟；④ A 往那些資料夾丟檔案，同步時被寫成 B 公司任務的證據（開發環境實打成功） |
| **得手什麼** | 偷看並摻假別家客戶的稽核證據 |
| **修正的做法** | ① 手動入口排工作前先以呼叫者身分查專案（查不到 404），再確認是該專案管理者（否則 403）；② 背景第二層：載入專案樹時比對專案公司與工作公司，不同就以不可重試的錯誤終止，不建任何資料夾；③ 匯入雲端檔案寫證據前反查任務所屬專案並比對公司，查不到或不同即拒絕，元件沒注入時 fail-closed；④ 盤點既有 25,409 筆對應，確認新檢查不會擋到現有資料 |
| **狀態** | ✅ 已修（CM-2201，BE `73f5f90af`，1.21.0 出貨）。驗證：同公司非成員 403、別家專案 404 |

## M22-4 🟠 別家客戶的「總部」管理員改得動全站唯一的寄信與帳號目錄設定 {#m22-4}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟠 高（總表 #201；STRIDE：權限提升） |
| **在哪裡** | 系統設定模組：全站只有一份的寄信、員工帳號目錄、登入政策、日誌轉送設定 |
| **攻擊面位置** | 另一家不相干客戶的總部管理員（持有對應能力點） |
| **信任邊界位置** | **租戶 ↔ 平台**。M22-1 的修法用「可見路徑深度＝2」判總部，但最上層底下第二層不只一家，任何一家的管理員都算總部 |
| **元件端點位置** | 主專案 `common/authz/tenant_hq.py`（判定）、`infra/setup/setup_state_reader.py` 的 `primary_customer_tenant_id`；呼叫點 `core/plugins/system_core.py`、`guarded_system_config_service.py`、`security_policy_app_service.py`、`core/plugins/api_log.py` |
| **駭客怎麼打** | ① 客戶 B 的總部管理員；② 打開寄信或帳號目錄設定；③ 系統看他的路徑深度是 2，判定他是總部；④ 他改掉的是客戶 A、甚至原廠平台本身在用的那一份 |
| **得手什麼** | 改掉客戶 A 與原廠平台的寄信伺服器與帳號目錄 |
| **修正的做法** | 照決策者裁定，只改判定、四個呼叫點不動：① 平台管理員一律放行（現場維修）；② SaaS 部署（含預設）除平台外一律拒絕；③ 落地版只有「安裝精靈建立的那家客戶」（最上層下第一個子租戶）算總部，用不受隔離的唯讀查詢取，不靠可被編輯的描述欄；④ 無身分、查不到一律拒絕；⑤ 新增 10 條測試，突變改回舊判定 4 紅 |
| **狀態** | ✅ 已修（CM-2202，BE `6cfcb116e`，1.21.0 出貨） |

## M03-14 🟡 只能看的成員能完成別人的任務，還能對正式機器發掃描 {#m03-14}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #37；STRIDE：權限提升、竄改資料、事後無法追查） |
| **在哪裡** | 弱點檢測模組：八支會改狀態的功能（開始掃描、取消、重跑、立即開始、刪除紀錄等） |
| **攻擊面位置** | 該專案的任一成員，包含產品定義上「只能看、沒有待辦」的唯讀角色 |
| **信任邊界位置** | **專案內角色 ↔ 任務操作**。守門只問「是不是這個專案的人」，該問的是「是不是這個任務的負責人或專案管理者」 |
| **元件端點位置** | `jedi_detection/api/routes/detection_tool_route.py`：`POST /api/1.0/detection-tools/jobs/{job_uid}/execute`、`/cancel`，`POST /api/1.0/detection-tools/execution-groups/{group_uid}/assignments/{assignment_uid}/rerun`、`/cancel`、`/start-now`，`POST …/execution-groups/{group_uid}/cancel`，`DELETE /api/1.0/detection-tools/execution-groups/{group_uid}`、`DELETE /api/1.0/detection-tools/executions/{execution_uid}` → `detection_orchestration_service.py` |
| **駭客怎麼打** | ① 一個被加進專案、角色只是唯讀的成員；② 打開別人負責的弱點掃描任務；③ 直接呼叫「開始掃描」或「重跑」，守門看他是專案的人就放行；④ 對客戶的正式機器發動帶帳密的掃描，或反覆取消重派、刪掉失敗紀錄 |
| **得手什麼** | 操控掃描動作並竄改稽核流程狀態 |
| **修正的做法** | ① 主專案先開嚴格版守門 `assert_workflow_job_operator`（被指派人或專案管理者才放行），不收嚴既有那支，免得連佐證增刪改一起變（FR-114.1-1）；② 主專案在流程服務加一行公開包裝 `assert_job_operator`，套件只有流程實例編號也能呼叫；③ 套件八個呼叫點改接嚴格守門；④ 掃描回報觸發的背景自動完成沒有使用者，維持原樣不掛守門 |
| **狀態** | ✅ 已修（FR-114.1-1 CM-2021 BE `18b2ba06c`；FR-114.1-2 CM-2022 BE `e10d10a83`／套件 `88e519b4`；CM-2119 `e8445ae16` 修正未指派任務誤擋） |

## M06-7 🟡 只能看的成員完成或退回別人的稽核任務 {#m06-7}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #37；STRIDE：權限提升、竄改資料、事後無法追查） |
| **在哪裡** | 稽核流程模組：完成任務、退回任務 |
| **攻擊面位置** | 專案任一成員，包含唯讀角色 |
| **信任邊界位置** | **專案內角色 ↔ 任務操作**。原守門只確認「是這個專案的人」 |
| **元件端點位置** | 主專案 `api/flow_engine/routes/flow_engine_route.py`：`POST /api/1.0/flow-engine/task/complete/{id}`（`JobCompleteRoute`）、`POST /api/1.0/flow-engine/task/revert/{id}`（`JobRevertRoute`）→ `workflow_execution_service.complete_job`／`revert_job`；守門 `app/flow_engine/service/job_operator_guard.py` |
| **駭客怎麼打** | ① 唯讀成員；② 打開別人的稽核任務；③ 送出「完成」或「退回上一關」；④ 任務、問卷、流程狀態被改動，稽核紀錄上的經手人記成這個只能看的人 |
| **得手什麼** | 以旁觀角色推動或退回別人的稽核任務 |
| **修正的做法** | ① 新增 `assert_workflow_job_operator`：前段政策與既有守門一致，最後多問「是不是被指派人或專案管理者」；② 新開 `job_operator_guard.py`（原服務檔已破 800 行），完成與退回各一行呼叫；③ 新錯誤碼 `GRC_403065`，與「不是專案成員」分開；④ 保留兩條拿掉會壞的路：輪次主流程退回的已知專案旁路、無請求脈絡的背景完成；⑤ 後續任務歸屬改讀輪次鏈，未指派任務不再被誤擋（CM-2119） |
| **狀態** | ✅ 已修（FR-114.1-1，CM-2021，BE `18b2ba06c`；CM-2119 `e8445ae16`） |

## M05-6 🟡 還原歷史版本時指定別部門的版本編號，把別人答案複製進來 {#m05-6}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #38；STRIDE：竄改資料、資料外洩） |
| **在哪裡** | 問卷模組：「還原歷史版本」 |
| **攻擊面位置** | 對某份問卷有寫入權限的人（被指派人或管理者） |
| **信任邊界位置** | **問卷 ↔ 問卷**。系統驗了「你能不能改這份問卷」，但沒驗「你指定的版本是不是這份問卷的」 |
| **元件端點位置** | `jedi_survey/api/routes/question_answer_history_route.py`：`POST /api/1.0/project-survey/history/revert`（`RevertQuestionAnswerHistoryRoute`）→ `question_answer_history_service.revert_question_answer_from_history` |
| **駭客怎麼打** | ① 有自己一份問卷寫入權限的人；② 從「列出填答歷史」拿到別部門問卷的版本編號；③ 在自己那份按「還原歷史版本」，把版本編號換成別人的；④ 別人的答案被複製進自己這份 |
| **得手什麼** | 別部門問卷的作答內容 |
| **修正的做法** | ① 取出歷史版本後比對它記的問卷編號與這次操作的問卷，不同就回「找不到」；② 比對放在推播之前，避免比對失敗仍對同房間的人推一個空的「重新載入」；③ 權限維持被指派人或管理者，沒有放寬 |
| **狀態** | ✅ 已修（FR-114.1-4a，CM-2024，套件 `81051e8a`） |

## M05-8 🟡 有問卷權限就能竄改別人留言、改完仍掛原作者名 {#m05-8}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #39；STRIDE：事後無法追查、竄改資料） |
| **在哪裡** | 問卷模組：問卷討論的修改與刪除 |
| **攻擊面位置** | 持有問卷修改或刪除能力點的人，一般登入帳號動不了 |
| **信任邊界位置** | **使用者 ↔ 使用者的內容**。能力點只答「你能不能改問卷」，沒答「這則留言是不是你寫的」 |
| **元件端點位置** | `jedi_survey/api/routes/survey_discussion_route.py`：`PUT／DELETE /api/1.0/survey-discussion/{uid}`（`SurveyDiscussionRoute`）→ `app/service/survey_discussion.py` 的 `update_discussion`／`delete_discussion` |
| **駭客怎麼打** | ① 有問卷修改權限的人；② 找到同事寫的一則討論；③ 送修改請求改掉內容，系統不比對作者，改完仍掛同事的名字；④ 或直接刪除，紀錄從資料庫整筆抹掉 |
| **得手什麼** | 悄悄竄改或抹除別人的稽核討論紀錄 |
| **修正的做法** | ① 改：動手前比對建立者（不比最後修改者，那欄會被改的人覆寫），不是本人回 403；② 刪：改成軟刪除，標記已刪除、誰刪、何時刪，資料留著，讀取時濾掉；③ 刪除路由補傳操作者帳號；④ 主專案先補軟刪除三欄（CM-2025）。管理者代刪不在本卡範圍 |
| **狀態** | ✅ 已修（FR-114.1-4a，CM-2024 套件 `81051e8a`；CM-2025 BE `4e7754de7`） |

## M06-4 🟡 「檢查流程圖」沒權限檢查，還能丟超大內容進來 {#m06-4}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #40；STRIDE：讓服務停擺） |
| **在哪裡** | 稽核流程模組：流程範本編輯器的「檢查流程圖」 |
| **攻擊面位置** | 已登入使用者可打的流程範本 API。任一登入帳號 |
| **信任邊界位置** | **使用者 ↔ 流程服務**。同檔其他寫入都掛能力點，只有這支沒有；送進來的流程圖內容也沒有長度、節點數限制 |
| **元件端點位置** | 主專案 `api/flow_engine/routes/flow_template_route.py`：`POST /api/1.0/flow-engine/flow-templates/validate`（`FlowTemplateValidateRoute.post`）→ `flow_template_app_service.validate` |
| **駭客怎麼打** | ① 任一登入帳號；② 不必有範本權限，直接呼叫檢查流程圖；③ 丟一份很大、節點很多的流程圖；④ 系統照收照解析（實測一千節點 0.027 秒，打不到全站停擺，但沒權限的人能用、能塞大東西） |
| **得手什麼** | 無權限使用該功能並送入超大內容 |
| **修正的做法** | ① 路由補 `flow_template.update` 能力點；② 請求內容加 35 萬字上限（依現有最大範本放大推得）；③ 服務層新增節點數 100、分岔深度 8 的上限，走訪時記錄造訪過的節點，遇環不無限遞迴；④ 超限仍回 200＋警告清單，維持前端既有的顯示方式 |
| **狀態** | ✅ 已修（FR-114.1-8，CM-2031，BE `aa04ca1a7`） |

## M10-6 🟡 自己開專案當管理者，指派時「專案填自己的、任務填別人的」 {#m10-6}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #41；STRIDE：權限提升、竄改資料） |
| **在哪裡** | 任務與成員管理：新增任務指派 |
| **攻擊面位置** | 任一能自己開專案的人，開了就自動是那個專案的管理者 |
| **信任邊界位置** | **專案 ↔ 專案**。服務只驗「操作者是請求裡那個專案的管理者」，專案編號卻是呼叫方自己填的，沒核對任務本身屬於哪個專案 |
| **元件端點位置** | `jedi_task_platform/participant/api/routing.py`：`POST /api/1.0/task-assignee`（`TaskAssigneeRoute.post`）→ `task_assignee_service.add_task_assignee`；主專案實作 `infra/flow_control/task_existence_query.py` |
| **駭客怎麼打** | ① 任一員工先自己開一個專案（資訊不足，僅依總表描述展開）；② 送指派請求，專案編號填自己那個、任務編號填別人專案的；③ 系統確認他是自己專案的管理者就放行；④ 別人專案的任務被指派給他指定的人 |
| **得手什麼** | 對別人專案的任務下指派 |
| **修正的做法** | ① 人員那半：被指派人必須是該專案參與者，查無回 404 而不是 403（FR-114.1-3）；② 任務那半：查詢介面新增 `get_project_id`，主專案一行委派全系統唯一的任務歸屬判斷（輪次鏈），寫入前比對任務真正所屬專案與請求填的專案，不符或查不到一律 404；③ 原本打算在流程表補專案欄位，決策者裁作廢，避免出現兩份真相 |
| **狀態** | ✅ 已修（FR-114 CM-2071，套件 `ef9a313b`／BE `fccfe4db7`；人員半 CM-2023 套件 `50c6e0f0`） |

## M17-1 🟡 一個部門的編輯者改掉或永久刪掉別部門的公告 {#m17-1}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #42；STRIDE：竄改資料） |
| **在哪裡** | 公告模組：改公告、刪公告 |
| **攻擊面位置** | 持有公告編輯能力點的人（例如某部門的編輯者） |
| **信任邊界位置** | **使用者 ↔ 別人的內容**。能力點只管「能不能改公告」，管不到「能不能改別人的公告」 |
| **元件端點位置** | `jedi_bulletin/api/routes/bulletin_route.py`：`PUT／DELETE /api/1.0/bulletin/{uid}`（`BulletinDetailRoute`）→ `bulletin_service.update_bulletin`／`delete_bulletin` |
| **駭客怎麼打** | ① 某部門的公告編輯者；② 打開別部門發的公告；③ 改內容或按刪除；④ 刪除整筆從資料庫移除、救不回來；改的時候沒帶發送對象，那則公告會被靜默清空對象、變成全公司可見 |
| **得手什麼** | 竄改或永久刪除別部門的公告 |
| **修正的做法** | ① 改與刪先比對建立者帳號，不是本人一律當成查無此公告（改回 404、刪回 false）；② 刪除路由補傳操作者帳號；③ 修掉靜默清空：請求沒帶發送對象時整組維持原樣；④ 同批加入「發送範圍」欄位（見 M17-3） |
| **狀態** | ✅ 已修（FR-114.1-5a，CM-2026，套件 `8ca1c279`） |

## M19-1 🟡 權限被關掉的人把網址貼上去照樣看整張設備清冊 {#m19-1}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #43；STRIDE：資料外洩） |
| **在哪裡** | 設備與系統清冊模組：設備清冊與資訊系統清冊的七支讀取 |
| **攻擊面位置** | 被管理員刻意收掉「查看設備」權限的帳號（例如約聘、外部顧問） |
| **信任邊界位置** | **使用者 ↔ 清冊服務**。前端選單有藏，後端讀取只驗登入；程式碼裡還寫明「讀取刻意不掛」 |
| **元件端點位置** | `jedi_asset/api/routing.py`：`POST /api/1.0/devices`、`GET /api/1.0/devices/menu`、`GET /api/1.0/device/{uid}`、`GET /api/1.0/device/{uid}/references`、`GET /api/1.0/information-systems/menu`、`POST /api/1.0/information-systems/list`、`GET /api/1.0/information-system/{uid}` |
| **駭客怎麼打** | ① 一個被收掉查看權限的約聘帳號；② 側邊選單看不到入口；③ 把清冊頁網址直接貼上，或直接呼叫清單 API；④ 主機名稱、網路位址、作業系統版本、各系統機密等級與負責人整張打開 |
| **得手什麼** | 同一家客戶內完整的設備與資訊系統清冊 |
| **修正的做法** | ① 七支讀取各補能力點守門（`device.read`、`information-system.read`），沿用既有能力點名，不新造；② 契約補兩個預設常數與設定欄位；③ 先由 FR-114.1-9 補資料：缺這兩顆的既有角色補上，避免五處下拉選單安靜變空（CM-2032） |
| **狀態** | ✅ 已修（FR-114.1-6，CM-2029，套件 `0ecdd3a1`；權限資料 CM-2032 BE `fc9fca7d0`） |

## M23-2 🟡 一般員工改／刪別人的意見回饋，連帶關掉開發團隊的問題單 {#m23-2}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #45；STRIDE：竄改資料、事後無法追查） |
| **在哪裡** | 意見回饋模組：清單、明細、新增、修改、刪除、刪附件 |
| **攻擊面位置** | 任一一般員工，不需任何特殊權限 |
| **信任邊界位置** | **使用者 ↔ 別人的回饋**，以及**本系統 ↔ 外部問題單系統**。刪除會跨出本系統去關外部問題單，一旦送出收不回來；當時六支只驗登入、也不比對作者 |
| **元件端點位置** | `jedi_issue/api/routing.py`：`POST /api/1.0/feedbacks`、`GET／POST／PUT／DELETE /api/1.0/feedback/{uid}`、`DELETE /api/1.0/feedback/file/{uid}/{file_uid}`；`jedi_issue` 的 feedback app service |
| **駭客怎麼打** | ① 任一員工；② 在意見回饋頁找到別人送的回饋；③ 直接呼叫刪除或修改；④ 刪除連帶把開發團隊正在處理的那張外部問題單關掉，稽核紀錄還把操作人記成合法本人 |
| **得手什麼** | 竄改、刪除別人的回饋並關閉外部問題單 |
| **修正的做法** | ① 六支全部補能力點（讀取收「管理」或「唯讀檢視」任一顆）；② 改、刪、刪附件比對建立者，不是本人 403；刪除的比對放在關閉外部問題單之前，非本人一次外部呼叫都不送；③ 清單加「查看範圍」參數（我的／全部），「全部」要檢視權限，不帶時依權限推定；④ 主專案接上「有沒有這顆能力點」的判定介面 |
| **狀態** | ✅ 已修（FR-114.1-7，CM-2030，套件 `23c2ee69`／BE `4b1224c4a`） |

## M11-5 🟡 子公司管理員清空母公司範本的適用控制項清單 {#m11-5}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #46；STRIDE：竄改資料） |
| **在哪裡** | 合規文件核心：資源庫改範本的「適用控制項」清單 |
| **攻擊面位置** | 某家子公司的管理員（有範本修改權限），只打得到有上下層分享或原廠公版的範本 |
| **信任邊界位置** | **子租戶 ↔ 母租戶／原廠公版**。同一支程式隔四行，改「分享範圍」那條檢查歸屬，改「控制項清單」那條沒有 |
| **元件端點位置** | 主專案 `api/module_frame/routes/module_frame_route.py`：`PUT /api/1.0/module-frame/{uid}`（`ModuleFrameRoute.put`）→ `module_frame_service.update_module_frame` → `app/oscal/service/resource_library_app_service.py` 的 `update_applicable_controls` |
| **駭客怎麼打** | ① 子公司管理員；② 打開母公司分享下來的範本；③ 只送控制項清單、不送分享欄位，把清單改掉或清空；④ 底下每條實作說明一併刪除，之後用這份範本開專案的人以為在範圍內的項目其實沒被評估，畫面沒有錯誤 |
| **得手什麼** | 讓共享範本的評估範圍被悄悄改掉 |
| **修正的做法** | ① 新增 `assert_resource_tenant_writable`：不看分享範圍、任何寫入都驗「是不是自己公司的」，平台管理員豁免；② `update_applicable_controls` 進門就驗，查詢多撈範本的租戶；③ 範本底下的流程定義初始化也帶上擁有者；④ 後續 `update_module_frame` 在任何欄位寫入前先驗歸屬（CM-2178） |
| **狀態** | ✅ 已修（CM-2040 BE `60015232f`；FR-114 CM-2178 `abf1cf8a4`，1.21.0 出貨） |

## M10-11 🟡 子公司管理員清空母公司範本的控制項清單（與 M11-5 同案） {#m10-11}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #46；STRIDE：竄改資料） |
| **在哪裡** | 任務與成員管理頁登記的同一件事：資源庫改範本控制項清單 |
| **攻擊面位置** | 子公司管理員 |
| **信任邊界位置** | **子租戶 ↔ 母租戶**。寫入前沒先確認範本屬於自己客戶 |
| **元件端點位置** | 同 M11-5：`PUT /api/1.0/module-frame/{uid}` → `resource_library_app_service.update_applicable_controls` |
| **駭客怎麼打** | ① 子公司管理員（資訊不足，僅依總表描述展開）；② 改一份母公司分享下來的範本；③ 把適用控制項清單整個改掉或清空；④ 底下實作記錄一併刪除，用這份範本開的專案留下看不出來的評估缺口 |
| **得手什麼** | 讓共享範本的控制項範圍被竄改 |
| **修正的做法** | 同 M11-5：把歸屬檢查從「只在改分享身分時」搬到「只要有寫入就檢查」，經 `assert_resource_tenant_writable` |
| **狀態** | ✅ 已修（CM-2040 `60015232f`；FR-114 CM-2178 `abf1cf8a4`，1.21.0 出貨） |

## M03-12 🟡 知道任務編號就讀到別人專案的掃描執行紀錄 {#m03-12}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #47；STRIDE：資料外洩） |
| **在哪裡** | 弱點檢測模組：查掃描執行紀錄 |
| **攻擊面位置** | 同一家客戶裡任一成員 |
| **信任邊界位置** | **使用者 ↔ 檢測服務**。同服務另外八個吃任務編號的功能都先查成員，只有這支直接撈 |
| **元件端點位置** | `jedi_detection/api/routes/detection_tool_route.py`：`GET /api/1.0/detection-tools/jobs/{job_uid}/executions`（`DetectionToolJobExecutionsRoute`）→ `detection_orchestration_service.list_executions` |
| **駭客怎麼打** | ① 同客戶任一成員；② 拿到別人專案的任務編號；③ 呼叫查執行紀錄；④ 讀到別人專案的掃描歷史 |
| **得手什麼** | 別人專案的掃描執行紀錄 |
| **修正的做法** | ① 補查任務＋`assert_project_participant`（讀取門檻是任一參與者，不是寫入用的被指派人）；② 查無任務拋「找不到」而不是回空清單，空清單分不出「沒資料」與「沒這個任務」 |
| **狀態** | ✅ 已修（CM-2042，套件 `5a9baf2b`）。輸入檔標 CM-2040，實際修正 commit 標的是 CM-2042 |

## M05-3 🟡 送空白請求「列出任務問卷」拿到全客戶問卷清單 {#m05-3}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #48；STRIDE：資料外洩） |
| **在哪裡** | 問卷模組：「列出任務問卷」 |
| **攻擊面位置** | 任一登入帳號 |
| **信任邊界位置** | **使用者 ↔ 問卷服務**。查詢條件全可空，底層全空不過濾 |
| **元件端點位置** | `jedi_survey/api/routes/task_survey_route.py`：`POST /api/1.0/task-surveys`（`TaskSurveysRoute`）、`GET /api/1.0/task-survey/{uid}` → `task_survey_service.get_task_surveys`／`get_task_survey_by_uid` |
| **駭客怎麼打** | ① 任一登入帳號；② 對「列出任務問卷」送空白請求；③ 系統不限範圍；④ 回來全客戶問卷的編號、所屬專案、狀態、受檢設備與部門，正好拿去讀答案（M05-2） |
| **得手什麼** | 全公司問卷清單與往下取答案的編號 |
| **修正的做法** | ① 任務編號二擇一必填，空白請求回 400；② 取首筆做歸屬檢查（任一參與者）；③ 單筆讀取補守門，並把原本重複的兩支實作收成一支；④ 任務歸屬改由主專案注入唯一判斷（CM-2114） |
| **狀態** | ✅ 已修（CM-2034，套件 `83fc34e5`；CM-2114 `49cbe0b7b`） |

## M05-4 🟡 送空白請求「列出填答歷史」看到全公司誰改過哪份問卷 {#m05-4}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #49；STRIDE：資料外洩） |
| **在哪裡** | 問卷模組：「列出填答歷史」與歷史選單 |
| **攻擊面位置** | 任一登入帳號 |
| **信任邊界位置** | **使用者 ↔ 問卷服務**。不限範圍、不檢查歸屬 |
| **元件端點位置** | `jedi_survey/api/routes/question_answer_history_route.py`：`POST /api/1.0/project-survey/histories`、`GET /api/1.0/project-survey/histories/menu/{uid}` → `question_answer_history_service` |
| **駭客怎麼打** | ① 任一登入帳號；② 對「列出填答歷史」送空白請求；③ 系統不過濾；④ 看到全公司跨專案誰何時改了哪份問卷，同時拿到 M05-1、M05-6 要用的版本編號 |
| **得手什麼** | 全公司問卷異動軌跡與版本編號 |
| **修正的做法** | ① 問卷編號二擇一必填；② 反查問卷後補參與者守門，並把問卷編號強制寫回查詢條件；③ 歷史選單同補守門；④ 任務歸屬改走注入的唯一判斷（CM-2114） |
| **狀態** | ✅ 已修（CM-2034，套件 `83fc34e5`；CM-2114 `49cbe0b7b`） |

## M05-5 🟡 送空查詢「問卷討論列表」撈回全公司缺失細節與審查意見 {#m05-5}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #50；STRIDE：資料外洩） |
| **在哪裡** | 問卷模組：問卷討論列表 |
| **攻擊面位置** | 任一登入帳號 |
| **信任邊界位置** | **使用者 ↔ 問卷服務**。兩個查詢條件都可空，空了不過濾 |
| **元件端點位置** | `jedi_survey/api/routes/survey_discussion_route.py`：`POST /api/1.0/survey-discussions`（`SurveyDiscussionsRoute`）→ `survey_discussion_service.get_survey_discussions` |
| **駭客怎麼打** | ① 任一登入帳號；② 對討論列表送空查詢；③ 系統不確認你是不是該專案的人；④ 全公司的討論串回來，裡面是稽核缺失細節與審查意見，含沒參與的專案 |
| **得手什麼** | 全公司稽核討論內容 |
| **修正的做法** | ① 問卷編號與任務問卷編號兩者都必填；② 由任務問卷反查任務所屬專案後補參與者守門；③ 主專案補上討論服務需要的依賴（`4a5650c5d`）；④ 任務歸屬改走注入的唯一判斷（CM-2114） |
| **狀態** | ✅ 已修（CM-2034，套件 `83fc34e5`／BE `4a5650c5d`；CM-2114 `49cbe0b7b`） |

## M05-7 🟡 想進哪間多人填答房間就進哪間，旁聽並抓答案快照 {#m05-7}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #51；STRIDE：資料外洩） |
| **在哪裡** | 問卷模組：多人同時填答的即時通道（房間） |
| **攻擊面位置** | 任一登入帳號，經即時通訊通道（SocketIO）而不是一般網址 |
| **信任邊界位置** | **使用者 ↔ 即時通道**。房號就是問卷編號，任何事件都能直接指定房號，不必先加入 |
| **元件端點位置** | `jedi_survey/app/handler/fill_survey_socketio_handler.py`：命名空間 `/socket/fill-survey` 的 `on_join`、`on_leave`、`on_editing`、`on_update`、`on_edit`、`on_endedit` |
| **駭客怎麼打** | ① 任一登入帳號；② 連上填答通道，送加入房間事件，房號填別專案的問卷編號；③ 系統不問成員；④ 旁聽對方正在編輯的問卷，並抓到當下的答案快照 |
| **得手什麼** | 別專案問卷的即時作答內容 |
| **修正的做法** | ① 新增 `_assert_room_member`；② 六個事件進入點全部呼叫，因為這些事件不必先加入就能指定房號，只守加入等於沒守；③ 任務歸屬改走注入的唯一判斷（CM-2114） |
| **狀態** | ✅ 已修（CM-2034，套件 `83fc34e5`；CM-2114 `49cbe0b7b`） |

## M06-5 🟡 一個稽核輪次編號讀走別人的階段歷程與退回理由 {#m06-5}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #52；STRIDE：資料外洩） |
| **在哪裡** | 稽核流程模組：稽核輪次階段歷程 |
| **攻擊面位置** | 任一登入帳號 |
| **信任邊界位置** | **使用者 ↔ 稽核服務**。網址帶了專案編號，服務從頭到尾沒拿它比對，等於「假裝有檢查」 |
| **元件端點位置** | 主專案 `api/flow_engine/routes/stage_rollback_route.py`：`GET /api/1.0/project/{project_uid}/audit-round/{round_uid}/stage/transitions`（`RoundStageTransitionsResource`）→ 套件 `audit_round_app_service.list_stage_transitions` |
| **駭客怎麼打** | ① 任一登入帳號；② 網址專案填任何一個、輪次填別人的；③ 系統只用輪次撈資料；④ 拿到每次推進或退回的操作者、從哪階段到哪階段，以及稽核人員親筆寫的退回理由全文 |
| **得手什麼** | 別人專案的稽核階段歷程與退回理由 |
| **修正的做法** | ① 路由把網址專案與目前使用者往下傳；② 套件比對網址專案是不是這一輪的歸屬，不符回 404（回 403 等於確認編號存在）；③ 限該專案任一參與者；④ 後續使用者參數改必填（CM-2172），新呼叫端忘了傳不會安靜放行 |
| **狀態** | ✅ 已修（CM-2035，BE `e2c32258d`／套件 `3e82b625`；CM-2172 套件 `7df95248`） |

## M06-6 🟡 「查目前在哪個階段」兩個編號不核對，角色不符照樣整包回傳 {#m06-6}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #53；STRIDE：資料外洩） |
| **在哪裡** | 稽核流程模組：目前階段查詢，以及推進、退回 |
| **攻擊面位置** | 任一登入帳號；推進／退回需要在某專案有角色 |
| **信任邊界位置** | **專案 ↔ 輪次**。角色用網址的專案查、資料用網址的輪次查，兩個來源互不核對 |
| **元件端點位置** | 主專案 `api/flow_engine/routes/stage_advance_route.py`：`GET …/project/{project_uid}/audit-round/{round_uid}/stage/info`、`POST …/stage/advance`；`stage_rollback_route.py`：`POST …/stage/rollback`；新守門 `app/flow_engine/service/stage_project_guard.py` |
| **駭客怎麼打** | ① 在自己專案有角色的人；② 網址專案填自己的、輪次填別人的；③ 查階段時角色不符只把「能不能推進」標成否，其他資料照樣整包回傳；④ 推進與退回同樣寫法，當時只是剛好被下游另一道檢查擋住 |
| **得手什麼** | 別人專案的目前階段資料 |
| **修正的做法** | ① 新增 `resolve_project_id_for_round` 當三處共同入口：解析網址專案→確認這一輪屬於它→回傳專案給角色查詢；② 查詢、推進、退回三處全改走它，不再依賴下游擋；③ 陷阱：查詢有「沒有當前階段就提早回傳」的分支，檢查放在它之前 |
| **狀態** | ✅ 已修（CM-2035，BE `e2c32258d`） |

## M10-1 🟡 送不填專案編號的空白查詢，一次拿到公司全部專案的成員名冊 {#m10-1}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #54；STRIDE：資料外洩） |
| **在哪裡** | 任務與成員管理：專案成員查詢 |
| **攻擊面位置** | 同公司任一登入帳號 |
| **信任邊界位置** | **使用者 ↔ 成員服務**。專案編號可不填，不填就不過濾 |
| **元件端點位置** | `jedi_task_platform/participant/api/routing.py`：`POST /api/1.0/project-participants`、`GET /api/1.0/project-participants/menu` → `project_participant_service.get_project_participants`／`get_project_participant_menus` |
| **駭客怎麼打** | ① 同公司任一帳號（資訊不足，僅依總表描述展開）；② 對成員查詢送不帶專案編號的請求；③ 系統不限範圍；④ 全公司所有專案的成員名冊一次回來 |
| **得手什麼** | 全公司各專案的成員名冊 |
| **修正的做法** | ① 專案編號改必填（新碼 `PARTICIPANT_PROJECT_ID_REQUIRED`）；② 補歸屬檢查，非參與者 403；③ 新增 `assert_participant` 介面，主專案兩處實作一起補，避免後掛載的覆蓋先掛載的 |
| **狀態** | ✅ 已修（CM-2036，套件 `3ed750cd`／BE `05870c4cc`） |

## M10-2 🟡 換一個專案編號就讀到別人專案的群組層與控制項層人員配置 {#m10-2}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #55；STRIDE：資料外洩） |
| **在哪裡** | 任務與成員管理：群組層／控制項層成員查詢 |
| **攻擊面位置** | 同公司裡不是這個專案的人 |
| **信任邊界位置** | **使用者 ↔ 成員服務**。不檢查是不是專案成員 |
| **元件端點位置** | `jedi_task_platform` 的 `GET /api/1.0/project-control-participants`、`/project-control-participants/menu`（已退役）→ `project_control_participant_service.get_control_participants_with_inherits`／`get_control_participant_menu` |
| **駭客怎麼打** | ① 同公司非成員（資訊不足，僅依總表描述展開）；② 把網址專案編號換成別人的；③ 系統不問成員；④ 讀到別人專案在群組層與控制項層的完整人員配置 |
| **得手什麼** | 別人專案的細部人員配置 |
| **修正的做法** | ① 兩支讀取的專案編號改必填並補歸屬檢查，回傳範圍收斂到控制項層（CM-2036）；② 之後決策者裁定整組群組／控制項成員網址退役（讀取兩支一併拆），前端按鈕與側欄同步拿掉（CM-2072） |
| **狀態** | ✅ 已修（CM-2036 套件 `3ed750cd`；網址退役 CM-2072 套件 `f1fa23c1`／前端 `2cb7fb4`） |

## M10-3 🟡 直接開別人專案的摘要報告，讀稽核結論全文與歷史版本 {#m10-3}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #56；STRIDE：資料外洩） |
| **在哪裡** | 專案摘要報告：讀取、列表、匯出、歷史版本 |
| **攻擊面位置** | 同公司非成員 |
| **信任邊界位置** | **使用者 ↔ 報告服務**。不檢查專案成員；歷史列表用了「只產文件不驗證」的寫法，報告編號沒帶就放行 |
| **元件端點位置** | 主專案 `api/project_summary_report/routes/project_summary_report_route.py`：`POST /api/1.0/project-summary-reports`、`GET /api/1.0/project-summary-report/{uid}`、`GET /api/1.0/project-summary-report/export/{uid}`；`project_summary_report_history_route.py`：`POST /api/1.0/project-summary-report/histories`、`GET /api/1.0/project-summary-report/history/{uid}` |
| **駭客怎麼打** | ① 同公司非成員（資訊不足，僅依總表描述展開）；② 打開別人專案的摘要報告或列表；③ 系統不問成員；④ 讀到稽核結論全文與歷史版本，歷史版本常留著後來被刪掉的稽核發現 |
| **得手什麼** | 別人專案的稽核結論與被刪掉的發現 |
| **修正的做法** | ① 報告與歷史共六支讀取方法補參與者檢查；② 首腦退回：前棒改到零呼叫的列表方法，真正被路由呼叫的分頁列表補上「只回自己參與的專案」過濾；③ 歷史列表改成真的跑請求驗證，沒帶報告編號回 400 |
| **狀態** | ✅ 已修（CM-2037，BE `cd9223592`／`b047d16e3`） |

## M10-5 🟡 送空白查詢拿到公司內全部一萬多筆任務指派紀錄 {#m10-5}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #58；STRIDE：資料外洩） |
| **在哪裡** | 任務與成員管理：任務指派清單查詢 |
| **攻擊面位置** | 同公司任一登入帳號 |
| **信任邊界位置** | **使用者 ↔ 指派服務**。兩個查詢條件都可空 |
| **元件端點位置** | `jedi_task_platform/participant/api/routing.py`：`POST /api/1.0/task-assignees`（`TaskAssigneesRoute`）→ `task_assignee_service.get_task_assignees` |
| **駭客怎麼打** | ① 同公司任一帳號（資訊不足，僅依總表描述展開）；② 對指派清單送空白查詢；③ 系統不過濾；④ 全公司一萬多筆任務指派紀錄一次回來 |
| **得手什麼** | 全公司的任務指派紀錄 |
| **修正的做法** | ① 專案編號與任務編號二擇一必填（新碼 `TASK_ASSIGNEE_QUERY_KEY_REQUIRED`）；② 只給任務編號時反查流程實例再做歸屬比對；③ 非參與者 403 |
| **狀態** | ✅ 已修（CM-2036，套件 `3ed750cd`） |

## M10-9 🟡 取資料底層「條件全沒填就不過濾」，空查詢等於回整張表 {#m10-9}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #59；STRIDE：資料外洩） |
| **在哪裡** | 共用取資料底層：所有套件共用的資料存取基底 |
| **攻擊面位置** | 任一登入帳號，對任一支把請求條件原樣丟給底層的查詢 |
| **信任邊界位置** | **API 層 ↔ 資料存取層**。底層把「全空」解讀成「不過濾」，API 層沒有一支擋空查詢 |
| **元件端點位置** | `jedi_common/session/database/repository/base_repository_impl.py`（`if not filter_dict` 那段）；受影響的端點見 M05-1、M05-3～M05-5、M10-1、M10-5 |
| **駭客怎麼打** | ① 任一登入帳號（資訊不足，僅依總表描述展開）；② 挑一支查詢送空白條件；③ 底層看到條件全空就不加過濾；④ 回來整張表 |
| **得手什麼** | 整張表的資料（視接的是哪支查詢） |
| **修正的做法** | 決策者裁定「逐支補、底層不動」：① 有洞的查詢各自把關鍵編號改必填並補歸屬檢查（問卷六組 CM-2034、成員與指派三支 CM-2036）；② 底層規則維持原樣；③ 反悔條件：新功能第四次撞到同一件事就改底層 |
| **狀態** | ✅ 已修（逐支補：CM-2034 套件 `83fc34e5`、CM-2036 套件 `3ed750cd`） |

## M15-2 🟡 AI 儀表板 26 支查詢不問權限，基層員工拿到整份組織架構 {#m15-2}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #60；STRIDE：資料外洩） |
| **在哪裡** | AI 儀表板模組：產生儀表板時背後呼叫的 26 支資料查詢 |
| **攻擊面位置** | 能使用 AI 儀表板的任一帳號，含基層員工 |
| **信任邊界位置** | **AI 儀表板 ↔ 各業務服務**。儀表板不走一般網址入口、直接叫服務層，入口那道權限檢查根本不經過 |
| **元件端點位置** | `jedi_ai_dashboard/api/routing.py`：`POST /api/1.0/ai-dashboard/auto-generate` → `data_api_service.call_api`；查詢申報在主專案 `di_containers/dashboard_apis/*.py`（13 支檔） |
| **駭客怎麼打** | ① 基層員工；② 打開 AI 儀表板，說「列出所有使用者和他們的角色」；③ 儀表板直接呼叫使用者、角色、租戶、部門查詢，不問權限；④ 拿到整份組織架構、誰的權限最大、公司有哪些客戶 |
| **得手什麼** | 整份組織架構與權限配置 |
| **修正的做法** | ① 套件登記簿每支查詢多一欄「要什麼權限」，執行前檢查；② 沒填＝拒絕（fail-closed），真的公開要明寫「公開」標記，讓漏填與刻意公開分得出來；③ 主專案 26 支逐一申報，對照側邊選單既有權限；④ 給 AI 看的查詢目錄只列這個人能用的那些；⑤ 後續加申報簽名守衛（CM-2232） |
| **狀態** | ✅ 已修（CM-2038，BE `8f94e249d`／套件 `07126b3d`） |

## M15-3 🟡 AI 儀表板查專案清單把呼叫者當管理員，列出全部專案 {#m15-3}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #61；STRIDE：資料外洩） |
| **在哪裡** | AI 儀表板：專案清單查詢 |
| **攻擊面位置** | 能使用 AI 儀表板的任一帳號 |
| **信任邊界位置** | **使用者 ↔ 專案服務**。儀表板專用的查專案方法寫死「視同管理員」，繞過參與可見性 |
| **元件端點位置** | `POST /api/1.0/ai-dashboard/auto-generate` → 主專案 `app/flow_control/service/project_service.py` 的 `get_projects_for_dashboard` |
| **駭客怎麼打** | ① 不是任何專案成員的員工；② 在 AI 儀表板說「列出所有專案」；③ 查詢寫死視同管理員；④ 該客戶底下全部稽核專案（哪家客戶正在被查什麼）列出來 |
| **得手什麼** | 全部稽核專案清單 |
| **修正的做法** | ① 把寫死的「視同管理員」改成否，與規劃頁列表同一條規則：只回參與的專案；② 副作用經決策者接受：專案管理者也只看得到自己參與的；③ 收掉過期註解 |
| **狀態** | ✅ 已修（CM-2038，BE `8f94e249d`） |

## M17-2 🟡 對 AI 儀表板說「列出所有公告」拿到全客戶公告 {#m17-2}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #62；STRIDE：資料外洩） |
| **在哪裡** | 公告模組被 AI 儀表板呼叫的那條路 |
| **攻擊面位置** | 能使用 AI 儀表板的任一帳號 |
| **信任邊界位置** | **AI 儀表板 ↔ 公告服務**，以及**本系統 ↔ 外部 AI 服務**。公告的可見性旗標由呼叫端傳，儀表板沒傳，服務端就全回；前三筆原文還會送到外部 AI |
| **元件端點位置** | `POST /api/1.0/ai-dashboard/auto-generate` → 申報 `di_containers/dashboard_apis/bulletin.py` → `jedi_bulletin` 的公告查詢 |
| **駭客怎麼打** | ① 任一能用儀表板的帳號；② 說「列出所有公告」；③ 儀表板呼叫公告查詢不帶可見性旗標；④ 拿到別部門的、停用草稿、未到發布時間的公告，前三筆原文送到外部 AI（檢視時這條路因型別錯誤暫時出錯，是意外不是守門） |
| **得手什麼** | 整個客戶底下全部公告 |
| **修正的做法** | ① 公告查詢申報「看公告」權限；② 申報加固定參數，強制帶上可見性旗標；③ 修掉兩個真因：注入的使用者物件轉成一般字典（原本 `.get()` 會出錯）、可見性旗標從未傳遞 |
| **狀態** | ✅ 已修（CM-2038，BE `8f94e249d`／套件 `07126b3d`） |

## M20-3 🟡 任務詳情收了專案編號卻不用，填真的、全零、亂打都查得到同一筆 {#m20-3}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #63；STRIDE：資料外洩、竄改資料） |
| **在哪裡** | 任務管理：任務詳細資料的讀、改、刪 |
| **攻擊面位置** | 任一登入帳號，從任務列表看到別人專案的任務編號即可 |
| **信任邊界位置** | **專案 ↔ 任務**。網址要填專案編號，但從頭到尾沒用來比對任務歸屬；改刪只驗「你是網址專案的管理者」 |
| **元件端點位置** | 主專案 `api/flow_control/routes/job_route.py`：`GET／PUT／DELETE /api/1.0/grc/project/{project_uid}/job/{job_uid}`（`ProjectJobDetailResource`）→ `app/flow_control/service/job_service.py` 的 `_assert_job_belongs_to_project`；歸屬判斷 `infra/readmodel/tasks/job_project_ownership_query.py` |
| **駭客怎麼打** | ① 任一登入帳號；② 記下別人專案的任務編號；③ 網址專案填真的、全零或亂打一串都行；④ 讀到完整任務內容；若他是自己某專案的管理者，帶自己的專案編號還能改、刪那張任務 |
| **得手什麼** | 別人專案任務的內容，進而改刪 |
| **修正的做法** | ① 讀、改、刪前反查任務真正所屬專案並與網址比對，不符一律回「查無此資料」（不用 403，避免洩漏編號存在）；② 讀取補參與者檢查，刪除的專案編號改必填；③ 原本用指派表推專案，未指派任務（99.7%）會被誤擋，改成全系統唯一的輪次鏈判斷（CM-2113） |
| **狀態** | ✅ 已修（CM-2039，BE `8e33568d2`；CM-2113 `f52a53f1e`） |

## M11-11 🟡 刪佐證文件時查「你管不管這份計畫」，刪的卻是另外給的文件編號 {#m11-11}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #64；STRIDE：竄改資料） |
| **在哪裡** | 合規文件核心：刪除控制項底下的佐證文件、刪除程序書文件庫的文件 |
| **攻擊面位置** | 任一專案的管理者；自己開一個專案就自動是 |
| **信任邊界位置** | **計畫 ↔ 計畫**。守門看網址上的計畫，動手的是請求另外給的文件編號，兩者不核對；凍結的稽核快照也擋不住 |
| **元件端點位置** | 主專案 `api/oscal/__init__.py`：`DELETE /api/1.0/ssp/{ssp_uid}/control-implementation/{control_identifier}/reference-document/{doc_uid}`、`DELETE /api/1.0/ssp/{ssp_uid}/document-pool/{doc_uid}` → `ssp_control_implementation_service.delete_reference_document`、`ssp_document_pool_service.delete_from_pool` |
| **駭客怎麼打** | ① 自己開一個專案當負責人；② 拿到別專案、別家公司的文件編號；③ 在自己的計畫網址下送刪除，文件編號填別人的；④ 文件無聲消失，文件庫那條還連帶刪掉所有掛載、可能把實體檔一起銷毀 |
| **得手什麼** | 刪掉別人的佐證文件與程序書 |
| **修正的做法** | ① 刪除前比對文件掛在本計畫底下（佐證看掛載點、文件庫看所屬計畫），查無或不屬於一律回 404；② 文件庫刪除原本兩套查詢對不上、查無時拿空值繼續跑，改成單一查詢同時取兩個編號；③ 多版共用實體檔的「歸零才刪」邏輯不變；④ 新碼 `GRC_404049` |
| **狀態** | ✅ 已修（FR-114 CM-2041，BE `15d4efd3c`，1.21.0 出貨） |

## M11-13 🟡 建立合規資源庫的入口完全沒權限檢查 {#m11-13}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #65；STRIDE：權限提升） |
| **在哪裡** | 合規文件核心：建立合規資源庫 |
| **攻擊面位置** | 公司內任何角色，含唯讀帳號 |
| **信任邊界位置** | **使用者 ↔ 資源庫服務**。隔壁做同樣事的入口要權限，這支只驗登入；每建一次就複製一整套控制項目錄，又沒有次數上限 |
| **元件端點位置** | 主專案 `api/oscal/routes/resource_library_route.py`：`POST /api/1.0/oscal/resource-libraries`（`ResourceLibraryCreateRoute`）→ `resource_library_app_service.create_resource_library`；另兩條建庫路徑：Excel／Word 匯入的「建新範本」 |
| **駭客怎麼打** | ① 唯讀帳號；② 直接呼叫建立資源庫；③ 系統只驗登入，複製一整套控制項目錄；④ 寫個迴圈重複呼叫，用很小代價把資料庫灌大 |
| **得手什麼** | 越權建立資源庫並灌爆資料庫 |
| **修正的做法** | ① 入口掛 `module-frame.create`；② 發現 Excel 建新庫也沒這道門，一起補（三條建庫路徑只擋兩條等於沒擋）；③ 在三條路徑共用的合併點加每租戶每小時 10 次上限，綁租戶不綁人；④ 建庫只准用已發佈的框架版本（見 M11-30） |
| **狀態** | ✅ 已修（能力點與上限 CM-2040 BE `60015232f`；FR-114 CM-2179 `94275d72f`，1.21.0 出貨） |

## M07-2 🟡 舊版查結果／查報表不檢查成員，查不到就退回直接讀雲端硬碟 {#m07-2}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #66；STRIDE：資料外洩） |
| **在哪裡** | 證據自動分類（舊線）：查分類結果、查驗證與裁決報表 |
| **攻擊面位置** | 任一登入帳號，不必是專案成員 |
| **信任邊界位置** | **使用者 ↔ 分類服務 ↔ Google 雲端硬碟**。不檢查成員；查不到紀錄時還退回直接讀雲端，用的是呼叫者自己公司的鑰匙 |
| **元件端點位置** | `jedi_evidence_classification` 舊線（已拆）：`GET /api/1.0/classification-run/{run_folder_id}/state`、`…/report/validation`、`…/report/adjudication`、`GET /api/1.0/project/{project_uid}/classify-evidence/summary` |
| **駭客怎麼打** | ① 任一登入帳號；② 從工作清單（M07-3）拿到資料夾編號；③ 查結果與報表，系統不問成員；④ 查不到紀錄時直接去雲端讀，拿到檔案編號，再接 M07-1 抓檔 |
| **得手什麼** | 別專案的分類結果與檔案編號 |
| **修正的做法** | 拆除了什麼：舊線九支網址與服務方法整組拆掉（同 M07-1），前端舊線退場 |
| **狀態** | 🗑️ 已拆除（隨舊線退場，1.21.0 出貨；CM-2222，套件 `87fd5439`／BE `ccc5795f6`／前端 `2537ea5`） |

## M07-3 🟡 舊版工作清單不檢查成員，進度資料全系統共用不分客戶 {#m07-3}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #66；STRIDE：資料外洩） |
| **在哪裡** | 證據自動分類（舊線）：工作清單與單筆狀態查詢 |
| **攻擊面位置** | 任一登入帳號 |
| **信任邊界位置** | **租戶 ↔ 租戶**。進度資料放在記憶體，在資料庫隔離管不到的地方；查詢不檢查成員 |
| **元件端點位置** | `jedi_evidence_classification` 舊線（已拆）：`GET /api/1.0/project/{project_uid}/classify-evidence/jobs`、`GET …/classify-evidence/jobs/{job_uid}` |
| **駭客怎麼打** | ① 任一登入帳號；② 查任一專案的分類工作清單；③ 系統不問成員，進度資料也不分客戶；④ 拿到資料夾編號，這是 M07-2、M07-1 的入場券 |
| **得手什麼** | 別專案的分類工作與資料夾編號 |
| **修正的做法** | 拆除了什麼：同 M07-1，舊線九支網址整組拆掉 |
| **狀態** | 🗑️ 已拆除（隨舊線退場，1.21.0 出貨；CM-2222，套件 `87fd5439`） |

## M10-7 🟡 群組層成員的增／改／刪因漏接一條線完全不檢查權限 {#m10-7}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #68；STRIDE：權限提升、竄改資料） |
| **在哪裡** | 任務與成員管理：群組層成員的新增、修改、刪除 |
| **攻擊面位置** | 若被接上入口，任一登入帳號；查證時沒有路由或前端在用 |
| **信任邊界位置** | **使用者 ↔ 成員服務**。主專案 DI 建這個服務時漏接角色服務，守門形同虛設；而這張表被拿來算「哪些專案我看得到」 |
| **元件端點位置** | `jedi_task_platform/participant/app/service/project_group_participant_service.py` 的 add／update／delete（已刪）；主專案 `di_containers/flow_engine/project_participant_containers.py` |
| **駭客怎麼打** | ① 任一登入帳號（資訊不足，僅依總表描述展開）；② 經任何接到這三支的入口寫入群組層成員；③ 守門缺零件不檢查；④ 把自己加進別人的專案，之後「我看得到的專案」就多了它 |
| **得手什麼** | 把自己塞進別人專案的可見範圍 |
| **修正的做法** | 拆除了什麼：裁定不補接線（會安靜改變行為），三支寫入方法整支刪除；查詢方法保留（儀表板在用） |
| **狀態** | 🗑️ 已刪除（CM-2044，FR-114.3-2，套件 `9c9029fd`，1.21.0 出貨） |

## M10-8 🟡 流程參與者增／改／刪把關寫在永遠跑不到的位置 {#m10-8}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #69；STRIDE：權限提升） |
| **在哪裡** | 任務與成員管理：流程參與者的新增、修改、刪除 |
| **攻擊面位置** | 從沒啟用過的功能，目前沒有入口 |
| **信任邊界位置** | **使用者 ↔ 成員服務**。守門函式找不到紀錄就提早回傳，真正的檢查永遠跑不到 |
| **元件端點位置** | `jedi_task_platform/participant/app/service/process_participant_service.py` 的 add／update／delete 與 `_assert_manager_for_process`（已刪） |
| **駭客怎麼打** | ① 若有人把入口接上（資訊不足，僅依總表描述展開）；② 對不存在對應紀錄的流程送寫入；③ 守門提早回傳；④ 寫入照做，現在擋住它的只是功能本身壞著 |
| **得手什麼** | 改動流程參與者 |
| **修正的做法** | 拆除了什麼：三支寫入方法與成了孤兒的守門函式一併刪除，查詢方法保留 |
| **狀態** | 🗑️ 已刪除（CM-2044，FR-114.3-2，套件 `9c9029fd`，1.21.0 出貨） |

## M03-6 🟡 「測試連線」目標主機由呼叫者自己填，代理程式變成內網跳板 {#m03-6}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #79；STRIDE：資料外洩） |
| **在哪裡** | 弱點檢測模組：「測試連線」要連到哪台主機 |
| **攻擊面位置** | 能按測試連線的人（M03-2 修好後需有修改設定的權限） |
| **信任邊界位置** | **客戶內網 ↔ 外部**。代理程式在客戶內網裡，目標主機由請求指定，回應又分得出連得上／拒絕／逾時，等於提供內網探測回饋 |
| **元件端點位置** | `jedi_detection`：`POST /api/1.0/detection-tools/configs/{uid}/test-connection` → `app/service/detection_tool_service.py` |
| **駭客怎麼打** | ① 能按測試連線的人；② 在請求裡帶一長串內網位址；③ 代理程式逐台去連；④ 看回應差異（連得上、拒絕、逾時）一台一台摸清客戶內網有哪些機器開哪些服務 |
| **得手什麼** | 客戶內網的主機與服務分布 |
| **修正的做法** | 決策者裁定不做目標白名單，改做三件：① 單次主機數上限（預設 32，新碼 `DETECTION_TOOLS_400019`）；② 回應收斂成成功／失敗，逐台原因只寫進日誌；③ 新增稽核日誌：誰按的、哪個租戶、哪組設定、連哪幾台、逐台結果 |
| **狀態** | ✅ 已修（CM-2058，套件 `f8e3281a`／BE `ebc57b96f`） |

## M15-4 🟡 儀表板整包回傳，夾帶密碼鹽值與超管旗標 {#m15-4}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #95；STRIDE：資料外洩） |
| **在哪裡** | AI 儀表板：查到的資料轉成回覆內容 |
| **攻擊面位置** | 能用 AI 儀表板的帳號 |
| **信任邊界位置** | **後端 ↔ 瀏覽器／外部 AI 服務**。共用的序列化工具把物件上有的欄位全吐出，畫面只顯示三欄但整包已送出，送給外部 AI 的樣本也沒遮 |
| **元件端點位置** | `POST /api/1.0/ai-dashboard/auto-generate`；序列化工具 `jedi_common/utils/serialization_util.py` 的 `to_serializable`；日誌側 `jedi_iam/app/service/*_service.py` |
| **駭客怎麼打** | ① 能用儀表板的帳號；② 問一句「列出使用者」；③ 回應裡每個帳號都附著密碼加密鹽值與「是不是超級管理員」旗標；④ 這些內容同樣送進外部 AI 的提示 |
| **得手什麼** | 帳號密碼加密材料與最高權限帳號名單 |
| **修正的做法** | ① 序列化工具新增敏感欄位不輸出名單（鹽值、密碼、超管旗標、各類權杖），物件與字典兩個分支都擋；② 日誌側：身分套件查詢的記錄只寫筆數，不再把整筆帳號印進日誌（CM-2269） |
| **狀態** | ✅ 已修（CM-2064，套件 `248132ec`；日誌側 CM-2269 套件 `f679363d`） |

## M03-7 🟡 資料庫隔離規則方向寫反，子單位看得到還改得到母單位資料 {#m03-7}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #104；STRIDE：竄改資料、資料外洩） |
| **在哪裡** | 弱點檢測等模組的資料庫隔離規則（出貨基線共 11 條） |
| **攻擊面位置** | 任一子單位帳號，經任何會讀寫這些表的功能 |
| **信任邊界位置** | **子租戶 ↔ 母租戶**。規則把租戶路徑拆成陣列比對，子單位路徑包含母單位編號就放行；母單位反而看不到子單位 |
| **元件端點位置** | 資料表 `agent_tasks`、`detection_execution_groups`、`detection_executions`、`jedta`、`jedt`、`tdtc`、授權三張；匯入兩張 `oscal.ap_docx_parse_jobs`、`oscal.ar_xlsx_parse_jobs`；主線 `scripts/sql/2026-09-28-fr114-rls-tenant-prefix-unify.sql`、`2026-09-25-fr114-ap-ar-parse-jobs-rls-fix.sql` |
| **駭客怎麼打** | ① 子單位成員；② 打開檢測工具設定；③ 看到母單位登記的工具並能改；④ 再按「測試連線」（M03-2）把母單位的帳密送到自己的主機 |
| **得手什麼** | 母單位的檢測設定與帳密，並能改刪 |
| **修正的做法** | ① 九張表改成前綴比對 `app_tenant_allowed_for_session`，逐條改不批次取代；② 套件 migration 同步改，已裝機靠主線 migration 重建；③ 新增守衛測試，全庫不准再出現拆陣列寫法；④ 匯入兩張（文字包含比對、拿單位編號比使用者編號）以標準四條重建（CM-2195）；⑤ 授權三張的讀取另開祖先可讀（CM-2369，見 M18-8） |
| **狀態** | ✅ 已修（CM-2271，BE `6c74ff0e3`／套件 `6e0d0d4b`；匯入兩張 CM-2195 `56dd4cdc8`，1.21.0 出貨）。輸入檔記匯入兩張為 CM-1770，查 commit 實為 CM-2195 |

## M20-5 🟡 SaaS 版合規文件整塊沒有最後一道防線 {#m20-5}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #105；STRIDE：資料外洩） |
| **在哪裡** | 客戶資料隔離：合規文件一整塊（系統安全計畫、稽核計畫、稽核結果、稽核發現、矯正措施） |
| **攻擊面位置** | 多家客戶共用的 SaaS 版才有；落地版一家一套，不受影響 |
| **信任邊界位置** | **租戶 ↔ 租戶**（資料庫隔離牆）。這些表連客戶歸屬欄位都沒有，靠「屬於哪個專案」間接連到客戶，牆沒東西可依據 |
| **元件端點位置** | `oscal.*` schema 下 102 張沒有隔離的表（如系統安全計畫、稽核發現相關表）；應用層守門見 M11-1、M10-4 等 |
| **駭客怎麼打** | ① SaaS 版上另一家客戶的帳號；② 任何應用層守門漏掉的一條路；③ 資料庫不會再擋；④ 實測用一把指向不存在客戶的鑰匙去查：專案 0 筆，但系統安全計畫 639 筆、稽核發現 525 筆 |
| **得手什麼** | 別家客戶的合規文件 |
| **修正的做法** | 未修，清單已盤好：① 補「父表看得到、子表才看得到」的跟隨規則，不加欄位、不回填；② 53 張要補、49 張確認不用，真正要設計的只有 4 個接點；③ 依決策者裁定排在程式面守門修完並測過一版之後 |
| **狀態** | 🕓 SaaS 前必做（尚無修正 commit） |

## M16-1 🟡 「寄測試信」把存著的郵件密碼送到操作者指定的主機 {#m16-1}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #107；STRIDE：資料外洩） |
| **在哪裡** | 寄信與通知模組：郵件設定頁的「寄測試信」 |
| **攻擊面位置** | 持有「改寄信設定」權限的下層客戶管理員 |
| **信任邊界位置** | **子租戶 ↔ 上層**，以及**後端 ↔ 外部郵件主機**。寄信設定全系統一份在最上層，權限卻下放到各下層；密碼欄留空時系統沿用存著的真密碼去連 |
| **元件端點位置** | `jedi_notification/api/routes/mail_route.py`：`POST /api/1.0/mail/test`（`SendMailTestRoute`）→ 主專案 `app/notification/service/test_mail_service.py` 的 `send_test_mail` |
| **駭客怎麼打** | ① 下層管理員；② 打開郵件設定，伺服器位址改成自己的機器；③ 密碼欄不動，按「寄測試信」；④ 系統拿存著的上層密碼去登入他的機器，他拿到可通過寄件人驗證的郵件帳密 |
| **得手什麼** | 上層郵件帳密（範圍在同一客戶內部） |
| **修正的做法** | 權限面裁定不修（寄信設定屬最上游帳號權責，要不要下放由客戶決定）；程式面已加強：送來的伺服器位址或埠號與存著的不同時，不沿用存著的密碼、回 400 要求重填（新碼 `NOTIFY_400005`） |
| **狀態** | 🚫 裁定不修（權限面）；程式面加強 CM-2111，BE `e3c726896` |

## M20-4 🟡 兩支半夜清理程式靠「找不到身分就給最高權限」才跑得動 {#m20-4}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #108；STRIDE：權限提升、讓服務停擺） |
| **在哪裡** | 客戶資料隔離：兩支凌晨排程（框架解析工作清理、任務綁定孤兒清理） |
| **攻擊面位置** | 不是外部可打的入口，是規劃上的相依：收緊 M04-2 那條規則時會連帶出事 |
| **信任邊界位置** | **排程 ↔ 資料庫**。排程沒有使用者身分，原本靠「沒身分＝最高權限」跨客戶掃描；收緊後會安靜掃到 0 筆 |
| **元件端點位置** | 主專案 `core/scheduler.py` 的 `framework_parse_job_cleanup`（01:00）、`job_binding_orphan_cleanup`（03:10）；`app/flow_control/service/job_binding_orphan_cleanup_service.py` |
| **駭客怎麼打** | 不是攻擊路徑：① 有人收緊「沒身分給最高權限」；② 兩支排程拿不到資料；③ 不報錯、不留紀錄；④ 殘留資料一直堆積沒人發現 |
| **得手什麼** | （無攻擊者）清理功能安靜失效 |
| **修正的做法** | ① 兩支改用具名系統身分 `system_context("<排程名>")` 顯式宣告跨客戶，舊說明改寫成反向警語；② 新增守衛測試掃描排程程式碼；③ 後續其餘七支排程一併接上（CM-1787）；④ 孤兒清理加防呆：本輪看得到 0 筆任務代表身分沒設好，記錯誤、不刪（CM-2211） |
| **狀態** | ✅ 已修（CM-1767 BE `3af065133`；CM-1787 `1125ceee1`；防呆 CM-2211 `fe928859f`） |

## M11-12 🟡 把別人的程序書掛到自己控制項再下載 {#m11-12}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #153；STRIDE：資料外洩、竄改資料） |
| **在哪裡** | 合規文件核心：把程序書掛到控制項或查核項目 |
| **攻擊面位置** | 任一專案的管理者（自己開一個就有） |
| **信任邊界位置** | **計畫 ↔ 計畫**。文件編號換內部編號時查全系統，不限本計畫的文件池 |
| **元件端點位置** | 主專案 `api/oscal/__init__.py`：`POST /api/1.0/ssp/{ssp_uid}/control-implementation/{control_identifier}/document-mappings`、`…/objective/{statement_identifier}/document-mappings` → `ssp_document_pool_service.add_control_mappings`／`add_ao_mappings`；套件 `ssp_document_pool_query.resolve_doc_uids_to_ids` |
| **駭客怎麼打** | ① 自己開一個專案當負責人；② 拿到別專案的程序書文件編號；③ 掛到自己的控制項上；④ 掛載清單回應附上檔案編號，拿去打下載就把別人的程序書整份抓走 |
| **得手什麼** | 同公司別專案的機密程序書 |
| **修正的做法** | ① 套件換算函式的計畫編號改必填，只查這份計畫池子裡的文件；② 主專案五處呼叫全帶計畫編號；③ 掛載時有一筆不在池內就整批 404，檢查放在建立實作紀錄之前；④ 範本匯入那處一併補 |
| **狀態** | ✅ 已修（FR-114 CM-2173，套件 `5170e507`／BE `c4b6bdb16`，1.21.0 出貨） |

## M11-27 🟡 刪整個合規框架不檢查有沒有客戶在用 {#m11-27}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #156；STRIDE：竄改資料） |
| **在哪裡** | 合規文件核心：平台管理員刪除框架、刪除版本、編輯公版目錄、匯入覆蓋 |
| **攻擊面位置** | 平台管理員（屬誤操作釀災） |
| **信任邊界位置** | **平台 ↔ 客戶資源庫**。後端不檢查引用，把關交給前端藏按鈕；原以為能照抄的「引用檢查」查錯欄位，永遠是零筆 |
| **元件端點位置** | 主專案 `api/oscal/__init__.py`：`DELETE /api/1.0/oscal-framework/{uid}`、`DELETE /api/1.0/oscal-framework-version/{uid}`、`/oscal-catalog-*` 編輯、`POST /api/1.0/oscal-framework-parse-jobs/{uid}/confirm`；新查詢 `infra/readmodel/oscal/framework_version_usage_query.py` |
| **駭客怎麼打** | ① 平台管理員；② 刪掉一個版本或改公版目錄；③ 後端不檢查有沒有客戶資源庫在用；④ 客戶資源庫指向不存在的版本，列表框架名稱空掉、用 Word 更新時找不到目錄 |
| **得手什麼** | （誤操作）客戶資源庫失去框架來源 |
| **修正的做法** | ① 新增查詢：數「從這個版本建立、未刪除的資源庫」，用獨立唯讀身分跨客戶查，查不了就報錯而不是回 0；② 刪版本、刪框架、改公版目錄、匯入覆蓋四處，有引用就 409（新碼 `GRC_409038`）；③ 前端補三語訊息 |
| **狀態** | ✅ 已修（FR-114 CM-2180，BE `7262b74c8`，1.21.0 出貨） |

## M11-14 🟡 沒被授權的員工直接打網址讀走範本清單與全文 {#m11-14}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #157；STRIDE：資料外洩） |
| **在哪裡** | 合規文件核心：合規範本的清單與完整內容 |
| **攻擊面位置** | 沒被授權看範本的員工（選單看不到），任一登入帳號 |
| **信任邊界位置** | **使用者 ↔ 範本服務**。同檔新增、修改、刪除、複製都有能力點，兩支讀取漏掉；公版與分享範本跨公司也讀得到 |
| **元件端點位置** | 主專案 `api/module_frame/routes/module_frame_route.py`：`GET /api/1.0/module-frames/menu`（`ModuleFramesMenuRoute`）、`GET /api/1.0/module-frame/{uid}`（`ModuleFrameRoute.get`） |
| **駭客怎麼打** | ① 沒有看範本權限的員工；② 選單看不到，但直接打範本清單網址；③ 系統只驗登入；④ 讀走全公司範本清單與每一份完整內容 |
| **得手什麼** | 全公司範本與原廠公版內容 |
| **修正的做法** | ① 單筆讀取掛 `module-frame.read`；② 清單依裁定收「看範本或建專案」任一（建專案畫面的下拉在用）；③ 同批補 migration：給有建專案或管理範本權限的既有角色補上看範本，避免下拉安靜變空 |
| **狀態** | ✅ 已修（FR-114 CM-2186，BE `aafaa070d`，1.21.0 出貨） |

## M11-15 🟡 任何人讀走範本的稽核流程圖 {#m11-15}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #158；STRIDE：資料外洩） |
| **在哪裡** | 合規文件核心：範本的稽核流程圖（子項） |
| **攻擊面位置** | 任一登入帳號 |
| **信任邊界位置** | **使用者 ↔ 範本服務**。同檔寫入要權限，讀取不用 |
| **元件端點位置** | 主專案 `api/module_frame/routes/module_frame_item_route.py`：`GET /api/1.0/module-frame/item/{uid}`（`ModuleFrameItemRoute.get`） |
| **駭客怎麼打** | ① 任一登入帳號；② 拿範本子項編號打讀取；③ 系統只驗登入；④ 讀走一次稽核要走哪些步驟、每步誰負責、順序 |
| **得手什麼** | 範本的稽核流程設計 |
| **修正的做法** | 讀取補 `module-frame.read`，並先確認既有角色都拿得到這顆（同批 migration 補），避免所有人永遠被拒且畫面沒有錯誤 |
| **狀態** | ✅ 已修（FR-114 CM-2186，BE `aafaa070d`，1.21.0 出貨） |

## M11-6 🟡 改範本流程圖只有順便改「分享設定」才檢查歸屬 {#m11-6}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #159；STRIDE：竄改資料） |
| **在哪裡** | 合規文件核心：改範本的稽核流程圖 |
| **攻擊面位置** | 有範本修改權限的管理員 |
| **信任邊界位置** | **租戶 ↔ 原廠公版／母公司**。歸屬檢查寫在「有送分享欄位」的判斷式裡面，不送那個欄位就跳過 |
| **元件端點位置** | 主專案 `api/module_frame/routes/module_frame_item_route.py`：`PUT /api/1.0/module-frame/item/xml/{uid}`（`ModuleFrameItemXmlRoute`）→ `module_frame_item_service.update_module_frame_item_xml` |
| **駭客怎麼打** | ① 有範本修改權限的管理員；② 打開原廠公版或母公司範本的流程圖；③ 送修改請求、刻意不帶分享欄位；④ 歸屬檢查被跳過，別人的稽核流程被改掉 |
| **得手什麼** | 改掉原廠公版或母公司範本的稽核流程 |
| **修正的做法** | 方法一進來就撈範本（不存在 404）並驗歸屬，放在同步流程與分享分支之前，分享分支沿用已撈好的範本 |
| **狀態** | ✅ 已修（FR-114 CM-2178，BE `abf1cf8a4`，1.21.0 出貨） |

## M11-7 🟡 檢查網址上的子項、動手改的卻是另外送來的範本 {#m11-7}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #160；STRIDE：竄改資料） |
| **在哪裡** | 合規文件核心：刪或改範本子項 |
| **攻擊面位置** | 有範本修改／刪除權限的人 |
| **信任邊界位置** | **網址 ↔ 請求內容**。權限檢查看網址上的子項，實際動手改的是請求內容另外帶的主範本，兩者不核對 |
| **元件端點位置** | 主專案 `api/module_frame/routes/module_frame_item_route.py`：`PUT／DELETE /api/1.0/module-frame/item/{uid}` → `module_frame_item_service.update_module_frame_item`／`delete_module_frame_item`，新增 `_verify_item_belongs` |
| **駭客怎麼打** | ① 有範本權限的人；② 網址填自己範本的子項；③ 請求內容的主範本編號填別人的；④ 刪掉的子項與流程圖上被拿掉的方塊屬於不同範本，資料悄悄對不上 |
| **得手什麼** | 讓別人範本的流程圖與子項錯位 |
| **修正的做法** | 新增 `_verify_item_belongs`：核對請求指的主範本流程圖裡確實有一個節點指向網址上的子項（有帶節點編號時也要對上），再驗主範本與子項的歸屬 |
| **狀態** | ✅ 已修（FR-114 CM-2178，BE `abf1cf8a4`，1.21.0 出貨） |

## M11-16 🟡 列出範本掛的程序書清單再拿編號下載 {#m11-16}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #161；STRIDE：資料外洩） |
| **在哪裡** | 合規文件核心：範本的參考文件（程序書）清單 |
| **攻擊面位置** | 任一登入帳號 |
| **信任邊界位置** | **使用者 ↔ 範本服務**，以及**範本服務 ↔ 檔案服務**。清單附檔案編號，下載那支當時只認編號 |
| **元件端點位置** | 主專案 `api/module_frame/__init__.py`：`GET /api/1.0/module-frame/{uid}/reference-documents`（`ModuleFrameReferenceDocumentListResource`）；下載 `GET /api/1.0/file/download/{uid}` |
| **駭客怎麼打** | ① 任一登入帳號；② 打範本的程序書清單；③ 回應附每份的檔案編號；④ 拿去打下載網址，整份程序書下來 |
| **得手什麼** | 範本附的程序書 |
| **修正的做法** | ① 列清單要 `module-frame.read`（CM-2186）；② 下載走檔案歸屬檢查，跨公司一律拒絕（CM-2033）；③ 同公司可下載範本程序書屬刻意設計（範本是公司共用資源） |
| **狀態** | ✅ 已修（CM-2186 BE `aafaa070d`；CM-2033 BE `a334f5385`，1.21.0 出貨） |

## M11-17 🟡 任何人讀走範本的稽核目標預設內容 {#m11-17}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #162；STRIDE：資料外洩） |
| **在哪裡** | 合規文件核心：範本的「稽核目標預設內容」 |
| **攻擊面位置** | 任一登入帳號 |
| **信任邊界位置** | **使用者 ↔ 範本服務**。同檔修改、刪除要權限，隔壁同類檔四個入口全掛齊，這兩支讀取漏掉 |
| **元件端點位置** | 主專案 `api/module_frame/__init__.py`：`GET /api/1.0/module-frame/{uid}/objective-defaults`、`GET /api/1.0/module-frame/{uid}/objective-defaults/{objective_default_uid}`（`module_frame_control_objective_default_route.py`） |
| **駭客怎麼打** | ① 任一登入帳號；② 拿範本編號打稽核目標預設內容；③ 系統只驗登入；④ 讀走每條目標的完成狀態、實作說明、備註 |
| **得手什麼** | 範本的稽核目標預設內容 |
| **修正的做法** | 兩支讀取補 `module-frame.read`，與同批其他範本讀取一起上 |
| **狀態** | ✅ 已修（FR-114 CM-2186，BE `aafaa070d`，1.21.0 出貨） |

## M11-4 🟡 拿一個範本編號讀走範本裡的人員、設備、系統清冊 {#m11-4}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #163；STRIDE：資料外洩） |
| **在哪裡** | 合規文件核心：範本的人員、設備、系統元件、繼承授權、系統特性 |
| **攻擊面位置** | 任一登入帳號；原廠公版與母公司分享的範本跨公司也讀得到 |
| **信任邊界位置** | **使用者 ↔ 範本服務**。同檔新增、修改、刪除都有能力點，讀取漏掉 |
| **元件端點位置** | 主專案 `api/module_frame/__init__.py`：`GET /api/1.0/module-frame/{uid}/parties`、`/inventory`、`/components`、`/leveraged`、`/system-characteristic` |
| **駭客怎麼打** | ① 任一登入帳號；② 拿一個範本編號（公版或分享的也行）；③ 打這五個讀取入口；④ 拿到人員姓名、email、電話、地址，設備資產編號、IP、主機名，系統開放的協定與埠號，向哪些外部供應商繼承了哪些控制 |
| **得手什麼** | 現成的踩點資料 |
| **修正的做法** | 五個讀取入口各補 `module-frame.read`，一張卡一起補，搭配既有角色補權限 migration |
| **狀態** | ✅ 已修（FR-114 CM-2186，BE `aafaa070d`，1.21.0 出貨） |

## M11-8 🟡 改原廠或母公司範本底下六類附屬資料不問歸屬 {#m11-8}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #164；STRIDE：竄改資料） |
| **在哪裡** | 合規文件核心：範本底下人員、元件、設備、繼承授權、系統特性、設備對照的新增修改刪除 |
| **攻擊面位置** | 有範本寫入權限的公司管理員 |
| **信任邊界位置** | **租戶 ↔ 原廠公版／母公司**。資料存在沒有客戶欄位的表，只能在程式擋；六支服務只查範本存在 |
| **元件端點位置** | 主專案 `api/module_frame/__init__.py`：`POST／PUT／DELETE /api/1.0/module-frame/{uid}/parties…`、`/components…`、`/inventory…`、`/leveraged…`、`PUT …/system-characteristic`、`/ssp-resources/items…`；服務 `app/module_frame/service/module_frame_{party,components,inventory,leveraged,system_characteristic,ssp_resources}_service.py` |
| **駭客怎麼打** | ① 子公司管理員；② 打開母公司或原廠範本；③ 改掉系統特性（一次請求整份蓋掉）或刪掉繼承授權（相關連結悄悄斷掉）；④ 之後依這份範本開的專案全繼承被改過的基準 |
| **得手什麼** | 竄改共用範本的基準內容 |
| **修正的做法** | ① 六支服務既有的取範本小函式加 `writable` 參數，寫入時才呼叫 `assert_resource_tenant_writable`，讀取行為不變；② 用這支而不是「改分享設定」那支，因為後者不豁免平台管理員、會把公版維運鎖在外面；③ 六支一起補 |
| **狀態** | ✅ 已修（FR-114 CM-2187，BE `3f15886bb`，1.21.0 出貨） |

## M11-9 🟡 用 Excel 批次存範本控制項清單不查歸屬 {#m11-9}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #165；STRIDE：竄改資料） |
| **在哪裡** | 合規文件核心：範本控制項清單的 Excel 批次存檔 |
| **攻擊面位置** | 有建立範本權限的人 |
| **信任邊界位置** | **租戶 ↔ 原廠公版／母公司**。只查範本存在 |
| **元件端點位置** | 主專案 `api/module_frame/__init__.py`：`POST /api/1.0/module-frame/{uid}/control-defaults/import/validate`、`POST /api/1.0/module-frame/{uid}/control-defaults/import` → `module_frame_template_import_service.validate_items`／`save_import` |
| **駭客怎麼打** | ① 有建立範本權限的人；② 下載原廠公版範本的控制項 Excel；③ 改內容後以那份範本編號批次存檔；④ 系統只查範本存在就寫入 |
| **得手什麼** | 改掉公版或分享範本的控制項清單 |
| **修正的做法** | 檢核與存檔兩步都在解出範本後補歸屬檢查（`assert_resource_tenant_writable`），超標檔只加兩行呼叫 |
| **狀態** | ✅ 已修（FR-114 CM-2187，BE `3f15886bb`，1.21.0 出貨） |

## M11-10 🟡 只有「建立」權限的人走匯入改掉既有範本 {#m11-10}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #168；STRIDE：權限提升、竄改資料） |
| **在哪裡** | 合規文件核心：Excel 整批匯入範本 |
| **攻擊面位置** | 只被授權建立範本、沒被授權修改範本的人 |
| **信任邊界位置** | **能力點 ↔ 能力點**。畫面上改要「修改」權限，走匯入覆蓋只要「建立」權限，兩個入口兩套門 |
| **元件端點位置** | 主專案 `api/module_frame/routes/module_frame_import_route.py`：`POST /api/1.0/module-frame/import`（`ModuleFrameImportDataRoute`）→ `save_import_module_frame_data` |
| **駭客怎麼打** | ① 只有建立權限的人；② 準備一份 Excel，帶上既有範本編號；③ 送整批匯入；④ 系統照建立權限放行，既有範本整份被改 |
| **得手什麼** | 用建立權限改掉既有範本 |
| **修正的做法** | 決策者裁定「只有建立權限不能覆蓋」：匯入帶既有範本編號（覆蓋）時另查 `module-frame.update`，錯誤碼沿用 `GRC_403022` |
| **狀態** | ✅ 已修（FR-114 CM-2178，BE `abf1cf8a4`，1.21.0 出貨） |

## M06-17 🟡 規劃頁任務清單不問你是不是這個專案的人 {#m06-17}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #169；STRIDE：資料外洩） |
| **在哪裡** | 稽核流程：規劃頁的任務清單 |
| **攻擊面位置** | 同一家客戶裡任一有專案模組的帳號 |
| **信任邊界位置** | **使用者 ↔ 任務服務**。清單不驗成員，專案編號可不帶 |
| **元件端點位置** | 主專案 `api/flow_control/__init__.py`：`POST /api/1.0/grc/project/{project_uid}/ap/{ap_uid}/control-group/{group_uid}/control/{control_uid}/assessment-object/{ao_uid}/jobs/list`（`ProjectJobListResource`）→ `job_service.list_jobs` |
| **駭客怎麼打** | ① 同客戶任一有專案模組的帳號；② 帶別專案的編號，查核項目換成公開的標準條號一條條查；③ 系統不問成員；④ 列出每個查核項目底下的任務（指派人、部門、設備、問卷），任務編號正是改刪別人任務的材料 |
| **得手什麼** | 別專案的任務配置與任務編號 |
| **修正的做法** | ① `list_jobs` 開頭補參與者檢查：解析專案（沒帶或查無 404）→任一成員；② 角色服務沒注入時 fail-closed 403 |
| **狀態** | ✅ 已修（CM-2191，BE `33aab8cba`，1.21.0 出貨） |

## M06-18 🟡 匯入任務 Excel 的「確認」步驟改掉別專案的任務 {#m06-18}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #170；STRIDE：竄改資料） |
| **在哪裡** | 稽核流程：批次匯入任務 Excel 的確認步驟 |
| **攻擊面位置** | 任一專案的管理者 |
| **信任邊界位置** | **專案 ↔ 專案**。驗證步驟會報錯，確認步驟卻不重跑比對，白名單只查「任務存在」 |
| **元件端點位置** | `jedi_task_platform` 路由：`POST /api/1.0/grc/project/{project_uid}/ap/{ap_uid}/jobs/import/confirm`（`JobImportConfirmRoute`）→ 主專案 `app/flow_control/service/job_import_service.py` |
| **駭客怎麼打** | ① 專案 A 的管理者；② 匯出自己的任務 Excel，把「任務識別碼」欄換成專案 B 的任務、指派人填自己；③ 驗證報錯，但直接按確認；④ 一次批次改掉 B 的任務指派人、類型、部門、設備 |
| **得手什麼** | 批次竄改別專案的任務配置 |
| **修正的做法** | 新增 `_assert_plan_in_project`，匯出／驗證／重新驗證／確認四入口都呼叫：① 專案查無 404；② 計畫解出的專案須等於網址專案；③ 計畫下每張任務以唯一歸屬判斷批次核對，不符 404 |
| **狀態** | ✅ 已修（CM-2191，BE `33aab8cba`，1.21.0 出貨） |

## M06-19 🟡 把別專案卡住的任務「強制開始」 {#m06-19}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #171；STRIDE：竄改資料、事後無法追查） |
| **在哪裡** | 稽核流程：卡在合流的任務「強制開始」 |
| **攻擊面位置** | 任一專案的管理者 |
| **信任邊界位置** | **專案 ↔ 任務**。當初為避免用指派表誤擋而改信網址專案，任務歸屬不核對 |
| **元件端點位置** | 主專案 `api/flow_control/__init__.py`：`POST /api/1.0/grc/project/{project_uid}/job/{job_uid}/force-start`（`JobForceStartResource`）→ `job_force_start_service._load` |
| **駭客怎麼打** | ① 專案 A 的管理者；② 網址填自己專案、任務填專案 B 一張卡在合流的任務；③ 按強制開始；④ B 的主管收到通知但分支證據沒收齊，稽核事件卻記在 A 名下，事後看不出動的是 B |
| **得手什麼** | 推動別專案的稽核流程並混淆紀錄 |
| **修正的做法** | 查到任務後以全系統唯一的輪次鏈判斷比對網址專案與任務真正歸屬，不符或查無回 404；查詢與強制開始都走同一個載入函式 |
| **狀態** | ✅ 已修（CM-2113，BE `f52a53f1e`，1.21.0 出貨） |

## M07-13 🟡 把 AI 服務位址填成自己的機器騙走原廠金鑰 {#m07-13}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #172；STRIDE：資料外洩） |
| **在哪裡** | 證據自動分類：AI 服務設定與分類執行時組金鑰 |
| **攻擊面位置** | 客戶管理員；前提是「AI 金鑰只限平台管理員」開關放開（檢視時關著） |
| **信任邊界位置** | **客戶 ↔ 原廠**，以及**後端 ↔ 外部 AI 服務**。金鑰與位址各自往上層找，可組出「原廠金鑰＋客戶位址」；新客戶建立時還整格照抄原廠金鑰 |
| **元件端點位置** | 主專案 `app/system_config/service/ai_provider_key_resolver.py`、`tenant_storage_config_seeder.py`、`core/plugins/evidence_classification.py`；設定經 `PUT /api/1.0/system/config/{group}/{key}`（`AI_PROVIDER_CONFIG`） |
| **駭客怎麼打** | ① 開關放開後的客戶管理員；② 在 AI 服務設定把位址填成自己的機器、金鑰留空；③ 跑一次證據分類；④ 系統拿原廠金鑰放在授權標頭送到他的機器 |
| **得手什麼** | 原廠 AI 金鑰 |
| **修正的做法** | ① 新增 `resolve_credentials`：位址只取自金鑰所在同一層，金鑰落到環境變數時位址只認最上層；② 證據分類改用它；③ 建新租戶時拿掉每家供應商的密鑰欄再抄；④ 開關說明改寫「放開之前必須先做完前兩項」 |
| **狀態** | ✅ 已修（CM-2192，BE `21874f869`，1.21.0 出貨） |

## M24-3 🟡 上傳的檔案整個資料夾開在網站公開路徑下 {#m24-3}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #203；STRIDE：資料外洩） |
| **在哪裡** | 主系統：Flask 預設替 `static` 資料夾註冊的公開路徑 |
| **攻擊面位置** | 不需帳號，直連後端埠即可。出貨的網站前門只放行兩條路徑擋著，有人為排錯開了後端埠或換代理設定就整包公開 |
| **信任邊界位置** | **網際網路 ↔ 後端**。所有下載都該走要登入的功能，框架卻預設多開了一條不驗身分的靜態檔路徑 |
| **元件端點位置** | 主專案 `core/app_factory.py`（Flask 建構參數）；路徑 `GET /static/{path}` |
| **駭客怎麼打** | ① 不需帳號；② 找到直接開出來的後端埠；③ 打 `/static/` 加上問卷答案附件、意見回饋附件或匯出檔的路徑；④ 檔案直接下來 |
| **得手什麼** | 使用者上傳的附件與匯出檔 |
| **修正的做法** | ① 建構時 `static_folder=None` 不註冊路由（卡上寫的 `static_url_path=None` 實測在 Flask 3.1 會退回預設、擋不住）；② 建構後再把資料夾路徑指回原處，讓問卷備份等寫檔功能照舊拿得到；③ 先確認全專案沒有地方組 `/static/` 網址 |
| **狀態** | ✅ 已修（8-I，CM-2219，BE `782ebb8b3`，1.21.0 出貨） |

## M24-8 🟡 雲端硬碟資料夾設成「知道連結誰都能編輯」 {#m24-8}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #205；STRIDE：資料外洩、竄改資料） |
| **在哪裡** | 主系統雲端硬碟整合：建立專案資料夾時設定的分享權限 |
| **攻擊面位置** | 公司任一登入帳號（查得到根資料夾編號）；拿到連結的任何人 |
| **信任邊界位置** | **本系統 ↔ Google 雲端硬碟**。資料夾權限設成「知道連結的任何人可編輯」，權限邊界交給連結保密 |
| **元件端點位置** | 主專案 `infra/cloud_integration/google_drive/google_drive_api_client.py` 的 `share_anyone_writer`（呼叫端 `create_folder_handler.py`、`archive_drive_file_handler.py`、`drive_sync_orchestration_service.py`）；根資料夾編號經 `GET /api/1.0/integrations/google-drive` 回傳 |
| **駭客怎麼打** | ① 公司任一登入帳號；② 從雲端硬碟整合資訊拿到根資料夾編號；③ 組成連結打開；④ 看、改、刪全公司的證據檔，被改的檔下一輪同步還會被當正常證據匯入 |
| **得手什麼** | 全公司的雲端證據檔讀寫權 |
| **修正的做法** | 裁定不修（決策者 10-01）：雲端硬碟的分享權限未來由權限管理控管，或交由客戶在自己的 Google 設定處理，產品不另外管 |
| **狀態** | 🚫 裁定不修 |

## M24-4 🟡 兩家客戶接同一個 Google 帳號，同步會互相打到對方證據 {#m24-4}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #206；STRIDE：竄改資料） |
| **在哪裡** | 主系統雲端硬碟整合：背景同步與授權接線 |
| **攻擊面位置** | 不必有人攻擊；兩家客戶誤接同一個 Google 帳號即觸發，也可被刻意利用 |
| **信任邊界位置** | **租戶 ↔ 租戶**。同步用 Google 編號找對應任務不帶租戶，同一帳號的變動通知又混在一起送 |
| **元件端點位置** | 主專案 `api/cloud_integration/routes/google_drive_integration_route.py`：`GET /api/1.0/integrations/google-drive/callback`（`GoogleDriveCallbackRoute`）→ `google_drive_integration_service.handle_callback`；新查詢 `infra/cloud_integration/repository/drive_account_ownership_query_impl.py` |
| **駭客怎麼打** | ① 客戶 A 與 B 接了同一個 Google 帳號；② A 在雲端硬碟改或刪一個檔；③ 背景同步用 Google 編號找到 B 的任務；④ B 的證據被標成已刪除或指錯任務（開發環境重現過） |
| **得手什麼** | 別家客戶的稽核證據被誤改 |
| **修正的做法** | 決策者裁定：① 授權回呼取得 email 後，查「別的租戶、狀態連線中或過期、email 相同」就 409（新碼 `GRC_409039`），同租戶重新授權照常；② 跨租戶查詢用既有的提權唯讀樣板，經 DI 注入；③ 資料庫補唯一約束 migration；④ 前端補三語訊息 |
| **狀態** | ✅ 已修（CM-2204，BE `e00f5ed04`／前端 `2066e04`，1.21.0 出貨） |

## M10-20 🟡 摘要報告 PDF 插圖白名單放行 SVG，讓伺服器替你連內網 {#m10-20}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #208；STRIDE：資料外洩） |
| **在哪裡** | 專案摘要報告：匯出 PDF |
| **攻擊面位置** | 能編輯報告的專案成員 |
| **信任邊界位置** | **後端 ↔ 內網**。PDF 在伺服器端排版，插圖白名單放行 SVG，SVG 內部可再引用外部網址，排版器就替人發請求 |
| **元件端點位置** | 主專案 `api/project_summary_report/routes/project_summary_report_route.py`：`GET /api/1.0/project-summary-report/export/{uid}`（`ProjectSummaryReportExportReportRoute`） |
| **駭客怎麼打** | ① 能編輯報告的成員（資訊不足，僅依總表描述展開）；② 在報告插一張 SVG，裡面引用公司內網位址；③ 匯出 PDF；④ 伺服器替他去連內網，先前建的防護被繞過 |
| **得手什麼** | 借伺服器連內網 |
| **修正的做法** | 兩道都做：① 插圖白名單排除 SVG；② 排版器帶自訂取資源函式，非內嵌資料的來源一律拋錯，不對外發任何請求（樣板本身無外部資源，不影響排版） |
| **狀態** | ✅ 已修（8-C，CM-2213，BE `a5804a990`／`bde8f9d16`，1.21.0 出貨） |

## M10-21 🟡 還原報告歷史版本不檢查那版本是不是這份報告的 {#m10-21}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #209；STRIDE：竄改資料） |
| **在哪裡** | 專案摘要報告：還原歷史版本 |
| **攻擊面位置** | 專案成員 |
| **信任邊界位置** | **報告 ↔ 報告**。只驗專案參與者，不驗歷史版本屬於哪份報告 |
| **元件端點位置** | 主專案 `api/project_summary_report/routes/project_summary_report_history_route.py`：`POST /api/1.0/project-summary-report/history-revert`（`RevertProjectSummaryReportHistoryRoute`）→ `project_summary_report_history_service` |
| **駭客怎麼打** | ① 某專案成員（資訊不足，僅依總表描述展開）；② 拿別份報告的歷史版本編號；③ 在自己的報告按還原；④ 別份報告的內容蓋進這份 |
| **得手什麼** | 把別份報告內容蓋進來 |
| **修正的做法** | 取出歷史與報告後比對歷史所屬報告與這份報告，不符回 404 |
| **狀態** | ✅ 已修（8-A，CM-2211，BE `fe928859f`，1.21.0 出貨） |

## M24-7 🟡 診斷指令用猜得到的暫存夾、不檢查捷徑 {#m24-7}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #210；STRIDE：權限提升、竄改資料） |
| **在哪裡** | 主系統安裝程式：系統診斷指令 |
| **攻擊面位置** | 需主機或容器內的一般權限 |
| **信任邊界位置** | **容器內一般權限 ↔ 主機最高權限**。診斷指令以最高權限執行，建暫存夾用猜得到的名字、不檢查是不是捷徑 |
| **元件端點位置** | 主專案 `scripts/installer/guidant` 的 diag 段 |
| **駭客怎麼打** | ① 容器裡只有一般權限的人；② 預先放一個同名捷徑指向主機上的重要位置；③ 等管理員執行診斷；④ 最高權限帳號把資料寫進他指定的地方（執行者已重現） |
| **得手什麼** | 借最高權限改主機上的檔案 |
| **修正的做法** | ① 交換目錄改用系統產生的隨機名；② 建立前確認上層目錄存在且不是捷徑；③ 給容器寫的子目錄從全開改成限定擁有者；④ 找產出檔只收一般檔案；⑤ 退回後補：進交換目錄後一律用相對路徑寫檔 |
| **狀態** | ✅ 已修（8-D，CM-2214，BE `d39181f3b`／`046553756`，1.21.0 出貨） |

## M02-12 🟡 刪原廠共用檔，檔案本體先被刪、紀錄刪不掉，全客戶受害 {#m02-12}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | 🟡 中（總表 #212；STRIDE：讓服務停擺、竄改資料） |
| **在哪裡** | 檔案上傳模組：刪除檔案 |
| **攻擊面位置** | 一般公司的任一登入帳號，知道原廠共用檔（例如範本）的編號 |
| **信任邊界位置** | **客戶 ↔ 原廠公版**。刪除先刪實體再刪紀錄，資料庫那步被擋時實體已沒了，畫面還回成功 |
| **元件端點位置** | `jedi_file_upload/api/routing.py`：`DELETE /api/1.0/file/upload/{uid}`（`UploadFileRoute.delete`）→ `infra/adapter/minio/minio_adapter.py`、`local/local_file_adapter.py` 的 `delete_file` |
| **駭客怎麼打** | ① 任一家公司的帳號；② 拿到原廠共用範本檔的編號；③ 送刪除；④ 硬碟上的本體先被刪，資料庫紀錄因不是原廠而刪不掉，所有客戶的範本檔一起打不開 |
| **得手什麼** | 讓所有客戶的共用範本失效 |
| **修正的做法** | ① 刪除入口補歸屬檢查，原廠共用檔對一般公司禁刪（CM-2033 的寫入規則）；② 新增共用小工具：先刪紀錄，確認真的刪掉才刪實體；批次刪以差集算出實際刪掉哪幾筆，只對那些刪實體；③ 實體刪失敗只記日誌（留孤兒實體比壞紀錄好） |
| **狀態** | ✅ 已修（CM-2228，套件 `5ec9b617`；禁刪 CM-2033 `07563773`，1.21.0 出貨） |

## M10-10 ⚪ 寫入群組／控制項層成員時不比對該物件是否屬於這個專案 {#m10-10}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | ⚪ 低（總表 #109；STRIDE：竄改資料） |
| **在哪裡** | 任務與成員管理：群組層／控制項層成員的寫入 |
| **攻擊面位置** | 某個專案的管理者 |
| **信任邊界位置** | **專案 ↔ 專案**。只驗操作者是專案管理者，填進來的群組或控制項可能根本不屬於那個專案 |
| **元件端點位置** | `jedi_task_platform`（已退役）：`POST／PUT／DELETE /api/1.0/control-group-participant`、`/project-control-participant` → `control_group_participant_service`、`project_control_participant_service` |
| **駭客怎麼打** | ① 某專案的管理者（資訊不足，僅依總表描述展開）；② 寫入群組層或控制項層成員；③ 群組或控制項編號填別專案的；④ 系統不比對就寫入（實際上寫入端的編號解析早已是空殼、按儲存一律 403，已斷半年） |
| **得手什麼** | 把成員關係寫到別專案的物件上 |
| **修正的做法** | 拆除了什麼（決策者裁 A，退役）：① 四支網址與兩支路由、序列化檔拿掉；② 只被這些路由呼叫的服務方法與恆回 0 的解析空殼刪除；③ 讀取方法保留給儀表板與「我參與哪些專案」查詢；④ 前端「成員」按鈕與側欄同步拿掉 |
| **狀態** | 🗑️ 裁定退役（CM-2072，套件 `f1fa23c1`／前端 `2cb7fb4`；報告回寫 BE `390b13908`） |

## M10-12 ⚪ 更新專案成員撈不到既有紀錄就整段跳過管理者檢查 {#m10-12}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | ⚪ 低（總表 #110；STRIDE：權限提升） |
| **在哪裡** | 任務與成員管理：更新任務指派 |
| **攻擊面位置** | 已登入使用者可打的指派 API；今天靠下游另一道檢查頂住 |
| **信任邊界位置** | **使用者 ↔ 指派服務**。守門寫成「撈得到既有紀錄才檢查」，撈不到就整段跳過 |
| **元件端點位置** | `jedi_task_platform/participant/api/routing.py`：`PUT /api/1.0/task-assignee`（`TaskAssigneeRoute.put`）→ `task_assignee_service.update_task_assignee` |
| **駭客怎麼打** | ① 任一登入帳號（資訊不足，僅依總表描述展開）；② 送更新請求，帶一個查不到的紀錄；③ 管理者檢查被跳過；④ 今天下游「不得重複」檢查會擋，哪天改成「查不到就新增」這道把關就靜默消失 |
| **得手什麼** | （潛在）未經管理者檢查就寫入指派 |
| **修正的做法** | 撈不到既有紀錄就回 404，寫法照抄同檔刪除方法已驗證的形狀 |
| **狀態** | ✅ 已修（FR-114.1-3，CM-2023，套件 `50c6e0f0`） |

## M17-3 ⚪ 用網址直開公告什麼都不檢查，調部門後舊網址照樣打得開 {#m17-3}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | ⚪ 低（總表 #111；STRIDE：資料外洩） |
| **在哪裡** | 公告模組：用網址直接開一則公告 |
| **攻擊面位置** | 任一登入帳號，知道公告編號 |
| **信任邊界位置** | **使用者 ↔ 公告服務**。單筆讀取完全不判斷發送對象，唯一擋著的是「編號猜不到」 |
| **元件端點位置** | `jedi_bulletin/api/routes/bulletin_route.py`：`GET /api/1.0/bulletin/{uid}`（`BulletinDetailRoute.get`）→ `bulletin_service.get_bulletin` |
| **駭客怎麼打** | ① 剛調離原部門的員工；② 用以前存的公告網址；③ 系統不比對發送對象；④ 舊部門的公告照樣打得開，還沒發布的也能提前看 |
| **得手什麼** | 不該再看到或尚未發布的公告 |
| **修正的做法** | 與 M17-4 一套設計：① 公告新增「發送範圍」欄位（全公司或指定部門），舊資料一律標全公司；② 單筆讀取帶入使用者，照發送範圍判斷，看不到回 404；③ 判斷規則與列表的資料庫條件保持一致；④ 前端表單加發送範圍選項 |
| **狀態** | ✅ 已修（FR-114.1-5a CM-2026 套件 `8ca1c279`；欄位 CM-2027 BE `32ace7aa5`；前端 CM-2028 `e705b05`） |

## M17-4 ⚪ 沒被分配部門的帳號打公告列表，整個不過濾全部給看 {#m17-4}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | ⚪ 低（總表 #111；STRIDE：資料外洩） |
| **在哪裡** | 公告模組：公告列表 |
| **攻擊面位置** | 沒有被分配部門的帳號；「要不要過濾」開關還是呼叫端在請求裡自己傳的 |
| **信任邊界位置** | **使用者 ↔ 公告服務**。寫成「有部門才過濾」，沒部門就整個跳過 |
| **元件端點位置** | `jedi_bulletin/api/routes/bulletin_route.py`：`POST /api/1.0/bulletins`（`BulletinListRoute`）→ 公告 repo 的 `visible_to_org_unit_ids` 條件 |
| **駭客怎麼打** | ① 一個沒被分配部門的帳號；② 打公告列表；③ 系統跳過過濾；④ 整個客戶的公告（含草稿、過期）連同編號回來，編號正好餵給 M17-3 |
| **得手什麼** | 全客戶公告與其編號 |
| **修正的做法** | ① 驗證模式下一律設可見部門條件，沒部門時是空清單、語意為「只看全公司」；② 列表以「全公司或命中指定部門」下條件；③ 查詢刻意不開放以發送範圍篩選，避免字串條件變成恆真 |
| **狀態** | ✅ 已修（FR-114.1-5a，CM-2026，套件 `8ca1c279`） |

## M19-2 ⚪ 只有「修改」權限，送一個「停用」就等於刪掉資訊系統 {#m19-2}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | ⚪ 低（總表 #112；STRIDE：竄改資料） |
| **在哪裡** | 設備與系統清冊：修改資訊系統 |
| **攻擊面位置** | 只被給修改權限、刻意沒給刪除權限的操作人員；畫面上沒有這個欄位，要直接呼叫後端 |
| **信任邊界位置** | **能力點 ↔ 能力點**。修改的資料格式收「停用」欄位，等於用修改權限做到刪除 |
| **元件端點位置** | `jedi_asset/api/routing.py`：`PUT /api/1.0/information-system/{uid}`（`InformationSystemDetailRoute`）；序列化 `InformationSystemUpdateSchema` |
| **駭客怎麼打** | ① 只有修改權限的操作人員；② 對某資訊系統送修改請求，多帶「停用」；③ 系統照收；④ 那個系統從所有選單與清單消失，稽核專案選不到它 |
| **得手什麼** | 用修改權限讓資訊系統消失 |
| **修正的做法** | ① 修改用的資料格式拿掉「停用」欄位，停用只能走刪除（需刪除權限）；② 修改時沒帶這欄就維持原值，不再套程式預設值，已停用的系統不會被悄悄重新啟用 |
| **狀態** | ✅ 已修（FR-114.1-6，CM-2029，套件 `0ecdd3a1`） |

## M03-8 ⚪ 權限矩陣取消勾選，後端八支讀取照樣全給 {#m03-8}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | ⚪ 低（總表 #113；STRIDE：資料外洩） |
| **在哪裡** | 弱點檢測模組：掃描設定（檢測基準）的八支讀取 |
| **攻擊面位置** | 被管理員取消勾選「看掃描設定」的帳號 |
| **信任邊界位置** | **使用者 ↔ 檢測服務**。前端選單藏了，後端八支一支都沒檢查，程式裡還寫著「讀取端刻意不掛」 |
| **元件端點位置** | `jedi_detection/api/routes/detection_profile_route.py`：`POST /api/1.0/detection-tool-profiles/list`、`GET …/menu`、`GET …/{uid}`、`GET …/{uid}/versions`、`GET …/{uid}/referencing-usage`、`GET /api/1.0/detection-tool-profile-versions/{uid}/referencing-usage`、`…/controls`、`…/extraction` |
| **駭客怎麼打** | ① 被取消勾選的帳號；② 選單看不到；③ 直接打清單或詳情 API；④ 照樣拿到全部掃描設定，那格勾選形同虛設 |
| **得手什麼** | 管理員以為收掉的掃描設定 |
| **修正的做法** | ① 八支讀取掛新的讀取守門，用既有的 `detection-profile.read` 能力點，不需新增；② 守門用延遲求值形狀，避免在設定還沒載入時就炸；③ 改掉「GET 不設權限」的過期說明；④ 只加在「能不能進來」那層，「看得到哪些」的公版／自有／上游分享三路不動；⑤ 缺這顆權限的角色由 migration 補上（`3a651140e`） |
| **狀態** | ✅ 已修（CM-2042，套件 `5a9baf2b`；權限資料 BE `3a651140e`） |

## M04-12 ⚪ 共用的回覆工具有什麼吐什麼 {#m04-12}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | ⚪ 低（總表 #114；STRIDE：資料外洩） |
| **在哪裡** | 共用基礎：「資料轉成回覆內容」的序列化工具 |
| **攻擊面位置** | 任何呼叫這支工具卻忘了先篩欄位的功能 |
| **信任邊界位置** | **後端 ↔ 瀏覽器**。工具把物件上有的欄位全吐出，篩選責任全在呼叫端 |
| **元件端點位置** | `jedi_common/utils/serialization_util.py` 的 `to_serializable`；已出事的呼叫端是 AI 儀表板（見 M15-4） |
| **駭客怎麼打** | ① 任一能打到使用這支工具的功能的帳號；② 正常呼叫；③ 呼叫端沒先篩；④ 回應夾帶不該看的內部欄位（AI 儀表板真的吐過密碼鹽值） |
| **得手什麼** | 內部欄位，如密碼加密材料 |
| **修正的做法** | 工具本身加敏感欄位不輸出名單（鹽值、密碼、超管旗標、權杖類），物件與字典兩個分支都擋，不再只靠呼叫端自律 |
| **狀態** | ✅ 已修（CM-2064，套件 `248132ec`）。輸入檔標 CM-2038，實際修這支工具的 commit 標的是 CM-2064 |

## M06-11 ⚪ 沒被授權的人讀走全部流程範本 {#m06-11}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | ⚪ 低（總表 #115；STRIDE：資料外洩） |
| **在哪裡** | 稽核流程：流程範本的列表與單筆讀取 |
| **攻擊面位置** | 同一家客戶內沒被授權的帳號；跨客戶有資料庫隔離擋 |
| **信任邊界位置** | **使用者 ↔ 範本服務**。同檔新增、修改、刪除都有守門，讀取沒有 |
| **元件端點位置** | 主專案 `api/flow_engine/routes/flow_template_route.py`：`GET /api/1.0/flow-engine/flow-templates`、`GET /api/1.0/flow-engine/flow-templates/{uid}` |
| **駭客怎麼打** | ① 同客戶沒被授權的帳號；② 選單看不到；③ 直接打範本列表與單筆；④ 讀走全部流程範本與系統內建範本的階段設計、角色指派、判斷條件 |
| **得手什麼** | 客戶的稽核流程設計 |
| **修正的做法** | 兩支讀取補守門，收「看流程範本」或「建專案」任一（建專案第一步要選範本，只認前者會讓能建案的人卡住） |
| **狀態** | ✅ 已修（CM-2035，BE `e2c32258d`） |

## M06-14 ⚪ 三支直接拿路徑開檔的閒置程式 {#m06-14}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | ⚪ 低（總表 #118；STRIDE：竄改資料、資料外洩、權限提升） |
| **在哪裡** | 稽核流程套件：流程圖產生工具裡「存到檔案」「匯出 XML」「從 XML 匯入」三支 |
| **攻擊面位置** | 今天無：全程式零呼叫。只要有人把使用者輸入接到參數上就變成任意檔案讀寫 |
| **信任邊界位置** | **程序內 ↔ 檔案系統**。直接拿傳入的路徑開檔讀寫，沒有任何路徑限制 |
| **元件端點位置** | `jedi_flow_engine/common/utils/bpmn_generator.py` 的 `save_bpmn_to_file`、`export_xml`、`import_xml`（已刪） |
| **駭客怎麼打** | ① 今天沒有入口；② 若日後有功能把使用者填的檔名傳進來；③ 程式照路徑開檔；④ 就能讀寫伺服器上任意檔案 |
| **得手什麼** | （潛在）任意檔案讀寫 |
| **修正的做法** | 拆除了什麼：三支死碼整支刪除，連帶移除只給匯入用的模組匯入；有人在用的字串版匯出／匯入保留 |
| **狀態** | 🗑️ 已刪除（隨 CM-2059 套件 commit `4a416ece` 一併清除，1.21.0 出貨） |

## M06-16 ⚪ 兩組沒人用、也沒權限檢查的備用服務 {#m06-16}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | ⚪ 低（總表 #118；STRIDE：竄改資料、資料外洩、權限提升） |
| **在哪裡** | 稽核流程：主專案 DI 裡登記、但沒人注入的備用服務 |
| **攻擊面位置** | 今天無：沒人用。哪天被接上就憑空多一組沒檢查的入口 |
| **信任邊界位置** | **主專案 ↔ 套件**。套件版服務沒有主專案的守門，主專案已經改用自己的實作，舊登記卻還留著 |
| **元件端點位置** | 主專案 `di_containers/flow_engine/workflow_excution_containers.py` 的 `job_execution_service` provider（已拆） |
| **駭客怎麼打** | ① 今天沒有入口；② 若有人把新路由接到這組服務；③ 服務本身不做權限檢查；④ 就多出一組只驗登入的入口 |
| **得手什麼** | （潛在）沒檢查的流程操作入口 |
| **修正的做法** | 拆除了什麼：全庫確認只剩登記本身在用後，拆掉沒人注入的服務登記與其匯入 |
| **狀態** | 🗑️ 已拆除（8-E，CM-2215，BE `822216b6c`，1.21.0 出貨） |

## M10-18 ⚪ 四支沒有把關的任務指派程式 {#m10-18}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | ⚪ 低（總表 #120；STRIDE：權限提升） |
| **在哪裡** | 任務與成員管理：任務指派相關的查詢方法 |
| **攻擊面位置** | 沒有對外入口 |
| **信任邊界位置** | **服務 ↔ 呼叫者**。方法本身沒有任何把關，安全性全靠沒人呼叫 |
| **元件端點位置** | `jedi_task_platform/participant/app/service/task_assignee_service.py` 的 `get_task_assignee_menu`、`get_task_assignees_with_inherits`、`get_user_task_queue`（已刪）及下游 domain／repo 同名方法 |
| **駭客怎麼打** | ① 今天沒有入口（資訊不足，僅依總表描述展開）；② 若有人接上；③ 方法不檢查歸屬；④ 撈得到任務指派與待辦資料 |
| **得手什麼** | （潛在）全公司任務指派資料 |
| **修正的做法** | 拆除了什麼：三支查詢與下游孤兒鏈整條刪除（v1 待辦已被主專案的新查詢取代）；第四支「批次指派」是前端活端點，保留 |
| **狀態** | 🗑️ 已刪除（CM-2044，FR-114.3-2，套件 `9c9029fd`，1.21.0 出貨） |

## M21-2 ⚪ 空白查詢撈走全站成員名冊 {#m21-2}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | ⚪ 低（總表 #121；STRIDE：資料外洩） |
| **在哪裡** | 意見回饋（問題單整合）：成員名冊查詢 |
| **攻擊面位置** | 任一登入帳號；查證時沒有任何地方在呼叫 |
| **信任邊界位置** | **租戶 ↔ 租戶**。那張表連客戶歸屬欄位都沒有，入口只驗登入，空查詢就回整張 |
| **元件端點位置** | `jedi_issue`（已刪）：`GET /api/1.0/issue/get_members`（`IssueMemberRoute`）→ `MemberService.get_members` |
| **駭客怎麼打** | ① 任一登入帳號；② 送空白查詢到成員名冊；③ 系統不過濾也不隔離；④ 整張全站成員名冊含電子郵件回來 |
| **得手什麼** | 全站成員與 email |
| **修正的做法** | 拆除了什麼：只拿掉對外查詢入口與整條讀取鏈（路由、服務、domain、repo 的 `get_members`）；資料表與同步程式保留，因為意見回饋指派負責人還在用 |
| **狀態** | 🗑️ 已刪除（FR-114.3-4，CM-2046，套件 `04e0e6c1`／BE `0fb8d93c0`，1.21.0 出貨） |

## M05-11 ⚪ 問卷資料夾清單叫出被隱藏的資料夾 {#m05-11}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | ⚪ 低（總表 #126；STRIDE：資料外洩） |
| **在哪裡** | 問卷模組：資料夾列表 |
| **攻擊面位置** | 任一登入帳號，連權限點都沒掛 |
| **信任邊界位置** | **使用者 ↔ 問卷服務**。請求內容直接展開成查詢條件，連「已刪除」這種內部欄位都能從外面塞 |
| **元件端點位置** | `jedi_survey/api/routes/survey_folder_route.py`：`POST /api/1.0/survey-folders`（`SurveyFoldersRoute`）→ `app/service/survey_folder.py` |
| **駭客怎麼打** | ① 任一登入帳號；② 打資料夾列表，多塞「已刪除＝1」；③ 系統照當查詢條件；④ 叫出流程自動產生的內部快照資料夾與已刪資料夾 |
| **得手什麼** | 系統刻意隱藏的資料夾 |
| **修正的做法** | ① 查詢條件改由後端寫死：只查未刪、排除系統資料夾；② 同批修改資料夾也改成只收名稱與說明，避免塞「已刪除」軟刪資料夾繞過「非空不可刪」 |
| **狀態** | ✅ 已修（CM-2060，套件 `54ac36f7`） |

## M22-3 ⚪ 儲存設定頁把共用密鑰明文送到瀏覽器 {#m22-3}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | ⚪ 低（總表 #134；STRIDE：資料外洩） |
| **在哪裡** | 系統設定模組：「儲存設定」頁 |
| **攻擊面位置** | 有正當權限的客戶管理員的瀏覽器；能看到他瀏覽器流量、存檔或前端錯誤紀錄的人 |
| **信任邊界位置** | **後端 ↔ 瀏覽器**。遮罩名單漏登記儲存設定那一組 |
| **元件端點位置** | `jedi_system_core/plugin/contract.py` 的 `DEFAULT_SECRET_MASKED_GROUPS`、`DEFAULT_SECRET_VALUE_KEYS`；讀取 `GET /api/1.0/system/configs/{group}`、`GET /api/1.0/system/config/{group}/{key}` |
| **駭客怎麼打** | ① 管理員正常打開儲存設定頁；② 瀏覽器收到密鑰明文；③ 攻擊者從他的瀏覽器擴充、存檔或前端錯誤回報系統拿到；④ 用這組密鑰直連儲存空間 |
| **得手什麼** | 檔案儲存密鑰 |
| **修正的做法** | ① 遮罩名單加儲存設定，密鑰欄名加兩種寫法；② 寫入側補「沒帶密鑰就沿用原值」，避免存檔抹掉密鑰；③ 後續改成服務層預設不回真值（見 M02-5） |
| **狀態** | ✅ 已修（CM-2054，套件 `16a3e41c`／BE `e90d8e3cb`） |

## M04-4 ⚪ 運作紀錄表沒有客戶隔離 {#m04-4}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | ⚪ 低（總表 #138；STRIDE：資料外洩） |
| **在哪裡** | 共用基礎：兩張運作紀錄表 |
| **攻擊面位置** | 需能直連資料庫的人（維運） |
| **信任邊界位置** | **租戶 ↔ 租戶**（資料庫）。表上沒有客戶欄位，無從隔離 |
| **元件端點位置** | 資料表 `system_logs`（`jedi_common/logger/db_log/system_log/infra/models/system_log.py`）、`api_logs`（`jedi_api_log/api_log/infra/models/api_log.py`） |
| **駭客怎麼打** | ① 能連進資料庫的人；② 查這兩張表；③ 沒有隔離規則；④ 看到全部客戶的運作紀錄 |
| **得手什麼** | 全部客戶的運作紀錄 |
| **修正的做法** | 不修的理由：① 登入失敗、系統啟動、背景排程這類紀錄天生分不出客戶，部分能分等於不能分；② 落地版一家一套資料庫，跨客戶不成立；③ 定位成維運資料，讀的人是自己人 |
| **狀態** | 🚫 裁定不修 |

## M04-5 ⚪ 完整出錯內容寫進沒隔離的紀錄表 {#m04-5}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | ⚪ 低（總表 #138；STRIDE：資料外洩） |
| **在哪裡** | 共用基礎：程式出錯內容寫進紀錄表 |
| **攻擊面位置** | 需能直連資料庫或讀紀錄的維運人員 |
| **信任邊界位置** | **應用程式 ↔ 維運資料**。完整錯誤內容寫進沒隔離的表 |
| **元件端點位置** | `jedi_common/logger/db_log/db_handler.py` → `system_logs` |
| **駭客怎麼打** | ① 能讀紀錄表的人；② 查出錯紀錄；③ 內容是完整堆疊與參數；④ 看到其他客戶操作時的細節 |
| **得手什麼** | 程式出錯細節 |
| **修正的做法** | 不修的理由：與 M04-4 同一套邏輯，看得到這張表的只有維運人員，完整出錯內容正是除錯要用的；日誌轉送到外部也帶完整內容，同樣不列入 |
| **狀態** | 🚫 裁定不修 |

## M17-5 ⚪ 公告與部門的關聯表沒有客戶隔離 {#m17-5}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | ⚪ 低（總表 #143；STRIDE：資料外洩） |
| **在哪裡** | 公告模組：「這則公告發給哪些部門」的關聯表 |
| **攻擊面位置** | 要查它得先有公告編號；主表擋得住 |
| **信任邊界位置** | **租戶 ↔ 租戶**（資料庫）。關聯表本身沒有隔離規則 |
| **元件端點位置** | 資料表 `bulletin_org_units`（`jedi_bulletin/infra/models/bulletin.py`） |
| **駭客怎麼打** | ① 任一帳號；② 需要先拿到別家的公告編號；③ 公告主表的隔離已經擋住；④ 就算查到關聯表，裡面只有兩個編號、沒有內容 |
| **得手什麼** | 幾乎沒有（兩個編號） |
| **修正的做法** | 不修的理由：多做一層會變成兩套規則各自維護，反而更糟；靠公告主表擋，留一句提醒給下一個人 |
| **狀態** | 🚫 裁定不修 |

## M11-28 ⚪ 任何人讀走範本的「設備／資訊系統對照」清單 {#m11-28}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | ⚪ 低（總表 #178；STRIDE：資料外洩） |
| **在哪裡** | 合規文件核心：範本的設備／資訊系統對照 |
| **攻擊面位置** | 任一登入帳號；要範本先掛過這類資料 |
| **信任邊界位置** | **使用者 ↔ 範本服務**。讀取沒有能力點 |
| **元件端點位置** | 主專案 `api/module_frame/__init__.py`：`GET /api/1.0/module-frame/{uid}/ssp-resources`（`ModuleFrameSspResourcesResource`） |
| **駭客怎麼打** | ① 任一登入帳號；② 拿範本編號打對照清單；③ 系統只驗登入；④ 拿到對照清單外加資產主檔裡真實紀錄的名稱與內部編號 |
| **得手什麼** | 範本對照的資產名稱與編號 |
| **修正的做法** | 補 `module-frame.read`，與 M11-4 同一張卡 |
| **狀態** | ✅ 已修（FR-114 CM-2186，BE `aafaa070d`，1.21.0 出貨） |

## M11-31 ⚪ 下載「空白範本」夾帶全公司資產與人員清冊 {#m11-31}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | ⚪ 低（總表 #179；STRIDE：資料外洩） |
| **在哪裡** | 合規文件核心：從框架版本下載空白範本（及另兩支範本下載） |
| **攻擊面位置** | 只該看範本的人 |
| **信任邊界位置** | **使用者 ↔ 範本服務**。範本的隱藏工作表一律帶全租戶人員與設備清單，不看呼叫者權限 |
| **元件端點位置** | 主專案 `api/module_frame/routes/ssp_import_template_route.py`：`GET /api/1.0/ssp-import-template?framework_version_uid=…`、`GET /api/1.0/module-frame/{uid}/ssp-import-template`、`GET /api/1.0/ssp/{ssp_uid}/excel-template`；新檔 `app/module_frame/service/template_lookup_scope.py` |
| **駭客怎麼打** | ① 只有看範本權限的人；② 下載空白範本；③ 打開隱藏工作表；④ 拿到全公司人員 email、全部設備名稱與 IP、全部資訊系統清單 |
| **得手什麼** | 全公司資產與人員清冊 |
| **修正的做法** | 決策者選 A：① 下拉的名稱類文字保留（填範本必要）；② 依權限拿掉自動帶入的敏感欄：沒有 `user.read` 不給 email、沒有 `device.read` 不給 IP；③ 三支下載共用同一個縮減函式 |
| **狀態** | ✅ 已修（CM-2188，BE `7d62545c3`，1.21.0 出貨） |

## M06-20 ⚪ 匯出任務 Excel 時檢查的專案和實際匯出的可以是兩個 {#m06-20}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | ⚪ 低（總表 #182；STRIDE：資料外洩） |
| **在哪裡** | 稽核流程：匯出任務 Excel |
| **攻擊面位置** | 任一專案的一般成員 |
| **信任邊界位置** | **專案 ↔ 計畫**。只驗「你是網址專案的成員」，網址裡的計畫編號換成別專案的就匯出別專案；守門服務沒注入時還整段跳過 |
| **元件端點位置** | `jedi_task_platform` 路由：`GET /api/1.0/grc/project/{project_uid}/ap/{ap_uid}/jobs/export`（`JobExportRoute`）→ 主專案 `job_import_service.py` |
| **駭客怎麼打** | ① 專案 A 的一般成員；② 匯出任務 Excel，網址專案填 A、計畫換成 B 的；③ 系統確認他是 A 的成員；④ 匯出 B 的整張任務表（任務編號、指派人帳號、部門、設備） |
| **得手什麼** | 別專案的任務規劃表 |
| **修正的做法** | 匯出入口呼叫 `_assert_plan_in_project`：計畫解出的專案須等於網址專案，計畫下每張任務也核歸屬；不再因守門沒注入而放行 |
| **狀態** | ✅ 已修（CM-2191，BE `33aab8cba`，1.21.0 出貨） |

## M06-21 ⚪ 匯入任務的「驗證」兩步不檢查身分、可試探存在性 {#m06-21}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | ⚪ 低（總表 #183；STRIDE：資料外洩） |
| **在哪裡** | 稽核流程：匯入任務的驗證與重新驗證 |
| **攻擊面位置** | 同一家客戶任一登入帳號 |
| **信任邊界位置** | **使用者 ↔ 匯入服務**。兩步完全不檢查身分，錯誤訊息又精確 |
| **元件端點位置** | `jedi_task_platform` 路由：`POST /api/1.0/grc/project/{project_uid}/ap/{ap_uid}/jobs/import`、`POST …/jobs/import/validate` → 主專案 `job_import_service.py` |
| **駭客怎麼打** | ① 同客戶任一帳號；② 上傳一份自編的任務 Excel 驗證；③ 系統不問身分；④ 從錯誤訊息試探某任務屬不屬於某計畫、某帳號／部門／設備存不存在（不會改資料） |
| **得手什麼** | 任務歸屬與帳號、部門、設備名稱的存在資訊 |
| **修正的做法** | 兩步開頭呼叫 `_assert_plan_in_project`：計畫屬於網址專案且你是成員 |
| **狀態** | ✅ 已修（CM-2191，BE `33aab8cba`，1.21.0 出貨） |

## M06-22 ⚪ 查別專案任務「卡在哪幾條分支」 {#m06-22}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | ⚪ 低（總表 #184；STRIDE：資料外洩） |
| **在哪裡** | 稽核流程：「查卡住狀態」 |
| **攻擊面位置** | 同一家客戶任一登入帳號 |
| **信任邊界位置** | **使用者 ↔ 流程服務**。說明寫「任一專案參與者可看」，程式沒驗成員也沒驗任務屬於網址專案 |
| **元件端點位置** | 主專案 `api/flow_control/__init__.py`：`GET /api/1.0/grc/project/{project_uid}/job/{job_uid}/blocked-status`（`JobBlockedStatusResource`）→ `job_force_start_service.inspect` |
| **駭客怎麼打** | ① 同客戶任一帳號；② 網址隨便填個專案、任務填別人的；③ 系統不驗；④ 讀到任務在等哪幾條分支，每條分支的任務編號都在裡面（正是 M06-19 的材料） |
| **得手什麼** | 別專案的流程卡點與任務編號 |
| **修正的做法** | ① 任務歸屬比對網址專案（CM-2113 的唯一判斷）；② `inspect` 開頭補 `_require_participant`，非成員 403 |
| **狀態** | ✅ 已修（CM-2148，BE `a3fe63b03`；CM-2113 `f52a53f1e`，1.21.0 出貨） |

## M05-14 ⚪ 沒買問卷模組的客戶照樣能用「填答」那一半 {#m05-14}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | ⚪ 低（總表 #186；STRIDE：權限提升） |
| **在哪裡** | 問卷模組：填答那半 14 個入口，外加 AI 儀表板查問卷 |
| **攻擊面位置** | 沒買問卷加購包的客戶帳號；每個入口仍有問卷歸屬檢查 |
| **信任邊界位置** | **商務授權 ↔ 功能**。選單反灰，後端不檢查「有沒有買」 |
| **元件端點位置** | `jedi_survey/api/routes/task_survey_route.py`、`question_answer_route.py`、`question_answer_history_route.py`、`question_answer_history_detail_route.py` 共 14 支（如 `POST /api/1.0/task-surveys`、`GET／PUT /api/1.0/project-survey/answers/{uid}`）；AI 儀表板申報 `di_containers/dashboard_apis/survey.py` |
| **駭客怎麼打** | ① 沒買問卷的客戶帳號；② 選單反灰；③ 直接打填答網址；④ 照樣列任務問卷、讀寫答案、看與還原歷史、匯入答案 |
| **得手什麼** | 免費使用付費功能 |
| **修正的做法** | ① 14 支各在登入檢查下一行加 `@require_license("survey")`；② AI 儀表板登記簿新增「需要的模組授權」欄，問卷兩支申報要問卷模組，主專案注入授權判定 |
| **狀態** | ✅ 已修（填答 CM-2194 套件 `0ced8e15`；儀表板 8-J CM-2220 BE `80dedfbb5`／套件 `fb271590`，1.21.0 出貨） |

## M12-4 ⚪ 任何人看任何專案的「任務設定樹」 {#m12-4}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | ⚪ 低（總表 #187；STRIDE：資料外洩） |
| **在哪裡** | 稽核輪次管理：任務設定樹 |
| **攻擊面位置** | 同一家客戶任一帳號；前端已沒有畫面在用 |
| **信任邊界位置** | **使用者 ↔ 稽核服務**。網址專案編號收了沒用，後段編號程式一次猜三種 |
| **元件端點位置** | `jedi_compliance_audit`（已拆）：`GET /api/1.0/grc/project/{project_uid}/ap/{ap_uid}/task-setup/tree` |
| **駭客怎麼打** | ① 同客戶任一帳號；② 手上有任一專案、輪次或稽核計畫編號；③ 打任務設定樹；④ 看到控制群組、控制項、評估目標與被指派人姓名 |
| **得手什麼** | 別專案的任務配置 |
| **修正的做法** | 拆除了什麼（決策者裁 B，拆網址不補守門）：網址與其整條鏈（路由、序列化、服務、DTO、domain、repo 介面與樹專用實作）刪除；主專案拿掉組裝；匯出還在用的兩個小函式保留 |
| **狀態** | 🗑️ 已拆除（CM-2176，套件 `742ef49e`／BE `553381efe`，1.21.0 出貨） |

## M12-5 ⚪ 非成員問得出專案「目前的 SSP」編號與狀態 {#m12-5}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | ⚪ 低（總表 #188；STRIDE：資料外洩） |
| **在哪裡** | 稽核輪次管理：查專案目前的系統安全計畫 |
| **攻擊面位置** | 同一家客戶的非成員；前端沒有地方在用 |
| **信任邊界位置** | **使用者 ↔ 稽核服務**。只驗登入 |
| **元件端點位置** | `jedi_compliance_audit`（已拆）：`GET /api/1.0/grc/project/{uid}/current-ssp-uid` |
| **駭客怎麼打** | ① 同客戶非成員；② 知道專案編號；③ 打這支；④ 拿到該專案目前系統安全計畫的編號與狀態（後續相關網址各自有檢查） |
| **得手什麼** | 一個隨機編號與狀態 |
| **修正的做法** | 拆除了什麼：與 M12-4 同批拆網址；前端拿掉沒人呼叫的常數與方法 |
| **狀態** | 🗑️ 已拆除（CM-2176，套件 `742ef49e`／BE `553381efe`／前端 `fc478d0`，1.21.0 出貨） |

## M12-6 ⚪ 專案經理改寫、再刪掉稽核員的「改善建議」 {#m12-6}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | ⚪ 低（總表 #189；STRIDE：竄改資料、事後無法追查） |
| **在哪裡** | 稽核輪次管理：風險底下的整改計畫 |
| **攻擊面位置** | 該專案的經理（受稽核方），輪次在整改中 |
| **信任邊界位置** | **受稽核方 ↔ 稽核方**。「新增整改計畫」帶既有編號就變成更新，能把稽核建議改成一般計畫，接著「建議不能刪」就不成立 |
| **元件端點位置** | 主專案 `api/project/routes/audit_round_route.py`：`POST /api/1.0/audit-round/{round_uid}/ar/risk/{risk_uid}/remediations`（`RiskRemediationsRoute`）→ 套件 `poam_app_service.add_remediation` |
| **駭客怎麼打** | ① 專案經理；② 拿到稽核員寫的改善建議編號；③ 用「新增整改計畫」帶這個編號送出，內容改掉；④ 類型被改成一般計畫後再整筆刪掉 |
| **得手什麼** | 抹掉稽核方的改善建議 |
| **修正的做法** | 決策者裁定「改善建議屬稽核方專屬」：① 這支拒絕類型為建議的寫入；② 帶編號命中既有建議一律拒絕覆蓋；③ 請求格式的類型只收「計畫中／已完成」 |
| **狀態** | ✅ 已修（FR-114 CM-2174，BE `a8f1a24b7`／套件 `b7022b8b`，1.21.0 出貨） |

## M11-29 ⚪ 把稽核「判定」接到別份的「風險」上（地雷） {#m11-29}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | ⚪ 低（總表 #190；STRIDE：竄改資料） |
| **在哪裡** | OSCAL 套件：把稽核判定連到風險 |
| **攻擊面位置** | 今天打不到：唯一呼叫端有先檢查 |
| **信任邊界位置** | **套件 ↔ 呼叫端**。套件方法不檢查判定與風險是不是同一份稽核結果，全靠呼叫端自律 |
| **元件端點位置** | `jedi_oscal_v2/app/service/ar/assessment_risk_service.py` 的 `link_findings`、`get_risk_findings`；呼叫端 `PUT /api/1.0/audit-round/{round_uid}/ar/risk/{risk_uid}/findings` |
| **駭客怎麼打** | ① 今天沒有入口；② 下一個呼叫這支方法的程式漏掉前置檢查；③ 套件照接；④ A 份稽核結果的判定被接到 B 份的風險上 |
| **得手什麼** | （潛在）稽核結果互相污染 |
| **修正的做法** | ① 套件方法先撈風險（不存在 404），逐一驗判定與風險屬於同一份結果，全部驗完才寫，有一筆不符整批拒絕；② 查詢只回同份結果的判定；③ 呼叫端既有檢查保留 |
| **狀態** | ✅ 已修（FR-114 CM-2184，套件 `ae5ffff5`，1.21.0 出貨） |

## M11-30 ⚪ 拿草稿版本編號複製平台管理員還沒公開的控制項 {#m11-30}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | ⚪ 低（總表 #195；STRIDE：資料外洩） |
| **在哪裡** | 合規文件核心：建立資源庫時選框架版本 |
| **攻擊面位置** | 能建資源庫的帳號（M11-13 修好後需建立權限） |
| **信任邊界位置** | **平台草稿 ↔ 客戶**。建庫不看版本發佈了沒 |
| **元件端點位置** | 主專案 `POST /api/1.0/oscal/resource-libraries` 與 Excel／Word 建新範本 → `resource_library_app_service.create_resource_library` |
| **駭客怎麼打** | ① 能建資源庫的帳號；② 拿到一個原廠還在編的草稿版本編號；③ 用它建資源庫；④ 複製到未公開的控制項內容，之後原廠改草稿客戶那份也不會跟著改 |
| **得手什麼** | 原廠未定稿的控制項內容 |
| **修正的做法** | ① 建庫合併點在複製目錄之前補「版本必須已發佈」，否則 412（新碼 `GRC_412057`），三條建庫路徑一處蓋三處；② 前端建新範本的版本選單只列已發佈 |
| **狀態** | ✅ 已修（FR-114 CM-2179，BE `94275d72f`／前端 `444b98b`，1.21.0 出貨） |

## M12-7 ⚪ 兩張「解析工作單」表照抄了五月就判定壞掉的隔離寫法 {#m12-7}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | ⚪ 低（總表 #197；STRIDE：資料外洩） |
| **在哪裡** | 稽核輪次管理：稽核計畫 Word、稽核結果 Excel 的解析工作單 |
| **攻擊面位置** | 子公司帳號；今天程式層四步都檢查公司，沒有直接可打的路 |
| **信任邊界位置** | **子租戶 ↔ 母租戶**（資料庫）。規則用文字包含比對、拿租戶編號比使用者編號、沒有超管放行 |
| **元件端點位置** | 資料表 `oscal.ap_docx_parse_jobs`、`oscal.ar_xlsx_parse_jobs`；`scripts/sql/2026-09-25-fr114-ap-ar-parse-jobs-rls-fix.sql` |
| **駭客怎麼打** | ① 子公司帳號；② 經任何直讀這兩張表的路徑；③ 資料庫規則把子公司路徑當成包含母公司；④ 看到母公司的工作單（開發環境模擬：13＋4 筆） |
| **得手什麼** | 母公司的解析工作單 |
| **修正的做法** | 兩表各刪掉舊規則，照標準寫法建查詢／新增／更新／刪除四條，確認隔離開啟；舊 migration 檔不改 |
| **狀態** | ✅ 已修（FR-114 CM-2195，BE `56dd4cdc8`，1.21.0 出貨）。驗證：子公司與別家看到 0 筆、自己與超管照常 |

## M12-9 ⚪ 確認匯入稽核結果時塞一個不屬於這輪的佐證 {#m12-9}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | ⚪ 低（總表 #198；STRIDE：竄改資料） |
| **在哪裡** | 稽核輪次管理：確認匯入稽核結果 Excel |
| **攻擊面位置** | 該專案的稽核員 |
| **信任邊界位置** | **瀏覽器 ↔ 後端**。確認時照收前端送來的佐證，不核對是不是預覽時伺服器算出的候選 |
| **元件端點位置** | 主專案 `api/project/__init__.py`：`POST /api/1.0/ar-import/{parse_uid}/confirm`（`ArImportConfirmRoute`）→ 套件 `ar_import_app_service`、新檔 `app/service/ar_import/evidence_guard.py` |
| **駭客怎麼打** | ① 稽核員 A；② 按確認匯入前改掉送出的資料，塞一個不屬於這輪的檔案或自己的網址；③ 系統照存；④ 同專案管理者點開佐證，預覽出 A 指定的檔，或連結開新分頁拿來釣魚 |
| **得手什麼** | 偽造佐證、釣同事 |
| **修正的做法** | ① 確認時從伺服器端的解析結果重建候選池，只保留池內的佐證，且改存伺服器那份；② 一般新增／修改觀察的連結只收 http(s)；③ 丟掉的筆數記警告 |
| **狀態** | ✅ 已修（FR-114 CM-2174，套件 `b7022b8b`／BE `a8f1a24b7`，1.21.0 出貨） |

## M18-13 ⚪ 授權過期的唯讀有兩個縫 {#m18-13}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | ⚪ 低（總表 #216；STRIDE：權限提升） |
| **在哪裡** | 授權管理：過期唯讀限制 |
| **攻擊面位置** | 授權已過期的客戶帳號 |
| **信任邊界位置** | **商務授權 ↔ 功能**。代理程式整段網址被豁免唯讀；問卷即時填答走即時通道、不經 HTTP 唯讀攔截 |
| **元件端點位置** | 主專案 `common/middleware/license_readonly_mw.py`（豁免清單）；`POST /api/1.0/agents/enroll-token`；`jedi_survey/app/handler/fill_survey_socketio_handler.py` 的 `on_update` |
| **駭客怎麼打** | ① 授權已過期的客戶；② 產生新的代理程式註冊碼；③ 或在問卷即時通道繼續填答寫入；④ 唯讀名存實亡 |
| **得手什麼** | 過期後繼續寫入 |
| **修正的做法** | ① 豁免收窄成代理程式自己打的控制面（報到、領工作、回報），產撤註冊碼不再豁免；② 即時填答在寫入前問主專案注入的唯讀判定，唯讀就只回錯誤事件；③ 順帶：到期當下即唯讀，不等每日排程 |
| **狀態** | ✅ 已修（8-G，CM-2217，BE `3ed47c7a9`／套件 `8ccfac73`，1.21.0 出貨） |

## M24-10 ⚪ 雲端硬碟授權連結沒綁發起的瀏覽器 {#m24-10}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | ⚪ 低（總表 #217；STRIDE：冒充身分、竄改資料） |
| **在哪裡** | 主系統雲端硬碟整合：授權回呼 |
| **攻擊面位置** | 拿到授權連結的任何人；攻擊者需騙對方按同意 |
| **信任邊界位置** | **發起者瀏覽器 ↔ 授權回呼**。回呼只認網址上的 state，不認是不是同一個瀏覽器 |
| **元件端點位置** | 主專案 `GET /api/1.0/integrations/google-drive/auth-url`、`GET /api/1.0/integrations/google-drive/callback` → `google_drive_integration_service.build_auth_url`／`handle_callback` |
| **駭客怎麼打** | ① 攻擊者在自己公司產生授權連結；② 轉寄給別家的人；③ 對方登入自己的 Google 按同意；④ 對方的硬碟被接進攻擊者的公司，放進去的檔案攻擊者全看得到 |
| **得手什麼** | 別人的雲端硬碟內容 |
| **修正的做法** | ① 發起時多產一個獨立隨機值，存在瀏覽器的 HttpOnly cookie，伺服器只存雜湊；② 回呼查到 state 先刪，再比對 cookie 雜湊，缺或不符就拒絕；③ 刻意不用卡上建議的「state 的雜湊」，因為 state 就印在轉寄的連結上，收到的人自己算得出來 |
| **狀態** | ✅ 已修（8-H，CM-2218，BE `a671d0b64`，1.21.0 出貨） |

## M24-12 ⚪ 驗證雲端硬碟憑證只查是不是平台管理員 {#m24-12}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | ⚪ 低（總表 #218；STRIDE：權限提升） |
| **在哪裡** | 主系統雲端硬碟整合：驗證應用程式憑證 |
| **攻擊面位置** | 平台管理員群組裡沒被分到儲存設定權限的人 |
| **信任邊界位置** | **能力點 ↔ 角色**。只查身分層級，不查功能權限 |
| **元件端點位置** | 主專案 `api/cloud_integration/routes/drive_app_credential_verify_route.py`：`POST /api/1.0/integrations/google-drive/verify-credentials` → `drive_app_credential_verify_service.py` |
| **駭客怎麼打** | ① 平台管理員群組裡沒有儲存設定權限的人；② 呼叫驗證憑證；③ 系統只看他是平台管理員；④ 拿任意一組憑證來試對錯 |
| **得手什麼** | 試探憑證對錯 |
| **修正的做法** | 平台管理員檢查之後再疊 `storage-config.read`（與設定頁同一顆），沒有就 403，擋在打 Google 之前 |
| **狀態** | ✅ 已修（8-A，CM-2211，BE `fe928859f`，1.21.0 出貨） |

## M06-24 ⚪ 流程圖存檔刪方塊，連底下證據硬刪別人專案的任務 {#m06-24}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | ⚪ 低（總表 #224；STRIDE：竄改資料） |
| **在哪裡** | 稽核流程：流程圖編輯器存檔 |
| **攻擊面位置** | 同公司有「改流程範本」權限的人，知道範本編號 |
| **信任邊界位置** | **範本權限 ↔ 專案權限**。編輯器存檔比對出要刪的任務就硬刪，只有範本能力點守門；規劃頁刪任務卻要專案管理人 |
| **元件端點位置** | 主專案 `PUT /api/1.0/module-frame/item/xml/{uid}` → `module_frame_item_service.update_module_frame_item_xml` → `app/flow_control/service/workflow_xml_sync_service.py` 的 `sync` |
| **駭客怎麼打** | ① 有改範本權限但不是那個專案管理人的人；② 打開某專案在用的流程範本；③ 刪掉一個方塊存檔；④ 對應的任務連同底下證據被硬刪 |
| **得手什麼** | 刪掉別人專案的任務與證據 |
| **修正的做法** | 決策者裁定：真的要增刪任務時，任何寫入前先反查流程實例所屬專案；查得到就要是該專案管理人，查不到放行；歸屬查詢或角色服務漏注入時 fail-closed |
| **狀態** | ✅ 已修（CM-2208，BE `1516d90bc`，1.21.0 出貨） |

## M10-22 ⚪ 換個專案編號看別人專案的證據蒐集進度 {#m10-22}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | ⚪ 低（總表 #225；STRIDE：資料外洩） |
| **在哪裡** | 任務與成員管理：任務執行進度 |
| **攻擊面位置** | 同公司任一登入帳號 |
| **信任邊界位置** | **使用者 ↔ 任務服務**。只驗登入 |
| **元件端點位置** | `jedi_task_platform/task/api/routing.py`：`GET /api/1.0/grc/project/{project_uid}/task-execution/progress`（`TaskExecutionProgressRoute`）→ `task_execution_service.get_prep_job_progress` |
| **駭客怎麼打** | ① 同公司任一帳號（資訊不足，僅依總表描述展開）；② 換網址上的專案編號；③ 系統不問成員；④ 看到別人專案的任務完成數 |
| **得手什麼** | 別專案的證據蒐集進度 |
| **修正的做法** | 改收使用者編號，先解專案（查無 404）再走既有角色守門，四種角色任一即可，不符回 `GRC_403053` |
| **狀態** | ✅ 已修（8-A，CM-2211，套件 `cdb83526`，1.21.0 出貨） |

## M24-18 ⚪ 診斷資料照索引檔路徑讀檔、沒限制在暫存目錄內 {#m24-18}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | ⚪ 低（總表 #228；STRIDE：資料外洩） |
| **在哪裡** | 主系統：出貨主機預先蒐集診斷資料 |
| **攻擊面位置** | 需能動到索引檔的人（主機或容器內權限），前提同 M24-7 |
| **信任邊界位置** | **容器 ↔ 主機**。照容器寫的索引檔路徑讀檔，不檢查是否還在暫存目錄 |
| **元件端點位置** | 主專案 `infra/support/collector/staged_collector.py` 的 `_read_staged`；匯出 `POST /api/1.0/support/diagnostic-bundle` |
| **駭客怎麼打** | ① 能改索引檔的人；② 在索引寫入暫存目錄以外的路徑或指向外面的捷徑；③ 蒐集時照讀；④ 那些檔被打進診斷包帶走 |
| **得手什麼** | 暫存目錄以外的檔案 |
| **修正的做法** | ① 檔名解析成真實路徑後必須直接落在交換目錄內，`../`、絕對路徑、外指捷徑一律拒讀並記警告；② 服務名只收已知清單 |
| **狀態** | ✅ 已修（8-D，CM-2214，BE `d39181f3b`／`046553756`，1.21.0 出貨） |

## M24-9 ⚪ 即時通知有一個頻道不驗身分、誰都能加入任意房間 {#m24-9}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | ⚪ 低（總表 #230；STRIDE：資料外洩） |
| **在哪裡** | 主系統：即時通知頻道 |
| **攻擊面位置** | 不需帳號；今天伺服器不往那裡送資料、前端元件也沒接在畫面上 |
| **信任邊界位置** | **網際網路 ↔ 即時通道**。頻道不驗身分，任何人能進任何房間並廣播 |
| **元件端點位置** | 主專案（已刪）`app/notification/handler/notification_socketio_handler.py`，命名空間 `/socket/notification`；`config/socketio_namespaces.py` |
| **駭客怎麼打** | ① 不需帳號；② 連上通知頻道；③ 加入任意房間；④ 元件哪天接回畫面，就能偷聽討論串並廣播假內容 |
| **得手什麼** | （潛在）偷聽與偽造討論 |
| **修正的做法** | 拆除了什麼：整條流程討論前後端都斷了、沒人用，決策者裁定拔除——頻道處理程式、流程留言 API、對應錯誤碼、前端討論元件一併刪掉 |
| **狀態** | 🗑️ 已拔除（CM-2373，BE `99beceb26`／前端 `2358d96`） |

## M11-40 ⚪ 人員對帳收了公司編號卻沒用、拿全客戶資料比對 {#m11-40}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | ⚪ 低（總表 #231；STRIDE：資料外洩） |
| **在哪裡** | 合規文件核心：系統安全計畫匯入的人員對帳 |
| **攻擊面位置** | 平台管理員身分下跑匯入 |
| **信任邊界位置** | **租戶 ↔ 租戶**。四處查詢收了公司編號沒拿來篩，平台視角會跨客戶比對 |
| **元件端點位置** | 主專案 `domain/oscal/service/reconciliation/person_reconciler.py`；入口 `POST /api/1.0/ssp-docx-imports/parse`、`POST /api/1.0/ssp-excel-imports/parse` |
| **駭客怎麼打** | ① 平台管理員跑系統安全計畫匯入；② 人員對帳拿 email、姓名去比；③ 查詢不帶公司條件；④ 比到別家客戶的人，結果寫進附註（不影響權限） |
| **得手什麼** | 跨客戶人員比對結果 |
| **修正的做法** | 四個比對函式補 `_scoped()`，寫法對齊組織對帳；公司編號為空時維持原行為 |
| **狀態** | ✅ 已修（8-A，CM-2211，BE `fe928859f`，1.21.0 出貨） |

## M11-41 ⚪ 兩張解析工作單表資料庫新增規則寫成「一律允許」 {#m11-41}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | ⚪ 低（總表 #232；STRIDE：竄改資料） |
| **在哪裡** | 合規文件核心：系統安全計畫 Word／Excel 解析工作單 |
| **攻擊面位置** | 今天無：寫入一律用登入身分的公司，沒有別的路 |
| **信任邊界位置** | **租戶 ↔ 租戶**（資料庫）。新增規則 `WITH CHECK (true)`，最後那道牆在新增這面等於沒設 |
| **元件端點位置** | 資料表 `oscal.ssp_docx_parse_jobs`、`oscal.ssp_excel_parse_jobs`；`scripts/sql/2026-09-26-fr114-8e-ssp-parse-jobs-tenant-check.sql` |
| **駭客怎麼打** | ① 今天沒有入口；② 哪天多一條寫入路徑；③ 資料庫不檢查租戶；④ 就能寫進別家公司名下 |
| **得手什麼** | （潛在）寫進別家公司的工作單 |
| **修正的做法** | 新增規則改成「超管或允許的租戶」，與另外兩張同類表一致；查詢／更新／刪除本來就是標準寫法 |
| **狀態** | ✅ 已修（8-E，CM-2215，BE `822216b6c`，1.21.0 出貨） |

## M15-7 ⚪ AI 儀表板生成只查有沒有買模組、不查角色權限 {#m15-7}

| 欄位 | 內容 |
|---|---|
| **嚴重度** | ⚪ 低（總表 #234；STRIDE：權限提升） |
| **在哪裡** | AI 儀表板：生成儀表板 |
| **攻擊面位置** | 客戶刻意沒開 AI 儀表板功能的角色（例如稽核人員），直接打網址 |
| **信任邊界位置** | **能力點 ↔ 功能**，以及**本系統 ↔ 外部 AI 費用**。只查商務授權，不查功能權限 |
| **元件端點位置** | `jedi_ai_dashboard/api/routes/ai_dashboard_route.py`：`POST /api/1.0/ai-dashboard/auto-generate`（`AiDashboardAutoGenerateRoute`）；主專案接線 `core/plugins/ai_dashboard.py` |
| **駭客怎麼打** | ① 沒被開 AI 儀表板的角色；② 選單看不到；③ 直接打生成網址；④ 照樣能用，每次付兩次外部 AI 費用 |
| **得手什麼** | 越權使用並讓原廠吸收 AI 費用 |
| **修正的做法** | ① 套件新增必填接線 `capability_required`，沒接拒絕掛載；② 生成端點掛 `ai-dashboard.read`；③ 主專案接上 `require_capability` |
| **狀態** | ✅ 已修（8-J，CM-2220，套件 `fb271590`／BE `80dedfbb5`，1.21.0 出貨） |
