Guidant AI 資安檢視 · 模組報告

公告(jedi-bulletin)

發布內部訊息給指定部門看的功能。這塊被低估了——原本按規模排在風險最低那一組,實際查下來,「誰能看到哪一則公告」的判斷繞得出乎意料,而且改刪公告從來不問「這則是不是你發的」。真正的病根不是某一段程式寫錯,而是系統分不出「你刻意要發給全公司」跟「你忘了選對象」。公告這塊的五條,四條已修好、一條裁定不補;順手撈到的六條外流憑證已撤銷換發,其中安裝程式預設管理員密碼那條已裁記錄不修。

§1

這塊在產品裡做什麼

公告是系統裡「一則訊息要發給哪些人看」的功能:管理員發一則公告、指定要發給哪些部門,其他人在自己的畫面上看到。

聽起來很單純,但它有一個容易被忽略的性質——公告是「發給很多人看、而且大家會相信」的內容。一則被竄改的公告,殺傷力跟一封冒名寄出的信是同一個量級。

它平常在做什麼

  1. 有權限的人發一則公告
  2. 指定要發給哪幾個部門
  3. 其他人在畫面上看到屬於自己部門的那些

為什麼它被低估了

按檔案數量排,這塊被歸在「只是讀取展示、攻擊面小」那一組。

但那個評估只看了一半。 「誰能看到哪一則」的判斷完全不在這塊本身,而在主系統那一端——而那一端的判斷邏輯繞得出乎意料。


§2

檢視軌跡

檢視期間:2026-09-08 ~ 2026-09-09|範圍:62 個檔案(模組本體 29、主系統接線 33)|共 2 輪

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

輪次 查什麼 檔數 覆核投票 結果 原始報告檔名
B1 模組本體——存取公告的那些程式 29 6 票全投完,兩條疑點都 3 票判定不成立 工具零發現(人工另查出 3 條,全是目前打不到的路徑) scan-B1-package-core
B2 主系統這端——「誰能看到哪一則」的判斷 33 38 票全投完,十條全部三票一致 通過 10 條(公告這塊本身 4 條,另 6 條是順手撈到的密碼外洩)。另有 1 條是這一輪點名要查、但不走投票的項目(下表第 5 條) scan-B2-host-wiring

第二輪前後撞了兩次用量上限,還踩到一個工具本身的邊界問題——差一點吃掉三條發現

第一次中斷:十一條疑點裡有八條的票沒投完。工具沒有拿部分票充數,全部交給下一輪重投。這次中斷前研究階段已經完整跑完,所以 33 個檔案確實都讀過了,斷的只有投票。

第二次中斷:又有四條沒湊滿票,而且這次沒有下一輪可以交棒——工具的處置是直接丟棄。那四條裡有三條的已投票數是全員通過,而且其中兩條正是派工時點名的重點。如果就這樣出報告,這兩個重點會在報告上留白,看起來像是「查過沒事」。

處置:用工具本身的續跑機制接回同一次執行,已投的 19 票從紀錄回放、只重派掛掉的 5 票,25 個程序全數回報。票數仍由工具自己計算,不是人工拼湊出來的。

還踩到一個併檔陷阱:同一批的兩份結果會靜默擇一保留,而兩份的編號含意不同——差一點把三條發現(含兩個重點)整批吃掉。處置方式與判準都寫在原始報告裡。

兩件沒查、不能當作乾淨

  1. 公告內容可能夾帶惡意程式碼的風險——要去畫面那一端查「公告內容是怎麼顯示出來的」才能下結論,這一輪沒有查。公告是發給很多人看的內容,這個風險的受害面比一般欄位大。
  2. 查詢稽核欄位時會不會繞過使用者資料的隔離——這一輪沒有查。

§3

問題一覽

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

