Guidant AI 資安檢視 · 模組報告

問卷(jedi-survey)

稽核用的問卷從出題到填答都走這一塊。這裡有整份報告最乾淨的一個對照——檢視當時七個「寫」的入口全部有權限檢查,七個「讀」的入口一個都沒有,就散在同一批檔案裡、寫法一模一樣,差別只在有沒有多問一句。本塊 13 條資安問題已全數修好、隨 1.21.0 出貨。

§1

這塊在產品裡做什麼

稽核工作的核心動作之一是「發問卷、收答案」。這一塊負責整條流程:

它平常在做什麼

  1. 稽核人員出題——建問卷、分頁、題目、選項,放進資料夾管理
  2. 把問卷指派給受檢單位的某個人
  3. 受檢人員填答,可以多人同時編輯同一份
  4. 稽核人員審核、寫意見,需要時退回上一版重填

為什麼這塊特別要緊

問卷裡裝的是稽核過程本身——受檢單位坦承了什麼、稽核人員判斷哪裡有缺失、退回的理由寫了什麼。

這些內容在稽核結束之前本來就該只有當事雙方看得到。一家公司內部不同部門之間看到彼此的稽核答案,受檢單位以後就不敢據實填了。


§2

檢視軌跡

檢視期間:2026-09-17 ~ 2026-09-23|範圍:模組本體 81 個檔案(正式程式共 162 個,剔除 81 支沒有邏輯的檔案);另加我們主系統這一端的接線 14 個|共 4 輪(模組本體 2 輪,第二輪再拆成兩批執行;主系統接線 2 輪)

原始技術報告放在需求中心的 FR-109 站,檔名見下表最後一欄。那裡有每一輪的完整技術細節、檔案清單與逐條推理,供需要深究或稽核抽查時查閱。

輪次 查什麼 檔數 覆核投票 結果 原始報告檔名
V2 填答那一半——指派、填答、歷史版本、多人同時編輯 31 24 票全投完、零漏投 通過 7 條(2 高 5 中),全部是新的 scan-V2-answering-chain
V1 出題那一半——問卷、題組、題目、選項、資料夾、討論、匯入匯出 50 24 票全投完、零漏投(兩批各 15 票與 9 票) 通過 7 條,其中 5 條是新的(3 中 2 低),另 2 條與 V2 重複 scan-V1-authoring-chain
W1(主系統接線) 我們主系統這一端怎麼把問卷接上來 8 6 票全投完 淨新增 1 條低(第 14 條,填答那一半沒擋「有沒有買問卷」);其餘與既有第 9 條同一件事 scan-W1(在 FR-115 站)
W2(主系統接線) 任務那一端怎麼使用問卷與檢測 6 15 票全投完 屬於問卷的部分與既有條目重複;新的一條落在稽核流程那塊(任務清單不查專案成員) scan-W2(在 FR-115 站)

第二輪第一次跑失敗了,重切之後才成功——這件事要交代清楚

50 個檔案一次跑,跑了四個小時、負責檢視的程序七次被系統的停滯偵測中斷、完全沒有產出。決策者裁定拆成兩批各 25 個檔案重跑,兩批都順利完成、票也都投完。

換句話說,這一輪的結果不是第一次就拿到的,而是換了做法之後才拿到。這是「每一輪不要塞太多檔案」這條規矩再一次被驗證。

兩輪一共推翻了四個開工時的懷疑,其中一個推翻得特別扎實

開工時懷疑「客戶之間的資料隔離可能在某個環節斷掉」。執行者連進開發環境用唯讀方式實際查證——隔離鏈是通的,資料庫執行規則時會一層一層往上套,最後收斂到問卷自己的客戶欄位。

統籌者另外查了安裝程式,確認它設定出來的正式服務是用受隔離的帳號連資料庫,能繞過隔離的那個帳號只在裝機與備份還原時才用。

所以這一塊的十二條問題,影響範圍確定是「同一家客戶內部跨專案、跨部門」,不會跨到別家客戶。 這個結論不是推論出來的,是實際查出來的。

但同一次查證也帶出一個要記下來的依賴

四張問卷翻譯表自己的隔離規則只驗「上層資料還在不在」,靠上層表自己的規則往上遞迴擋。鏈是通的,但前提是上層的規則一直正確——上層哪天放寬,這四張表會跟著破,而且從外面看不出跡象。建議在資料庫文件裡把這層依賴標明。


