Guidant AI 資安檢視總報告 · OWASP Top 10(2025)

A10 例外狀況處理不當(Mishandling of Exceptional Conditions)

命中這一類的 16 件,還原成 16 條原始條目(14 條出自模組頁,2 條只在總表、沒有模組頁逐條表)。每一條用白話寫出:在哪裡、攻擊面、信任邊界、元件端點、駭客怎麼打、得手什麼、修正的做法。

🟡 8 ⚪ 8 已修 14/不修 2
§1

這一類是什麼

出錯的那一刻沒有「安全地失敗」——程式沒有預防、偵測、妥善回應不尋常的狀況:該拒絕的時候放行、該安靜記一筆的時候把整個請求弄壞、該只講一句白話的時候把伺服器內部細節整段吐出去。OWASP Top 10:2025 新增這一類,官方涵蓋的代表弱點是錯誤訊息帶出敏感資訊(CWE-209)、未捕捉例外(248)、未檢查回傳值(252)、空值存取(476)、出錯時沒安全失敗(636)。

這次掃到的 16 條,可以歸成四種形狀:

  • 錯誤訊息講太多(M11-22、M11-32、M05-12):程式出錯時把原始錯誤字串原樣回給畫面,伺服器路徑、套件名稱、資料表與欄位名、甚至資料庫語句都跟著出去。
  • 一筆壞資料或一個怪輸入就讓整條路 500(M06-8、M24-16、M24-17、總表#145、M21-3):沒有防呆,一筆空白範本讓所有人的清單頁一直壞;設定碼塞中文、時間格式一邊帶時區一邊不帶,程式就當掉。
  • 出錯的方向反了(M04-8、M08-4、M06-25、M20-4、M10-12):判斷失敗時「退到寬鬆那邊」——設定認不得就退回開發設定、帳本讀不出就當作沒用過、身分抓不到就當作看不到資料而全刪、查不到紀錄就跳過檢查。今天多半沒被打到,但都是哪天條件一變就靜默出事的寫法。
  • 次要動作失敗連累主要動作(總表#103、M02-12、M24-15):寫日誌失敗把使用者的請求一起弄壞;刪檔先刪實體、紀錄刪不掉卻回成功。

這一類多數不是駭客能主動挑的入口,所以 16 條裡沒有高風險。但它的共同教訓很清楚:每一個 except、每一個「查不到就……」都要先問「失敗時應該倒向哪一邊」——倒向嚴格、倒向安靜、倒向只講白話。

兩條沒有模組頁出處:總表#103(寫日誌失敗連累請求)與總表#145(公告空值崩潰)來自掃描總表的「非資安但確實是錯誤」清單,模組頁沒有逐條表,本頁依總表描述、原始掃描報告與修正 commit 還原。

§2

條目清單

# 條目 嚴重度 出自 狀態
1 M21-3 沒有部門的一般使用者送意見回饋必定失敗、畫面不給理由 🟡 中 意見回饋重新檢視 ✅ 已修
2 M04-8 環境設定值打錯字時悄悄退回開發用的紀錄設定 🟡 中 共用基礎 ✅ 已修
3 M06-8 一筆空白內容的流程範本讓所有人的清單頁一直出錯 🟡 中 稽核流程 ✅ 已修
4 M08-4 解鎖檔使用紀錄毀損時,系統當作一張都還沒用過 🟡 中 防竄改檢查 🚫 裁定不修
5 總表#103 寫日誌進資料庫失敗會連帶弄壞使用者的請求 🟡 中 總表 ✅ 已修
6 M20-4 兩支半夜清理程式靠「找不到身分就給最高權限」才跑得動 🟡 中 客戶資料隔離 ✅ 已修
7 M11-22 框架 PDF 解析失敗時把原始錯誤訊息整段回給前端 🟡 中 合規文件核心 ✅ 已修
8 M02-12 刪原廠共用檔時實體先被刪、紀錄卻刪不掉 🟡 中 檔案上傳下載 ✅ 已修
9 M10-12 更新任務指派時撈不到既有紀錄就跳過管理者檢查 ⚪ 低 任務與成員管理 ✅ 已修
10 M05-12 問卷資料夾列表把資料庫原始錯誤整句吐回畫面 ⚪ 低 問卷 ✅ 已修
11 總表#145 公告查無此筆對空值設值、沒有登入身分時建立公告崩潰 ⚪ 低 總表 ✅ 已修
12 M11-32 Word 打不開時錯誤訊息帶出伺服器暫存檔路徑 ⚪ 低 合規文件核心 ✅ 已修
13 M24-16 首次安裝精靈設定碼塞中文或特殊字元,伺服器回 500 ⚪ 低 主系統自己的程式 ✅ 已修
14 M24-15 雲端硬碟通知說有去重複、實際沒做 ⚪ 低 主系統自己的程式 🚫 裁定不修
15 M06-25 孤兒清理排程若沒帶身分,會把正常資料整批判成孤兒刪掉 ⚪ 低 稽核流程 ✅ 已修
16 M24-17 匯出診斷包時一邊帶時區一邊不帶,程式當掉回 500 ⚪ 低 主系統自己的程式 ✅ 已修

§3

M21-3 🟡 沒有部門的一般使用者送意見回饋必定失敗、畫面不給理由

欄位 內容
嚴重度 🟡 中(總表 #44;STRIDE:讓服務停擺)
在哪裡 意見回饋模組:使用者在「意見回饋」頁按送出
攻擊面位置 不是攻擊面,是功能自己壞掉:任何一個還沒被分配部門的正常帳號都會踩到,新客戶剛裝好、部門還沒建起來時特別容易
信任邊界位置 應用程式 ↔︎ 資料庫隔離規則。資料庫那層的「新增」規則多要求了「送出者必須有部門」,跟另外三條(查、改、刪)形狀不同;規則擋下時應用程式沒有把原因翻成白話,畫面只看到失敗
元件端點位置 jedi_issue/api/routing.py:POST /feedback(FeedbackRoute.post)→ feedback_service.add_feedback();擋下它的是資料庫規則 feedback_issues_insert,定義在 jedi-issue/jedi_issue/migrations/004-feedback-issues-rls-grants.sql(既有庫由主專案 scripts/sql/2026-09-22-fr114-feedback-issues-insert-relax-org.sql 換掉)
駭客怎麼打 ①(不是駭客,是一般使用者)管理員剛建好帳號、還沒指定部門;② 這個人打開意見回饋、填好內容按送出;③ 資料庫的新增規則看到「部門是空的」就擋下,畫面只顯示失敗;④ 管理員去查這個人的權限,每一項都對,查不出原因
得手什麼 沒有人得手;是正常使用者用不了功能,而且沒人看得出為什麼
修正的做法 ① 決策者裁定放寬規則:送意見回饋本來就與部門無關,不採「規定帳號必有部門」(影響所有建帳流程)也不採「只把錯誤講清楚」(沒解決卡住);② 「新增」規則改成跟另外三條一模一樣:超級管理員可略過,否則只看「這筆屬於哪一家客戶、你是不是那家的」,「必須有部門」整個拿掉;③ 套件裡給新裝庫用的那份與主專案給既有庫換規則的 migration 兩處同時改,少一邊就會「新裝的沒事、舊庫還卡著」;④ 客戶隔離沒跟著鬆:客戶欄位留空的列仍插不進去
狀態 ✅ 已修(FR-114.1-9/CM-2032,主專案 commit fc9fca7d0、套件 commit e98fea29)。驗證:沒部門的人送得出去且客戶欄位正確;跨客戶送、客戶欄位留空仍被擋
§4

M04-8 🟡 環境設定值打錯字時悄悄退回開發用的紀錄設定

欄位 內容
嚴重度 🟡 中(總表 #84;STRIDE:資料外洩)
在哪裡 共用基礎模組:服務啟動時讀「現在是什麼環境」(RUN_ENV)來決定日誌怎麼記
攻擊面位置 不是外部能打的面:要能改主機上的部署設定才碰得到。真正會發生的是客戶或維運手誤,例如把 prod 寫成 production
信任邊界位置 部署設定 ↔︎ 程式。程式讀到認不得的值時,應該倒向嚴格的那一邊;當時倒向的是開發設定——多開「把日誌寫進資料庫」那條路、掛在七種紀錄類別上,等於打錯字讓客戶機變寬鬆
元件端點位置 jedi-common/jedi_common/logger/config_logger.py:dictConfig(_CONFIGS.get(RUN_ENV, …)) 的退路;兩套設定在同目錄 config_dev.py/config_prod.py。出貨端設定在 docker/production/docker-compose.yml(RUN_ENV: ${RUN_ENV:-prod})與 scripts/installer/install.sh
駭客怎麼打 ①(多半是手誤,不是駭客)客戶機房的維運改部署設定,把環境值打成 production;② 服務照常啟動,程式認不得這個值;③ 自動退回開發用的日誌設定,開發才該有的紀錄路徑全開;④ 沒有任何錯誤或警告,誰都不知道這台機器的紀錄方式已經跟其他客戶不一樣
得手什麼 客戶機在不知情下改用開發用的紀錄設定,多記、多寫進資料庫
修正的做法 ① 認不得的設定值改退到正式設定,不是開發設定;② 刻意不報錯、不擋啟動——決策者原話「不能因為設定錯誤就不讓啟動,客服會瘋掉」,把打錯字的後果從「悄悄變寬鬆」反過來變成「悄悄變嚴格」;③ 動手前先查出貨端兩處(docker-compose 兩支服務、安裝程式)都已顯式寫 prod,確認不再依賴這個預設值;④ 完全沒設的情況維持開發,那只會發生在開發機
狀態 ✅ 已修(CM-2065/FR-114.5C-4,套件 commit a9562dd0,1.21.0 出貨)
§5

M06-8 🟡 一筆空白內容的流程範本讓所有人的清單頁一直出錯

欄位 內容
嚴重度 🟡 中(總表 #87;STRIDE:讓服務停擺)
在哪裡 稽核流程模組:流程範本的儲存與讀取(法規框架裡的流程圖、每輪稽核的流程)
攻擊面位置 需要登入且持有「法規框架修改」權限(module-frame.update)。寫壞一筆是一次性動作,之後受害的是其他正常使用者,不是寫壞它的人
信任邊界位置 寫入端 ↔︎ 資料庫 ↔︎ 所有讀取端。寫入端用「這欄有沒有值」當判斷,空字串在程式裡算「沒有值」,就跳過驗證直接存;讀取端又相信資料庫裡的一定是合法流程圖、無條件解析不防呆。兩邊都沒守
元件端點位置 寫入:主專案 api/module_frame/__init__.py 的 PUT /module-frame/item/xml/{uid}(ModuleFrameItemXmlRoute,xml 欄位 payload.get("xml", ""))→ 套件 jedi_flow_engine/infra/repository/workflow_template_repo_impl.py 的 add()/update();讀取:jedi_flow_engine/infra/mapper/workflow_template_mapper.py 的 to_entity()(所有查詢的唯一轉換點),例如 GET /flow-engine/process-definition/{uid} 與各範本清單
駭客怎麼打 ① 一個有法規框架修改權限的帳號,打開流程圖編輯;② 送出時把流程圖內容留空(或直接不帶);③ 寫入端判斷「沒有值」就跳過檢查,空字串照樣存進資料庫;④ 之後任何人打開會讀到這筆範本的清單或詳細頁,解析器對空字串報錯、整頁 500;⑤ 不會自己恢復,要有人進資料庫把那筆修掉
得手什麼 讓所有人的流程範本清單與相關頁面持續打不開
修正的做法 ① 寫入端分清三種情況:抽出 _sync_json_from_xml()——沒送(維持舊值)、空白字串(直接拒絕,新錯誤碼 FLOW_ENGINE_400004)、有值(解析並驗證);② 讀取端防呆:to_entity() 遇空值跳過解析、解析失敗退成「0 個任務」並記警告,一筆壞資料不再拖垮整份查詢;③ 主專案這邊範本的新增與修改也補上「先驗流程圖再存」,原本只有發布時才驗
狀態 ✅ 已修(CM-2059,套件 commit 4a416ece、主專案 commit 8edbaf944,1.21.0 出貨)。驗證:DEV 27 份範本修前修後逐份比對走訪結果一致,0 份被新規則擋下
§6

M08-4 🟡 解鎖檔使用紀錄毀損時,系統當作一張都還沒用過

欄位 內容
嚴重度 🟡 中(總表 #91;STRIDE:竄改資料)
在哪裡 防竄改檢查模組:機器因偵測到竄改而被鎖住後,客戶拿原廠簽發的一次性解鎖檔來解鎖
攻擊面位置 不是網路攻擊面:要有客戶主機的寫入權限,而且手上握有一張以前用過的解鎖檔。發生在服務開機那一刻,不經過任何 API
信任邊界位置 主機檔案系統 ↔︎ 開機閘門。「哪些解鎖檔用過了」記在主機上的一個檔案;這個檔案讀不出來時,閘門應該懷疑而不是相信。當時「檔案不存在」(合理,從沒解鎖過)與「檔案內容毀損」(不合理)被混為一談,都當成空帳本
元件端點位置 套件 jedi-integrity/jedi_integrity/app/unlock.py:try_redeem() 核銷、_read_used_nonces() 讀帳本;帳本檔是鎖定標記目錄下的 used-nonces.json,解鎖檔是同目錄 unlock.token;由 jedi_integrity/app/startup_gate.py 在開機時呼叫
駭客怎麼打 ① 有主機權限的人,手上留著一張以前合法用過的解鎖檔;② 機器再次被鎖住後,把「已用過的解鎖檔清單」那個檔案改成亂碼;③ 開機時閘門讀不出這個檔,當作一張都沒用過;④ 把舊解鎖檔放回去,第四道「用過沒」的檢查失效。但還有三道擋著:舊檔要對得上這台機器的指紋、這一次鎖定的事件編號、而且還在有效期限內——所以實際能復活的情況很窄
得手什麼 一次性解鎖檔被當成可重複用,降低了「每次鎖定都要原廠重新研判」的保障
修正的做法 不修的理由(決策者 10-01 裁定):做得到的人要同時有主機權限、又握有舊解鎖檔,而且還受指紋、事件編號、效期三道限制;直接拒絕會讓正常的檔案損壞把客戶機器永久鎖死,原廠目前沒有「帳本毀損後重新簽發」的流程。已做的補強:CM-2053 把「不存在」與「毀損」分開,毀損時印出高等級警示(含請客戶連同機器指紋回報原廠),留下痕跡;等原廠有重簽流程再改成拒絕
狀態 🚫 裁定不修(決策者 10-01;CM-2053 已加毀損警示,套件 commit 953697c9)
§7

總表#103 🟡 寫日誌進資料庫失敗會連帶弄壞使用者的請求

欄位 內容
嚴重度 🟡 中(總表 #103;STRIDE:讓服務停擺;另一套分類 A09 日誌與告警失效)
在哪裡 共用基礎:系統把操作紀錄與錯誤寫進資料庫的那一段(每個請求都可能經過)
攻擊面位置 不是駭客能主動挑的入口(檢查員三票一致認定非資安問題);只要資料庫那一刻寫不進去(連線斷、表被鎖、權限被收回),任何正常使用者的請求都可能被連累。正式環境原本不走這條路,CM-1920 為了「只放行稽核事件與錯誤」在正式/STG 環境補掛了這段,風險因此放大
信任邊界位置 業務程式 ↔︎ 紀錄機制。紀錄是附帶動作,失敗時應該自己吞掉、印到旁邊;當時寫資料庫那支完全沒有錯誤處理,錯誤沿著「記一筆日誌」的呼叫點往上冒,回到業務程式變成使用者看到的系統錯誤
元件端點位置 套件 jedi-common/jedi_common/logger/db_log/db_handler.py:DBLogHandler.emit();掛載設定在同套件 logger/config_prod.py(CM-1920 commit 4bf7906 補掛)
駭客怎麼打 ①(不是駭客主動打)一般使用者在正式環境正常操作,某個動作會記一筆紀錄;② 那一刻資料庫寫不進去;③ 錯誤從紀錄機制一路往上冒,把使用者原本已經做完的請求一起弄成失敗;④ 另一個隱藏問題:這支用到的時間欄位要靠前面的紀錄處理器順便填好,它能正常運作純粹是因為剛好排在第三個,換個順序就會自己出錯
得手什麼 沒有人得手;「記錄失敗」這種小事變成使用者的操作失敗
修正的做法 ① emit() 整支包上錯誤處理,失敗時呼叫紀錄機制的標準出口 handleError(record)(印到錯誤輸出、不回呼紀錄機制,不會無限迴圈);② 拆掉隱性的排序依賴:時間改讀一定有值的 record.created,不再依賴前一個處理器填好的格式化時間;③ 操作者帳號與暱稱改成「有就記、沒有就空著」,缺了只少記操作者、不再出錯
狀態 ✅ 已修(CM-2065/FR-114.5C-4,套件 commit a9562dd0,1.21.0 出貨)
§8

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

欄位 內容
嚴重度 🟡 中(總表 #108;STRIDE:權限提升、讓服務停擺;另一套分類 A01 存取控制失效)
在哪裡 客戶資料隔離專項:每天凌晨兩支自動清理程式——01:00 清法規框架解析的舊工作單、03:10 清「指向已刪除任務」的孤兒資料
攻擊面位置 不是攻擊面,是規劃上的相依:兩支程式沒有使用者身分,當時能看到全部客戶資料,靠的是資料庫連線那層「沒有身分就給最高權限」的放行規則(CM-1559 已記錄那條規則本身就是個洞)
信任邊界位置 背景程式 ↔︎ 資料庫隔離規則。要跨客戶掃描的背景程式應該顯式宣告「我是系統、我要跨客戶」,而不是靠「找不到身分」這個失敗狀態被放行;靠失敗狀態放行的那一天被收緊,兩支程式會看不到任何資料、安靜地什麼都不做
元件端點位置 主專案 core/scheduler.py:_framework_parse_job_cleanup_tick()(01:00)、_job_binding_orphan_cleanup_tick()(03:10)→ app/flow_control/service/job_binding_orphan_cleanup_service.py 的 run_once();具名身分工具在套件 jedi_common.session.auth.system_context
駭客怎麼打 ①(不是駭客,是未來的修補動作)有人去收緊「沒有身分就給最高權限」那條規則(遲早要做,本身就是一個洞);② 兩支清理程式從此連線時看不到任何客戶資料;③ 它們不報錯,只是每晚掃到 0 筆;④ 孤兒資料與過期工作單一直堆積,沒有人發現
得手什麼 沒有人得手;是清理機制在不知不覺中停擺
修正的做法 ① 套件新增 system_context("<排程名>"),把「繞過隔離」從失敗狀態的副作用改成一個要顯式宣告、有名字的動作;② 兩支排程各自包上具名系統身分;孤兒清理那支要在服務自開資料庫連線之前就設好身分,否則那條連線讀不到;③ 原本「沒有身分就給最高權限」的說明文字改寫成反向警語;④ 加守衛測試掃排程程式碼,確認跨客戶的排程都有具名身分,突變(拿掉)會轉紅
狀態 ✅ 已修(CM-1767/FR-094.1a,主專案 commit 3af065133、套件 commit 2bd93918)。2026-09-16 複查確認兩支都以具名系統身分執行
§9

M11-22 🟡 框架 PDF 解析失敗時把原始錯誤訊息整段回給前端

欄位 內容
嚴重度 🟡 中(總表 #155;STRIDE:資料外洩)
在哪裡 合規文件核心:平台管理員上傳法規框架 PDF,系統自動解析出控制項
攻擊面位置 要先是平台管理員(框架是全域資源,匯入只限最上層),所以評中。設計上就開放上傳任意檔案給這個角色
信任邊界位置 伺服器內部 ↔︎ 使用者畫面。程式出錯的原始訊息只該留在伺服器日誌;當時解析失敗時把「白話訊息:原始錯誤」整串存進解析單,查詢時原樣回給前端。而同一段也包著「寫資料庫」,寫庫失敗時連資料庫語句都會帶出去
元件端點位置 主專案 api/oscal/__init__.py:POST /oscal-framework-parse-jobs/parse(FrameworkParseJobParseRoute)→ app/oscal/service/framework_parse_job_service.py 的 parse()(require_platform_admin())→ 失敗時 write_error();讀回:GET /oscal-framework-parse-jobs/{uid}
駭客怎麼打 ① 拿到平台管理員帳號的人,打開框架匯入;② 上傳一份故意弄壞的 PDF;③ 解析失敗,系統把程式的原始錯誤(伺服器路徑、用的是哪一版解析套件)整段存起來;④ 打開解析結果就看到這些內部細節,不必自己猜伺服器怎麼裝的;⑤ 若讓寫資料庫那一步也失敗,連資料庫語句都會顯示出來
得手什麼 伺服器路徑、第三方套件與版本、資料庫語句片段——下一步攻擊的地圖
修正的做法 ① 解析單與回給前端的只放固定錯誤碼 GRC_FRAMEWORK_PARSE_FAILED 與白話訊息,原始錯誤只用 logger.exception 進伺服器日誌;② 寫資料庫那段在同一個保護範圍內,寫庫失敗也只存白話訊息;③ 已經存在、可能帶路徑的舊解析單不另外修資料(解析單 24 小時就過期)
狀態 ✅ 已修(FR-114 CM-2183,commit c33f7e828,1.21.0 出貨)。驗證:DEV 上傳亂碼 PDF,畫面只見「框架 PDF 解析失敗」,日誌留完整原始錯誤
§10

M02-12 🟡 刪原廠共用檔時實體先被刪、紀錄卻刪不掉

欄位 內容
嚴重度 🟡 中(總表 #212;STRIDE:讓服務停擺、竄改資料;另一套分類 A01 存取控制失效)
在哪裡 檔案上傳下載模組:刪除檔案;受害的是原廠給所有客戶共用的檔案(框架匯入的公版指引、檢測規則包等)
攻擊面位置 任何一家客戶的一般登入帳號都構成攻擊面,只要知道一個原廠共用檔的編號
信任邊界位置 客戶 ↔︎ 原廠共用資源。刪除時應該先問「這個檔是不是原廠的、你能不能刪」,而且資料庫紀錄沒刪成就不能動實體檔。當時順序是先刪實體、再刪紀錄;紀錄那一步被資料庫隔離規則擋下,但實體已經沒了,而程式不看刪除結果、照樣回成功
元件端點位置 jedi_file_upload/api/routing.py:DELETE /file/upload/{uid}(UploadFileRoute.delete)→ 儲存後端 infra/adapter/minio/minio_adapter.py(落地版的 SeaweedFS 也走這支)與 infra/adapter/local/local_file_adapter.py 的 delete_file()/delete_files_by_uids();歸屬檢查在主專案 core/plugins/file_upload.py 的 SystemAssetFileOwnershipProvider
駭客怎麼打 ① 任何一家客戶的一般員工,在畫面上看到一個原廠範本檔的編號;② 對這個編號按刪除;③ 系統先把儲存空間裡的實體檔刪掉;④ 接著刪資料庫紀錄時被隔離規則擋下(不是原廠),但程式沒看結果,回「刪除成功」;⑤ 紀錄還在、檔案沒了,所有客戶打開這個範本都是壞檔
得手什麼 一個帳號就讓原廠給所有客戶的共用範本全部打不開
修正的做法 分兩步:① 原廠共用檔禁刪:歸屬登記表裡,原廠共用檔對「寫入(刪除)」一律回「找不到」,只有平台管理員走例外口(CM-2033 接上歸屬檢查時一起做);② 刪除順序反過來:新開共用的 record_first_delete.py,先刪紀錄,紀錄真的刪掉才刪實體;實體刪失敗只記日誌(留孤兒實體檔比留壞紀錄好);批次刪除刪完再查一次,只對真的刪掉的那幾筆動實體;衍生的 PDF 轉檔同樣改順序
狀態 ✅ 已修(禁刪:CM-2033 主專案 commit a334f5385;刪除順序:CM-2228 套件 commit 5ec9b617,1.21.0 出貨)。驗證:新增 10 條測試,突變改回「先刪實體」3 條轉紅
§11

M10-12 ⚪ 更新任務指派時撈不到既有紀錄就跳過管理者檢查

欄位 內容
嚴重度 ⚪ 低(總表 #110;STRIDE:權限提升;另一套分類 A01 存取控制失效)
在哪裡 任務與成員管理模組:修改某個任務的被指派人
攻擊面位置 已登入使用者可打的任務指派 API。今天打不穿:後面還有一道「這筆指派是否已存在」的檢查頂著,實際結果是「查無資料」而不是「未經授權的寫入」
信任邊界位置 使用者 ↔︎ 任務指派服務。「是不是這個專案的管理者」應該在任何寫入前無條件檢查;當時寫成「撈得到既有紀錄才檢查」,撈不到時整段檢查被跳過、繼續往下走,等於把守門交給後面那道剛好擋得住的檢查
元件端點位置 jedi-task-platform/jedi_task_platform/participant/api/routing.py:PUT /task-assignee(TaskAssigneeRoute.put)→ participant/app/service/task_assignee_service.py 的 update_task_assignee()
駭客怎麼打 ① 任一登入帳號;② 送一個「更新任務指派」的請求,指向一筆根本不存在的指派;③ 程式撈不到既有紀錄,管理者檢查整段跳過;④ 今天靠後面那道檢查回「找不到」;但哪天有人把那支改成「查不到就當新增」,這個請求就會直接寫進去,而且沒有任何守門
得手什麼 今天什麼都得不到;是一道會靜默消失的把關
修正的做法 ① 撈不到既有紀錄就明確拒絕(回「找不到」,錯誤碼 GRC_TASK_ASSIGNEE_NOT_FOUND),不再跳過守門往下走;② 形狀照抄同一支檔案「刪除指派」已驗證過的寫法;③ 程式旁留警語「撈不到就拒絕、不可跳過守門」,防日後改成新增時把把關一起拿掉
狀態 ✅ 已修(FR-114.1-3/CM-2023,套件 commit 50c6e0f0,1.21.0 出貨)。驗證:實跑「更新撈不到被擋、正常更新放行」
§12

M05-12 ⚪ 問卷資料夾列表把資料庫原始錯誤整句吐回畫面

欄位 內容
嚴重度 ⚪ 低(總表 #127;STRIDE:資料外洩)
在哪裡 問卷模組:問卷資料夾列表
攻擊面位置 已登入使用者可打的資料夾列表 API,任何能看問卷的帳號都構成攻擊面
信任邊界位置 伺服器內部 ↔︎ 使用者畫面。產品有一套統一錯誤處理(回固定錯誤碼、原文進日誌);這支自己包了一層「不管出什麼錯都接住、把錯誤內容原樣回傳」,繞過了統一處理
元件端點位置 jedi_survey/api/routing.py:POST /survey-folders(SurveyFoldersRoute.post,jedi_survey/api/routes/survey_folder_route.py)
駭客怎麼打 ① 任一能看問卷的帳號,打開問卷資料夾列表;② 改請求內容,在查詢條件裡塞一個型別不對的值(例如該填數字的地方填文字);③ 資料庫報錯;④ 這支把錯誤原文整句回到畫面,資料表名稱、欄位名稱、查詢片段全看得到
得手什麼 問卷那塊的資料表結構,方便拼下一步攻擊
修正的做法 ① 拿掉 try/except Exception 後「回傳原始錯誤訊息」那段,讓錯誤照原路交給統一錯誤處理(固定錯誤碼+完整日誌);② 同一支的查詢條件改由後端寫死(只查未刪除、預設排除系統自動產生的資料夾),不再接任何請求參數,這個錯誤面本身也收掉了
狀態 ✅ 已修(CM-2060,套件 commit 54ac36f7,1.21.0 出貨)
§13

總表#145 ⚪ 公告查無此筆對空值設值、沒有登入身分時建立公告崩潰

欄位 內容
嚴重度 ⚪ 低(總表 #145;STRIDE:讓服務停擺)
在哪裡 公告模組:修改公告、建立公告
攻擊面位置 掃描當時沒有任何呼叫者:主專案走自己那份公告程式,套件那份是死碼,所以打不到。兩處都只會讓該次請求 500,不是資料外洩也不是權限繞過
信任邊界位置 路由層 ↔︎ 服務層的身分傳遞。服務層應該信任路由層交給它的「操作者是誰」,自己不去抓;當時服務層自己去取登入者再取 .uid,取不到時對空值取值直接崩潰。另一處是存在性檢查查錯對象
元件端點位置 套件 jedi_bulletin/api/routing.py:POST /bulletin(BulletinCreateRoute)、PUT /bulletin/{uid}(BulletinDetailRoute.put)→ jedi_bulletin/app/service/bulletin_service.py 的 add_bulletin()/update_bulletin();存在性檢查在 jedi_bulletin/infra/repository/bulletin_repo_impl.py 的 update()
駭客怎麼打 ① (當時沒有入口,以下是有入口時的樣子)有人修改一則不存在的公告;② 程式想確認「這筆存在嗎」,卻檢查了傳進來的參數、不是查詢結果,以為存在;③ 下一行對空值設值,該次請求 500;④ 另一條:背景程式或排程在沒有登入身分的情況下建公告,取登入者編號時崩潰
得手什麼 沒有人得手;是該次請求失敗
修正的做法 公告整塊從主專案搬進套件時一併處理:① 存在性檢查改成檢查查詢結果(if not bulletin_model),查無此筆回空、上層轉成「公告不存在」;② 服務層不再自己抓登入者,改由路由層用 current_user_login_name() 取帳號傳進來,服務層只收「操作者是誰」這個參數
狀態 ✅ 已修(FR-080 第 5 棒/CM-1625,套件 commit 3f27793f)。⚠️ 該 commit 是整塊搬遷,訊息沒有點名本條,修正依據是逐行比對 diff 確認兩處都已改
§14

M11-32 ⚪ Word 打不開時錯誤訊息帶出伺服器暫存檔路徑

欄位 內容
嚴重度 ⚪ 低(總表 #194;STRIDE:資料外洩)
在哪裡 合規文件核心:在專案裡匯入系統安全計畫(SSP)的 Word 檔
攻擊面位置 已登入、能匯入 SSP 的使用者。設計上就開放上傳任意檔案
信任邊界位置 伺服器內部 ↔︎ 使用者畫面,與 M11-22 同一個病:解析 Word 的程式把原始錯誤字串直接當成給使用者看的訊息
元件端點位置 主專案 api/oscal/__init__.py:POST /ssp-docx-imports/parse(SspDocxImportParseRoute)→ app/oscal/service/ssp_docx_import_app_service.py → domain/oscal/parser/docx_parser_core.py 的 ParseException;讀回:GET /ssp-docx-import/{parse_uid}。同形狀的還有 Excel 匯入 app/oscal/service/ssp_excel_import_app_service.py
駭客怎麼打 ① 能匯入 SSP 的使用者;② 上傳一個副檔名是 .docx、內容卻不是 Word 的檔案;③ 解析失敗,畫面顯示類似「找不到 /var/folders/…/tmpxxx.docx」的訊息;④ 伺服器暫存目錄的完整路徑就外洩了
得手什麼 伺服器暫存目錄的路徑(洩漏範圍只有這個)
修正的做法 與 M11-22 同一張卡:① ParseException 的訊息只放錯誤碼對應的白話訊息,不再夾帶原始錯誤,原始錯誤用 raise … from e 掛在後面,日誌照樣看得到完整鏈;② 新增 public_parse_error():業務上的錯誤(找不到控制項編號、框架不符)照原樣給使用者,其他一律回 GRC_DOCX_PARSE_FAILED;③ Excel 匯入的「非預期錯誤」分支同樣改回 GRC_EXCEL_INVALID_FILE 白話訊息
狀態 ✅ 已修(FR-114 CM-2183,commit c33f7e828,1.21.0 出貨)。驗證:DEV 上傳非 zip 的 .docx,畫面只見「docx 結構損壞,解析失敗」
§15

M24-16 ⚪ 首次安裝精靈設定碼塞中文或特殊字元,伺服器回 500

欄位 內容
嚴重度 ⚪ 低(總表 #214;STRIDE:讓服務停擺)
在哪裡 主系統:新機第一次開站的安裝精靈,要求輸入安裝程式印出來的設定碼
攻擊面位置 不需要帳號,但只在系統還沒開通時打得到(開通後這組入口就封了)。任何連得到這台新機的人都構成攻擊面
信任邊界位置 未登入的外部 ↔︎ 開站入口。比對設定碼時應該對任何輸入都安全地回「不對」;當時用的比對函式遇到非英數字(中文、表情符號、網頁伺服器解出的高位字元)會直接拋錯,變成 500
元件端點位置 主專案 api/setup/__init__.py:GET /setup/status、POST /setup/provision(api/setup/routes/setup_route.py,X-Setup-Token 標頭)→ common/setup/setup_token.py 的 verify_token()
駭客怎麼打 ① 在客戶剛裝好、還沒開通的時候連到這台機器,不需要帳號;② 在設定碼標頭裡塞中文或特殊字元送出;③ 比對函式拋錯,伺服器回 500;④ 重複送,錯誤警報被灌滿,真正的錯誤被淹掉。不會放行、也不會洩漏設定碼
得手什麼 用假的錯誤警報淹沒真的錯誤
修正的做法 ① 比對前兩邊都先轉成位元組再比(固定時間比對不變);② 轉不過去(孤立的特殊字元)或型別不對一律回「不符」,維持失敗就拒絕。實際做法與模組頁原建議的「只收英數字」不同:改成「任何字元都能安全比對、不符就拒」,效果相同且不必另外維護字元白名單
狀態 ✅ 已修(8-E,CM-2215,commit 822216b6c,1.21.0 出貨)。驗證:帶「中文」「ÿéü」狀態查詢回 200、開通回 403,日誌 0 個錯誤堆疊
§16

M24-15 ⚪ 雲端硬碟通知說有去重複、實際沒做

欄位 內容
嚴重度 ⚪ 低(總表 #219;STRIDE:讓服務停擺)
在哪裡 主系統:Google 雲端硬碟有變動時推通知過來,系統排一個同步工作
攻擊面位置 對外開放的通知接收口,不帶使用者登入(是 Google 打進來),靠通道編號+通道密鑰比對才收。能送出有效通知的只有 Google 或握有密鑰的人
信任邊界位置 Google ↔︎ 我們後端。通知通過密鑰比對後,每一則都排一個同步工作;程式說明寫「工作端會依待辦狀態去重複」,實際排工作時沒有檢查是否已有同一租戶的待辦工作
元件端點位置 主專案 api/cloud_integration/__init__.py:POST /webhooks/google-drive/{tenant_id}(GoogleDriveWebhookRoute)→ app/cloud_integration/service/google_drive_webhook_service.py 的 handle_notification() → domain/cloud_integration/service/drive_sync_job_domain_service.py 的 enqueue()(直接新增一筆,不查重複)
駭客怎麼打 ①(多半是 Google 正常行為)雲端硬碟短時間內推來多則通知;② 每一則都通過驗證、各排一個同步工作;③ 同步多跑幾次。同步本身可以重跑、不會出錯,只多耗一點資源
得手什麼 沒有人得手;只是多跑幾次同步
修正的做法 不修的理由:重複通知只會讓同步多跑一次,同步本身可重跑、不會出錯,屬程式品質問題,不影響資料或權限
狀態 🚫 裁定不修
§17

M06-25 ⚪ 孤兒清理排程若沒帶身分,會把正常資料整批判成孤兒刪掉

欄位 內容
嚴重度 ⚪ 低(總表 #226;STRIDE:竄改資料)
在哪裡 稽核流程模組:每天凌晨 03:10 清「指向已刪除任務」的孤兒綁定資料(任務與證據、設備等的對應關係)
攻擊面位置 不是外部能打的面,今天不會發生(排程有帶系統身分,見 M20-4)。風險在「哪天身分沒帶上」——例如有人改排程時漏掉、或身分機制出錯
信任邊界位置 背景程式 ↔︎ 資料庫隔離規則。判斷孤兒的方式是「綁定資料指向的任務,我看不看得到」;身分沒帶上時資料庫讓它一筆任務都看不到,判斷方向就整個反過來——所有綁定資料都變成「孤兒」。刪之前應該先確認「我看得到東西」
元件端點位置 主專案 core/scheduler.py 的 _job_binding_orphan_cleanup_tick() → app/flow_control/service/job_binding_orphan_cleanup_service.py 的 run_once() → infra/flow_control/repository/job_binding_orphan_query.py(掃四張綁定表、刪孤兒)
駭客怎麼打 ①(不是駭客,是哪一次身分沒帶上)凌晨清理排程啟動,但沒有帶系統身分;② 資料庫隔離讓它看不到任何任務;③ 程式以為「每一筆綁定都指向不存在的任務」;④ 把所有客戶的任務與證據對應關係一次清空。開發環境已用可撤回的方式實測過這個方向
得手什麼 沒有人得手;是一夜之間所有客戶的任務與證據對應關係被清掉
修正的做法 ① run_once() 刪除前先問一次「這個身分看得到幾筆任務」(infra 層新增 count_visible_jobs());② 看得到 0 筆就判定身分沒設好,記錯誤日誌、本輪一筆都不刪;③ 判斷放在服務層,資料存取層只多一支計數
狀態 ✅ 已修(8-A,CM-2211,commit fe928859f,1.21.0 出貨)。驗證:模擬看得到 0 筆時四張表都記錯誤、刪除 0 次;10 筆時照常刪
§18

M24-17 ⚪ 匯出診斷包時一邊帶時區一邊不帶,程式當掉回 500

欄位 內容
嚴重度 ⚪ 低(總表 #227;STRIDE:讓服務停擺)
在哪裡 主系統:客戶的平台管理員在畫面上選時間範圍、下載診斷包給原廠排查
攻擊面位置 需要登入且是平台管理員(服務層呼叫 require_platform_admin())。多半是正常操作踩到,不是攻擊
信任邊界位置 使用者輸入 ↔︎ 服務層。兩個時間在比較前應該先統一格式;當時只帶起始時間(帶時區)、不帶結束時間時,程式自己補的結束時間不帶時區,兩者一比就拋錯變 500
元件端點位置 主專案 api/support/__init__.py:POST /support/diagnostic-bundle(DiagnosticBundleRoute)→ app/support/service/diag_bundle_export_service.py 的 _resolve_window()
駭客怎麼打 ①(正常操作就會踩到)平台管理員要匯出診斷包;② 只填起始時間、而且是帶時區的格式,結束時間留空;③ 程式比較兩個時間時拋錯,回 500;④ 診斷包匯不出來,排查被耽誤。不會外洩任何東西
得手什麼 沒有人得手;是維運拿不到診斷包
修正的做法 ① 比較前兩個時間都先過打包核心既有的 _as_naive_local(),統一成同一種格式(沿用同一支、不另寫);② 不合法的範圍照舊回 400、不夾成邊界值——靜默夾住會讓人以為抓了七天、實際只有一天
狀態 ✅ 已修(8-D,CM-2214,commit d39181f3b,1.21.0 出貨;同卡退回補修的 046553756 屬同卡另一條主機指令問題、與本條無關)。驗證:起始帶時區+結束不帶、結束帶 UTC+起始不帶,都正常回傳