# 問題 風險 分類 出事會怎樣(誰受影響) 怎麼修 狀態
1 改公告、刪公告只問「你能不能改公告」,從不問「這一則是不是你的」 🟡 中 只驗登入、不檢查歸屬 別部門發的公告被人竄改或刪掉。公告是大家會相信的內容,一則被動手腳的公告能直接誤導全公司。而且刪公告是整筆從資料庫移除、內容救不回來;改的時候若沒帶發送對象,還會靜默清空——那則公告就變成所有人都看得到 改與刪之前先確認「這則公告是不是自己發的」 ✅ 已修(FR-114.1-5a/CM-2026,套件 commit 8ca1c279)——改與刪都先比對建立者帳號,不是本人一律當作「查無此公告」(改回 404、刪回 false);同時修掉附帶的靜默清空:請求沒帶發送對象時整組維持原樣,不再清光
2 AI 儀表板這條路繞過守門直接讀公告 🟡 中 只驗登入、不檢查歸屬 任何一個登入帳號叫出 AI 儀表板、說一句「列出所有公告」,就拿到整個客戶底下的全部公告——含別部門的、已停用的草稿、還沒到發布時間的。而且前三筆內容會原文送到外部 AI 服務。
⚠️ 檢視時這條路會當場出錯、暫時走不通,但那是意外不是守門(原因見下方詳述)
和 AI 儀表板那塊一起修(見該塊報告第 2 條) ✅ 已修(CM-2038,commit 8f94e249d)——儀表板呼叫公告查詢前先檢查「看公告」權限,並強制帶上部門過濾
3 用網址直接開一則公告時,完全不做任何檢查 ⚪ 低 只驗登入、不檢查歸屬 調部門之後,舊部門的公告用舊網址還是打得開;還沒發布的公告可以提前看到。目前擋著的只有「網址裡那串編號猜不到」這一件事 與第 4 條合併成同一套設計:發送對象改成必選,讀取時一律照那個對象過濾 ✅ 已修(FR-114.1-5a/CM-2026,套件 commit 8ca1c279)——公告新增「發送範圍」欄位(全公司/指定部門),用網址直開時照該欄位判斷看不看得到,看不到回「公告不存在」
4 公告列表碰到「沒有被分配部門」的帳號,直接不過濾、全部給看 ⚪ 低 只驗登入、不檢查歸屬 沒有被分配部門的帳號,打列表就拿到整個客戶的全部公告(含草稿、含過期),連同它們的網址編號——而那些編號正好餵給上面第 3 條。而且「要不要過濾」這個開關是呼叫端自己在請求裡傳的,不是伺服器判定的 與第 3 條合併成同一套設計(見下方「一套設計解掉三件事」) ✅ 已修(FR-114.1-5a/CM-2026,套件 commit 8ca1c279)——列表改成一律照「發送範圍」過濾:沒有被分配部門的帳號只看得到「全公司」那類,不再整個跳過過濾;舊公告一律標記為全公司,行為與修改前相同
5 公告與部門的關聯表沒有設定客戶資料隔離 ⚪ 低 客戶資料沒隔開 「這則公告要發給哪些部門」這份關聯不受客戶隔離保護。目前影響有限——那張表只存兩個編號、沒有任何內容,而且要查它得先有公告編號,公告主表擋得住 裁定不補——多做一層會變成兩套規則各自維護,反而更糟(理由見下方詳述) 🚫 裁定不修(靠公告主表擋;要留一句提醒給下一個人)

檢視時順手做密碼掃描撈到的六條(與公告無關)

這六條跟公告沒有關係——是檢視時順手做的密碼掃描在整個程式庫裡撈到的,列在這裡是為了完整。不要把它們讀成「公告這塊有六條密碼問題」。