§3

問題一覽

排序按風險,同風險的同一類排在一起。「出事會怎樣」那欄講的是業務影響——誰受影響、損失什麼,不是技術現象。

# 問題 風險 分類 出事會怎樣(誰受影響) 怎麼修 狀態
1 查填答歷史明細完全不檢查資料歸屬,送空白請求就整包讀走 🟠 高 只驗登入、不檢查歸屬 同一家公司內任何有帳號的人,都能讀走全公司每一份問卷的每一版答案與審核意見——包含受檢單位坦承的缺失、稽核人員的判斷。這是稽核過程本身外洩,不是一般資料外洩 問卷編號改成必填,拿到編號之後再確認呼叫者是不是該問卷所屬專案的參與者(現成的判斷已經有了,直接用)。任務歸屬改由宿主注入全系統唯一判斷(FR-114 CM-2114),修正未指派任務問卷被誤擋 ✅ 已修(CM-2034)
2 讀某份問卷的答案不檢查這份問卷是不是你的 🟠 高 只驗登入、不檢查歸屬 拿一個問卷編號(第 3 條那支功能就會給)就能讀走別部門的作答內容、分數與審核意見。同一支服務裡另外三支寫入方法每一支都有檢查,只有這支讀取漏掉 取出問卷之後補一句「呼叫者是不是這個專案的參與者」,用現成的判斷,一行就好。任務歸屬改由宿主注入全系統唯一判斷(FR-114 CM-2114),修正未指派任務問卷被誤擋 ✅ 已修(CM-2034)
3 列出任務問卷不限範圍,送空白請求就回整個客戶的全部 🟡 中 只驗登入、不檢查歸屬 是第 2 條的入場券。外洩的是全公司問卷的編號、所屬專案、狀態、受檢設備與部門 專案編號改成必填,再確認呼叫者是不是該專案的參與者。任務歸屬改由宿主注入全系統唯一判斷(FR-114 CM-2114),修正未指派任務問卷被誤擋 ✅ 已修(CM-2034)
4 列出填答歷史不限範圍 🟡 中 只驗登入、不檢查歸屬 可以看出全公司跨所有專案裡誰在什麼時候改了哪份問卷;同時提供第 1 與第 6 條需要的版本編號 問卷編號改成必填,再確認呼叫者是不是該問卷所屬專案的參與者。任務歸屬改由宿主注入全系統唯一判斷(FR-114 CM-2114),修正未指派任務問卷被誤擋 ✅ 已修(CM-2034)
5 問卷討論列表送空查詢就把全公司討論撈回來 🟡 中 只驗登入、不檢查歸屬 討論串裡是稽核過程的缺失細節與審查意見,含你完全沒參與的專案 兩個查詢條件改必填,再確認呼叫者是不是該專案的參與者(現成的判斷已經有了)。任務歸屬改由宿主注入全系統唯一判斷(FR-114 CM-2114),修正未指派任務問卷被誤擋 ✅ 已修(CM-2034)
6 還原歷史版本時不檢查「你指定的版本是不是這份問卷的」 🟡 中 只驗登入、不檢查歸屬 可以把別部門問卷的答案複製進自己有權限的那一份裡——這不只是看到,是把別人的內容搬進來 已修(FR-114.1-4a):取出歷史版本後,比對它記的問卷編號與這次操作的問卷是不是同一份,不同就回「找不到」。比對放在推播之前——原本還原成功會對同一份問卷的其他人推「重新載入」,比對失敗若不先擋就會推一個空事件出去。權限照舊走嚴的那條(被指派人本人或專案管理者),沒有跟著放寬 ✅ 已修(FR-114.1-4a)
7 多人同時填問卷的「房間」想進哪間就進哪間 🟡 中 只驗登入、不檢查歸屬 可以旁聽別專案正在編輯中的問卷,還能抓到當下的答案快照 進房間前先確認呼叫者是不是該問卷所屬專案的參與者,讀答案快照那條路要用同一道檢查守住。任務歸屬改由宿主注入全系統唯一判斷(FR-114 CM-2114),修正未指派任務問卷被誤擋 ✅ 已修(CM-2034)
8 改/刪問卷討論不檢查是不是本人寫的,改完還掛原作者名字 🟡 中 只驗登入、不檢查歸屬 門檻不是零——要按得動改/刪,得先有問卷的修改或刪除權限,一般登入帳號動不了。但那道權限只問「你有沒有改問卷的權力」,不問「這則留言是不是你寫的」,所以有問卷權限的人可以竄改別人的討論內容、而且改完還掛著原作者的名字;刪除是直接從資料庫抹掉、不留痕跡。對稽核產品來說,討論紀錄被改而看不出來是實質缺陷 已修(FR-114.1-4a):改——動手前先載出那則留言比對建立者,不是本人就拒絕(比對建立者不是最後修改者,後者會被改留言的人覆寫)。刪——不再從資料庫抹掉,改成標記「已刪除/誰刪的/何時刪的」,資料列留著,列表讀取時濾掉;同樣限本人。管理者代刪那條路徑本卡未做(卡上只要求本人檢查),要做要另開卡 ✅ 已修(FR-114.1-4a)
9 多人同時填問卷時,「這筆是誰填的」直接採用畫面送上來的名字 🟡 中 外部送什麼就收什麼 這不是越權——能連上來的本來就是合法填答者。問題在稽核紀錄的可信度:一個合法填答者可以把自己的修改署成別人的名字,事後追查「誰改了這一題」就不可靠了 改用登入身分裡的名字,不要相信畫面送上來的值。實際做法:署名改讀登入憑證 ✅ 已修(CM-2060)
10 資料夾更新是畫面送什麼就收什麼,塞一個「已刪除」欄位就繞過守門 🟡 中 外部送什麼就收什麼 可以造出「資料夾已經刪掉、但問卷還掛在底下」的孤兒資料。根因是五個入口把自動的欄位檢查關掉之後、程式裡也沒有自己補驗一次,那道宣告等於裝飾品 「已刪除」這個欄位不准從畫面的請求收進來——「修改資料夾」與「改變刪除狀態」是兩件事,不該共用同一個入口。實際做法:改用只收名稱/說明的嚴格格式,未送欄位沿用現值 ✅ 已修(CM-2060)
11 資料夾列表可以叫出系統刻意隱藏的資料夾 ⚪ 低 外部送什麼就收什麼 可以看到流程自動產生的內部快照資料夾、以及已經刪掉的資料夾。只要登入,連權限點都沒掛 「已刪除」這個欄位不准從畫面的請求拿來當查詢條件,由程式自己寫死「只撈沒刪掉的」。同一支檔案裡的下拉選單那支就是寫對的範例——條件由程式寫死,呼叫端插不了手。實際做法:查詢條件改由後端寫死只查未刪、排除系統資料夾 ✅ 已修(CM-2060)
12 資料夾列表把資料庫的原始錯誤訊息整句吐回畫面 ⚪ 低 回應夾帶不該送的欄位 送一個型別不對的條件就會把資料表名稱、欄位名稱與查詢片段吐給前端,等於把內部結構攤開 拿掉那段「不管出什麼錯都接住、把錯誤內容原樣回傳」的程式,讓錯誤照原路往上走、由產品既有的統一錯誤碼機制接手;寫進紀錄檔那一行留著。實際做法:移除吞例外回傳原始訊息,交還統一錯誤處理 ✅ 已修(CM-2060)
14 沒買問卷模組的客戶,直接打網址照樣能用問卷的「填答」那一半 ⚪ 低 要先做產品決策 這是商務授權被繞過、不是資料外洩:問卷是加購包,沒買的客戶選單會反灰,但「填答」那半 14 個入口一個都沒檢查「有沒有買」,直接打網址就能列任務問卷、讀寫答案、看歷史版本、匯入答案(「設計問卷」那半 33 個入口有 32 個檢查了)。每個入口仍有問卷歸屬檢查,所以不會看到別人的東西。另有兩個入口同病:AI 儀表板查問卷、規劃頁把問卷掛上任務 填答那半每個入口在「必須登入」下面補一道「必須買了問卷」,三個入口一張卡(改套件,要先照外部套件異動規範提醒)。在 jedi_survey/api/routes/task_survey_route.py、question_answer_route.py、question_answer_history_route.py ✅ 已修,1.21.0 出貨(填答那半 CM-2194;經 AI 儀表板查問卷那個入口由 8-J,CM-2220,commit 80dedfbb5/套件 fb271590)