# 問題 風險 分類 出事會怎樣(誰受影響) 怎麼修 狀態
6 簽發登入憑證用的金鑰,明文躺在版控的對話紀錄裡(34 處) ⚪ 低 密碼外流 拿到這把金鑰就能自己偽造任何人的登入憑證——任何使用者、任何客戶、任何角色,不需要密碼也不需要雙因子。
🔴 但外流的只是我們開發機那一把:安裝程式每裝一套都會各自隨機產生一把新的,所以這把不會跟著出貨。影響範圍縮在我們自己的開發環境
撤銷換發、清出版控 ✅ 已修正——版控殘留字串已清(CM-2048,commit e8e134a4e);決策者裁定開發環境那把不需更換,安裝時每套各自隨機產生
7 安裝程式每裝一套都種下同一組最高權限帳號密碼,而且不強制首次登入就改 🟡 中 密碼外流 把那組密碼破解一次,就等於拿到每一套裝出去的最高權限帳號。這個帳號還被特別保護、刪不掉也停不掉。這條會跟著出貨,屬於出貨前必須處理的一條 改成安裝時隨機產生,或強制首次登入改密碼 📋 已裁記錄不修(1.21.0)——決策者裁定維持記錄、暫不調整,之後一併處理安裝期的憑證策略。
這條性質與其他五條不同
——不是「某一把外流了、換掉就好」,是每套安裝都種同一組,換發解決不了
8 資料庫管理帳號的密碼寫在文件裡,而那個帳號可以繞過所有客戶資料隔離 🟡 中 密碼外流 直接連進開發環境與出貨基準資料庫,繞過所有客戶隔離。出貨基準庫是所有安裝檔的來源,寫進去的東西會跟著裝到客戶機器上 換發密碼、清出版控 ✅ 已修正——密碼已換發,版控殘留字串已清(CM-2048,commit e8e134a4e)
9 四家 AI 服務的金鑰,完整躺在九個對話紀錄檔裡 🟡 中 密碼外流 用公司帳號燒錢;其中一把還能讀走服務端存的歷史執行紀錄,裡面例行含客戶資料 撤銷重發、清出版控 ✅ 已修正——金鑰已撤銷換發,九個對話紀錄檔裡的版控殘留字串已清(CM-2048,commit e8e134a4e)
10 公司套件倉庫與檔案儲存服務的帳密,寫在一份交接文件裡 🟡 中 密碼外流 這是供應鏈的根——有發布權的人可以推一個帶後門的元件,下次打包就自動裝進客戶手上的程式 換發、清出版控 ✅ 已修正——帳密已換發,交接文件裡的版控殘留字串已清(CM-2048,commit e8e134a4e)
11 一支資料庫調整腳本寫「環境變數沒設就用這個密碼」——而那個「這個」是真的管理員密碼 🟡 中 密碼外流 拿到程式庫就有一組可用的管理員帳密。變數忘了設時會靜默用真密碼,操作的人不會被告知 移除寫死的預設值 ✅ 已修正——該組密碼已換發;腳本改成環境變數沒設就中止並提示,版控殘留字串已清(CM-2048,commit e8e134a4e)

這六條的現況一句話

外流的通行證、金鑰、密碼都已經撤銷或換發完畢,版控裡的殘留字串也已由 CM-2048 清掉。唯一例外是第 7 條:那不是「某一把外流了」,是安裝程式每套都種同一組,換發解決不了它,已另有裁定。

這六條與寄信通知那塊列到的四條有重疊(同一批檔案、同一組密碼),問題總表請只算一次。


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

§4

公告這塊的五條,逐條說明

第 1 條:改刪公告不檢查歸屬(中,已修)

問題:系統只問「你有沒有編輯公告的權限」,從不問「這一則是不是你發的」。所以一個部門的公告編輯者,可以改掉或刪掉別部門發的任何一則公告——只要他知道那則公告的網址編號。

兩個加重的細節:

細節 後果
刪公告是整筆從資料庫移除,內容救不回來 誤刪或被惡意刪除之後,沒有任何東西可以還原。
🔴 值得記一筆的事實:那張資料表上其實有一個「是否已刪除」的欄位,但刪除那條路完全沒有用它——不是沒想到要留存,是留存的那條路沒被接上。
決策者判斷:公告只是通知用,刪了就刪了是合理的,不需要留存,所以這一項不列為待修
改的時候若沒帶發送對象,會靜默清空 一則原本發給三個部門的公告,被改一次之後變成所有人都看得到,而且不會有任何提示。
這一項由第 3、4 條那套設計一併解掉——清空之後會變成不合法的狀態、根本發不出去

修法(已修,CM-2026):改與刪之前先確認「這則公告是不是自己發的」,不是本人一律當作查無此公告。

第 2 條:AI 儀表板這條路繞過守門讀公告(中,已修)

問題:AI 儀表板可以直接呼叫「取公告列表」這支功能,這條路不經過畫面那層的任何守門。而且它呼叫時不指定「要不要按部門過濾」,所以部門過濾永遠不生效。

結果就是:任何登入帳號叫出 AI 儀表板、要它「列出所有公告」,就拿到整個客戶底下的全部公告——含別部門的、已停用的草稿、還沒到發布時間的。而且前三筆會原文送給外部 AI 服務。