另外一條:不是資安問題,但確實是程式錯誤

# 問題 影響 怎麼修
13 問卷用 Excel 匯入失敗時,暫存檔會永遠留在磁碟上 程式把上傳的檔案存成暫存檔後連續去解析三個工作表,只要任一步出錯,後面那句「刪掉暫存檔」就跑不到。每失敗一次就留一個檔,磁碟慢慢被吃掉。另外這支匯入沒有設檔案大小上限(要先有建立問卷的權限,所以屬於內部人風險) 把刪檔那句移到「無論成功失敗都會執行」的區塊;同時補上檔案大小上限。✅ 已不再發生(CM-2251,1.21.0 出貨):匯入改在記憶體解析、不再寫暫存檔,原始檔交由儲存後端留存;大小由全站 50MB 請求上限擋著

§4

這塊最重要的一件事:寫的都有檢查,讀的一個都沒有

檢視當時:填答功能的七個「寫入」入口,七個都有權限檢查。七個「讀取」入口,零個有。

7/7 對 0/7。 就散在同一批檔案裡、同一種寫法、用的是同一個現成的檢查,差別只在有沒有多問一句。

✅ 現況:七個讀取入口都已補上歸屬檢查(第 1~7 條,CM-2034/FR-114.1-4a,1.21.0 出貨)。