檢視時的陷阱:這條路當時會當場出錯——但那不是修好

這三件事要一起看,缺一件就會誤判:

  1. 檢視當時守門缺失完全存在。
  2. 它當時打不通,是被一個不相干的改動意外擋住的,不是有人去修它。 公告功能搬家之後,儀表板傳過去的東西跟它現在認得的格式對不上,一叫就出錯。錯誤被上層接住,使用者看到的是「生成失敗」。
  3. 🔴 只修那個小錯、不補守門,這條路就會恢復暢通。 所以修法是補守門,不是靠那個意外。

這與 AI 儀表板那塊的「查使用者名冊那一支打不通」是同一類情況(問題沒變、只是被意外擋住),但成因不同。

修法(已修,CM-2038):這條與 AI 儀表板那塊是同一件事——那塊的問題是「26 支查詢一支都沒檢查權限」,公告就是其中一支。儀表板在呼叫查詢之前補了一道權限檢查,公告那支要「看公告」權限,並強制帶上部門過濾,一併涵蓋了這條。

第 3、4 條:一套設計解掉三件事(低,已修)

先看這兩條檢視時各自是什麼問題:

  • 第 3 條:用網址直接開一則公告時,系統什麼都不檢查——不看部門、不看是不是草稿、不看有沒有到發布時間、不看是不是已經停用。列表那條費心做的部門過濾,在這條路上完全繞過。
  • 第 4 條:公告列表的部門過濾,碰到「沒有被分配部門」的帳號時,直接不過濾、全部給你看。程式旁邊的說明文字自己寫明了這個行為。而且「要不要過濾」這個開關,是呼叫端自己在請求裡傳的,不是伺服器判定的。

🔴 第 4 條還有一個加重點,成因要講清楚:「已發布」與「發布時間」的過濾,跟部門過濾綁在同一個開關上。所以那個開關沒生效時,不只是別部門的公告會漏出去——草稿與過期公告也一併可見。

裁定:公告的發送對象改成必選——「全公司」或「指定部門」,兩者擇一,都沒選就不能發布。

現在的問題不是「沒有部門的人看得到全部」,是系統分不出「你刻意選全公司」跟「你忘了選」——兩者在資料上長得一模一樣。讓意圖變明確,模糊地帶就消失了。

這一個設計,一次解掉三件事:

原本的問題 為什麼這個設計解得掉
第 4 條:沒有部門的帳號,清單整個不過濾、全部給看 「全公司」變成一個明確選出來的值,不再是「沒有資料」。系統不必再猜「沒填代表什麼」
第 3 條:用網址直接開單筆公告完全不檢查 讀取時照那個值過濾就好,清單與單筆用同一套規則,不會再有一條路鬆一條路緊
第 1 條旁邊的附帶問題:改公告時沒帶發送對象,會靜默清空,那則公告就變成所有人看得到 清空變成不合法狀態、會被擋下,不再等於「全部看得到」

所以這兩條沒有各自排工——它們跟第 1 條的附帶問題是同一件事的三個面,已在同一次改動(CM-2026)裡一起解掉:公告新增「發送範圍」欄位(全公司/指定部門),清單與單筆照同一套規則過濾,既有公告一律標為全公司。

同一個模糊狀態,兩個人做了相反的假設

「帳號沒有被分配部門」這一個狀態,在產品的兩個地方造成完全相反的結果:

哪一塊 沒有部門時會怎樣
公告(第 4 條) 全部看得到——過濾整個被跳過
意見回饋 什麼都送不出去——被資料庫規則擋住

🔴 共同根因:帳號可以沒有部門,而全系統沒有規定這種帳號該看到/做到什麼。一邊的作者假設「沒部門=不限制」,另一邊假設「沒部門=沒資格」——兩個假設相反,各自寫在自己那一層,沒有人對齊。

這比單一漏洞更值得記住:不是某個人寫錯,是規則從來沒定過,於是每個碰到它的人各自替它補了一個答案。

已裁定兩邊分開處理:公告這邊,發送對象改成必選、沒選就不能發布;意見回饋那邊維持原裁定——送意見回饋本來就跟部門無關,那條規則從一開始就不該要求部門,所以放寬它。

第 5 條:公告與部門的關聯表沒有客戶隔離(低,裁定不修)

問題:公告主表本身的客戶隔離是齊全的(四條規則都在),但「這則公告發給哪些部門」那張關聯表完全沒有。

裁定:那張關聯表不補客戶隔離,靠公告主表擋。

理由一:那張表只存兩個編號(哪則公告、哪個部門)、沒有任何內容,而且要查它得先有公告編號——公告主表擋得住。

理由二(更重要):多做一層會變成兩套規則各自維護,反而更糟。兩套一旦漂移(一邊改了、另一邊沒跟上),從外面完全看不出來——而那正是這一輪查出「11 張表的門禁規則方向寫反」的成因。

順帶一提,這也是為什麼「照抄主表」行不通:主表是靠「這筆屬於哪個客戶」那個欄位判斷的,而關聯表根本沒有那個欄位,照抄會直接建不起來。

要留一句提醒給下一個人

「靠主表擋」成立的前提是——沒有任何入口能直接查那張關聯表。

今天成立,但那是約定、不是機制:哪天有人加了一個直接查那張表的入口,這個前提就悄悄消失了,而且不會有任何警告。

建議把這個前提寫成一句註解留在程式旁邊,讓下一個要新增入口的人知道。這是給下一個人的提醒,不是待修項目。


§5

一件值得寫進報告的判斷(密碼外洩要怎麼評嚴重度)

第 6 條(簽發登入憑證的金鑰外流)原本被評為中風險,統籌者驗收時一度主張升級為高風險——理由是:實測證明那把金鑰跟現行環境設定完全一致、還在用,而且散在 34 個地方、已經推上主線。

決策者只問了一句話就推翻了這個判級:

「這個值在安裝時是不是動態產生的?如果客戶那邊每套都不一樣,那就不用急。」

答案——是。安裝程式每裝一套,都會各自隨機產生一把新的。

所以外流的只是我們自己開發機那一把,不會跟著出貨。 影響範圍從「每一套裝出去的都中」縮成「我們自己的開發環境」。

教訓:判密碼外洩的嚴重度,第一個要問的不是「這把還活著嗎」,也不是「散在幾個檔案」

要問的是「客戶端用的是不是同一把」。

前兩個問題都會讓人系統性地高估嚴重度。往後遇到任何寫死或外流的憑證,第一步先查安裝程式有沒有隨機產生。

這一條後來也套用在第 7 條上——那一條沒有隨機產生(每套安裝都種同一組),所以維持中風險、需要處理。


§6

這塊的結論

一句話:這塊本身寫得很規矩(模組本體工具零發現,這一輪查的那 29 個檔案裡沒有找到成立的資安問題),問題全部在主系統那一端——「誰能看到哪一則」的判斷上。五條發現,同一種病:知道你是誰,但不問「這一則是你的嗎」。

現況:

  1. 第 1、3、4 條已在同一次改動修好(CM-2026)——公告有了明確的「發送範圍」(全公司或指定部門),清單與單筆照同一套規則過濾;改與刪先確認是不是自己發的。它不是補三個洞,是把「系統分不出你刻意選全公司還是忘了選」這個模糊地帶整個拿掉。
  2. 第 2 條已與 AI 儀表板那塊一起修好(CM-2038)——儀表板呼叫公告查詢前先檢查權限。
  3. 第 5 條裁定不修,靠主表擋;前提是沒有任何入口直接查那張關聯表,這一點要讓下一個新增入口的人知道。
  4. 順手撈到的六條外流憑證:五條已撤銷換發、版控殘留已清;第 7 條(安裝程式每套都種同一組管理員密碼)已裁記錄不修,待安裝期憑證策略一併處理。
統計
檢視輪數 2 輪、62 個檔案
本頁列出的問題 11 條——公告這塊 5 條、順手做密碼掃描在整個程式庫撈到的 6 條(與公告無關)。
另有 3 條是人工另外查出來的,都是目前的程式走不到的路徑,所以沒有列進表裡
最嚴重 0 條(最高為中風險)
已修 9 條——公告這塊第 1~4 條;順手撈到那六條裡的第 6、8、9、10、11 條(撤銷換發、版控殘留已清)
裁定不修 2 條——第 5 條(關聯表不補客戶隔離,靠主表擋)、第 7 條(安裝程式預設管理員密碼,已裁記錄不修)
未修 0 條

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