為什麼會變成這樣

2026 年 7 月曾經做過一次「統一補權限檢查」的工作。那次的成果是真的——七個寫入路徑全部補上了。

關鍵在於那次立案時是怎麼描述問題的:紀錄上的第一行寫的是「任何登入者可以修改任何人的填答」。整件事從一開始就被定義成「改」的問題,「讀」從頭到尾沒有進過工作範圍。

換句話說,不是當時評估過覺得讀取可以放過,是根本沒想到「看得到不該看的」也是一種越權。補完之後也沒有人回頭檢查過另外半邊。

這件事解釋了為什麼自動掃描工具找不到這類問題

工具擅長找的 「這段程式寫錯了」——用了危險的寫法、忘了處理某個狀況
這裡的狀況 程式沒有寫錯。每一行都合法、正常運作、正確回傳資料
問題在哪 少寫了一行。而「本來應該有但沒有」這件事,要先知道「本來應該有」才看得出來

要看出這一條,得先把寫入的那一半與讀取的那一半擺在一起比對,發現「這裡有、那裡沒有」。這是人工比對才做得到的事。

讀得到什麼:其中四個最嚴重的是

七個讀取入口都沒有歸屬檢查,下面列出影響最大的四個:

讀取入口 送一個空白請求會拿到
填答歷史明細 整個客戶所有問卷的每一版答案、填答者的補充說明、稽核人員的審核意見、每次修改的時間與經手人
任務問卷列表 整個客戶的全部問卷:編號、屬於哪個專案、目前狀態、受檢設備與部門名稱、經手人
填答歷史列表 全公司跨所有專案,誰在什麼時候改了哪一份問卷
問卷討論列表 全公司所有問卷的討論串——稽核過程的缺失細節與審查意見

而且這幾支互為入場券:要讀答案得先有問卷編號,問卷列表那支就會免費給你。

要先有什麼:該客戶內任何一個有效登入帳號。什麼權限都不用,也不必參與任何專案。

要補的檢查是什麼:已經定案

讀取的門檻定在「這個專案的參與者」——只要你是這個專案的成員,不管掛什麼角色都讀得到;查不到角色的人一律擋掉。寫入維持原本較嚴的規則不變(被指派人本人,或專案管理者)。也就是說,讀比寫寬一級。

三件事要一起講清楚:

  1. 不必新寫判斷邏輯。寫入那側用的是套件自己那支判斷,讀取這側用主系統共用的那支「是不是這個專案的參與者」,兩支都已經存在。問卷這塊也拿得到——現有的守門本來就是靠主系統把「查角色」這個能力遞進來的,讀取沿用同一條路即可,不必另外接線。
  2. 不可以直接相信呼叫端送上來的專案編號。問卷本身不帶專案編號,必須先從任務反查出它屬於哪個專案,才知道要拿誰去比對。這跟「測試連線」那條定下的原則一樣:不要相信呼叫方自己報的東西,自己去查。
  3. 含意已經確認並接受:門檻定在參與者,代表專案裡「只能看」的角色也讀得到稽核答案與審核意見,但動不了。這與稽核流程那塊第 7 條的裁定一致——能看,不能動。

修的時候,兩件事必須一起做

這一組問題其實是兩個缺陷疊在一起:①查詢條件沒有必填,不填就不篩、整張表回來;②拿到編號之後,不檢查那個編號是不是你有權看的。

只做一個,另一個就是後門:

  • 只加必填 → 我填別人的編號,照樣讀得到
  • 只加歸屬檢查 → 我兩個條件都不填,它不篩,而且連「要檢查誰」都不知道,整道檢查直接跳過

順序是:先強制給編號,有了編號才有東西可以拿去檢查。

這一組涵蓋第 1、2、3、4、5、6、7 條,但第 6 條是唯一的例外:還原歷史版本是寫入動作,權限照舊走嚴的那條(被指派人本人或專案管理者),不要因為它列在同一組就跟著套用讀取的寬鬆門檻。它要補的只是「取出來的版本是不是屬於當前這份問卷」這一句比對。


下面只展開需要你自己判斷、或容易被誤解的幾條。 其餘的修法明確,看上面表格的「怎麼修」那一欄就夠了——沒有展開不代表沒查,也不代表不重要。

§5

第 10 條:一個設定讓五個入口的欄位檢查落空

這條值得單獨講,因為它跟前面那一整組「讀取沒守門」是完全不同的成因。

先講清楚一件事:那個設定本身不是錯的

產品的每個功能入口都會宣告「我只收這幾個欄位、格式要長這樣」。這是第一道防線。

有一個設定的意思是「宣告完之後先不要自動套用,改由程式自己手動驗一次」。這是全產品的正常做法——主系統有 149 處、套件庫有 81 處,合計兩百多處都這樣寫。同一支問卷套件裡,另外兩支入口就是照正確做法走的:關掉自動驗證之後,程式裡確實補了手動驗證。

真正的缺陷是那個組合:關掉了自動驗證,而且程式裡也沒補上那次手動驗證,就把畫面送上來的內容整包收下去。只有同時符合這兩點才會出事。

(這一點一定要講明白,否則照著報告去搜會撈出兩百多處,得出「整個產品都壞了」的錯誤結論——實際上有問題的是其中極少數。)

造成什麼

塞什麼 得到什麼
一個「已刪除」欄位 繞過「資料夾裡還有東西就不准刪」這道守門,造出孤兒資料
一個內部開關欄位 叫出系統刻意隱藏的資料夾(流程自動產生的快照、已刪除的)

全庫已經搜過了,結果如下

原本的建議是「其餘套件應該全部搜一遍」。這件事已經做完了——用上面那個組合條件(關掉自動驗證且沒補手動驗證)掃過主系統與全部套件:

哪裡 幾處 備註
問卷這塊 5 處 暴露程度最高,沒有額外守門
系統設定那塊 2 處 另外掛了平台管理員守門,一般帳號進不來
公告那塊 2 處 另外掛了公告的能力檢查
主系統 1 處 唯讀查詢,沒有寫入風險
合計 10 處,分佈在 5 個檔案

所以這不是一個要全庫大掃的問題,是十個已經點名的位置,其中只有問卷這 5 處完全沒有第二道防線,另外 5 處都還有一道門擋著、緊迫程度低一個檔次。

第 10、11、12 條的修法不一樣,不要當成同一招

這三條常被放在一起講,但第 10 與第 11 方向相反,第 12 的成因完全不同:

條 塞進去的是什麼 怎麼修
第 10 條(修改資料夾) 要寫進去的內容 「已刪除」這個欄位不准從請求收。「修改資料夾」與「改變刪除狀態」是兩件事,不該共用一個入口
第 11 條(資料夾列表) 查詢用的篩選條件 「已刪除」這個欄位不准從請求拿來查詢,由程式寫死「只撈沒刪掉的」。旁邊就有寫對的範例——同一支檔案裡的下拉選單那支,條件是程式自己定的,呼叫端插不了手
第 12 條 — 成因是另一段程式「不管出什麼錯都接住、把錯誤內容原樣回傳」,把產品既有的統一錯誤碼機制整個繞過去了——不是錯誤碼沒擋住,是錯誤碼從頭到尾沒被用到。修法是拿掉那段自己接住的程式,讓錯誤照原路往上走;寫進紀錄檔那一行留著

一句話記住第 10 與第 11 的關係:同一個欄位,第 10 條是「不准你寫」,第 11 條是「不准你問」。凡是屬於系統內部狀態的欄位,既不從請求寫入、也不從請求查詢。

另外:修完第 10、11 條之後,第 12 條會少掉一大半——型別不對的查詢條件在碰到資料庫之前就被擋掉了。但那段「原樣回傳錯誤」的程式還是要拿掉,因為那是兩道不同的防線:一道防「不該進來的進來了」,一道防「不該出去的出去了」。

順帶查到的一件事

全套件庫也掃過「自己接住錯誤然後原樣回傳」這種寫法,只有兩支套件有:問卷這塊 2 處、弱點檢測那塊 2 處,其餘乾淨。這不是全產品的通病,是兩支套件的個別寫法,四處都改掉就收乾淨了(弱點檢測那兩處併入該模組一起處理)。


§6

這塊的結論

一句話:這一塊十二條問題裡有七條是同一件事的不同出口——2026 年 7 月補權限時只補了「寫」的那一半,「讀」的那一半原封不動,直到這輪修正才補上;剩下五條分成兩個另外的成因。十三條資安問題全部已修好,隨 1.21.0 出貨,這塊沒有未修的條目。

修法(第 1~12、14 條已修,隨 1.21.0 出貨;以下是當時裁定的併法與要點):

  1. 第 1、2、3、4、5、6、7 條可以一次修完——它們是同一個缺口(少問一句「這份問卷所屬的專案,你有參與嗎」)的七個出口,同一套修法涵蓋得到,而且現成的判斷本來就有,直接呼叫即可。第 2 條甚至只要加一行。修的時候記住必填與歸屬檢查一定要一起做:只做必填,填別人的編號照樣讀得到;只做歸屬檢查,兩個條件都不填就連「要檢查誰」都不知道、整道檢查跳過。另外第 6 條是這一組裡唯一的例外——它是還原動作、屬於「寫」,權限照舊走嚴的那條,不要因為列在同一組就跟著放寬。
  2. 第 8 條(問卷討論的改/刪)自成一張工單——改別人的一律禁止;刪除開放給管理者,但必須留痕跡,記成「由某某管理員移除」。理由是:這是稽核產品,「紀錄可以被移除」本身不是問題,「移除了看不出來」才是。這與日誌那塊定下的精神一致——進來的照實記,交出去的時候才負責讓它安全。
  3. 第 10、11、12 條併成第三張工單——同一支檔案、同一個區塊,但三條的修法不一樣(第 10 條「不准寫」、第 11 條「不准查」、第 12 條拿掉那段原樣回傳錯誤的程式),不要當成同一招套過去。全庫已經搜過同類寫法,結果是十處、五個檔案,不是原本估的「要全庫大掃」。
  4. 好消息是跨客戶沒有破 ——這件事是實際查出來的,不是推論。所以這十二條的範圍是「同一家客戶內部」,緊迫程度比跨客戶外洩低一個檔次。但對稽核產品來說,同一家公司內部不同部門看到彼此的稽核答案,受檢單位以後就不敢據實填了,這本身就是產品價值的問題。

主系統接線那一端已補查(2026-09-23,兩輪 14 個檔):新增的只有第 14 條(沒買問卷也能填答,是商務授權問題、不是外洩),其餘與這頁既有條目重複。第 14 條可以跟第 1~7 條分開派,它不影響誰看得到誰的資料。

統計
檢視輪數 4 輪(模組本體 2 輪 81 個檔案+主系統接線 2 輪 14 個檔案)
找到的問題 14 條(13 條資安 + 1 條非資安但確實是程式錯誤)
高風險 2 條
已修 13 條(第 1~12、14 條,1.21.0 出貨)
未修 0 條
非資安的程式錯誤 1 條(第 13 條),已不再發生(CM-2251,匯入不再寫暫存檔)

你想知道 看哪裡
我們用什麼方法查、結果有多可信 檢視方法與工具
其他模組的檢視結果 回總報告首頁