---
title: 公告
eyebrow: Guidant AI 資安檢視 · 模組報告
h1: 公告（jedi-bulletin）
lede: 發布內部訊息給指定部門看的功能。**這塊被低估了**——原本按規模排在風險最低那一組，實際查下來，「誰能看到哪一則公告」的判斷繞得出乎意料，而且改刪公告從來不問「這則是不是你發的」。真正的病根不是某一段程式寫錯，而是**系統分不出「你刻意要發給全公司」跟「你忘了選對象」**。
---

## 這塊在產品裡做什麼

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

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

::: grid2
::: {.card .plain}
#### 它平常在做什麼
1. 有權限的人發一則公告
2. 指定要發給哪幾個部門
3. 其他人在畫面上看到屬於自己部門的那些
:::
::: {.card .warn}
#### 為什麼它被低估了
按檔案數量排，這塊被歸在「只是讀取展示、攻擊面小」那一組。

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

---

## 檢視軌跡

**檢視期間**：2026-09-08 ～ 2026-09-09｜**範圍**：62 個檔案（模組本體 29、主系統接線 33）｜**共 2 輪**

> **原始技術報告**放在需求中心的 [FR-079 站](https://guidantai-feature-doc.jedicotech.com/FR-079-2609-bulletin-security-scan/)，檔名見下表最後一欄。那裡有每一輪的完整技術細節、檔案清單與逐條推理，供需要深究或稽核抽查時查閱。

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

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

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

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

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

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

::: {.callout .warn}
**兩件沒查、不能當作乾淨**

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

---

## 問題一覽

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

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

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

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

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

::: {.callout .ok}
**這六條的現況一句話**

**外流的通行證、金鑰、密碼都已經撤銷或換發完畢**，剩下的只有把字串本身從版控裡清乾淨——**性質是打掃，不是堵漏**。唯一例外是第 7 條：那不是「某一把外流了」，是安裝程式每套都種同一組，換發解決不了它，已另有裁定。

這六條與[寄信通知那塊](M16-notification.html)列到的四條有重疊（同一批檔案、同一組密碼），**問題總表請只算一次**。
:::

---

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

## 公告這塊的五條，逐條說明

### 第 1 條：改刪公告不檢查歸屬（中）

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

**兩個加重的細節**：

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

**修法**：改與刪之前先確認「這則公告是不是自己發的」。

### 第 2 條：AI 儀表板這條路繞過守門讀公告（中）

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

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

::: {.callout .warn}
**這條路現在會當場出錯——但這不是被修好了**

這三件事要一起看，缺一件就會誤判：

1. **問題本身一點都沒有變。** 守門缺失完全沒有改。
2. **它現在打不通，是被一個不相干的改動意外擋住的，不是有人去修它。** 公告功能搬家之後，**儀表板傳過去的東西跟它現在認得的格式對不上，一叫就出錯**。錯誤被上層接住，使用者看到的是「生成失敗」。
3. 🔴 **哪天有人把那個小錯修掉，而沒有同步補上守門，這條路立刻恢復暢通。** 而修那個小錯看起來會完全像是在做正確的事——修一個明顯的錯誤，沒有人會意識到自己同時打開了一條門。

這與 [AI 儀表板那塊](M15-ai-dashboard.html)的「查使用者名冊那一支現在打不通」是**同一類情況**（問題沒變、只是被意外擋住），但成因不同。
:::

**修法**：這條與 [AI 儀表板那塊](M15-ai-dashboard.html)是同一件事——那塊的問題是「27 支查詢一支都沒檢查權限」，公告就是其中一支。那塊已裁定「在儀表板呼叫查詢之前補一道權限檢查」，**會一併涵蓋這條，不要分開修**。

### 第 3、4 條：一套設計解掉三件事（低）

**先看這兩條現在各自是什麼問題**：

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

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

::: {.callout .decided}
**裁定：公告的發送對象改成必選——「全公司」或「指定部門」，兩者擇一，都沒選就不能發布。**

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

**這一個設計，一次解掉三件事**：

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

**所以這兩條不要各自排工**——它們跟第 1 條的附帶問題是同一件事的三個面，一起改一次就好。

::: {.callout .warn}
**同一個模糊狀態，兩個人做了相反的假設**

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

| 哪一塊 | 沒有部門時會怎樣 |
|---|---|
| **公告**（第 4 條） | **全部看得到**——過濾整個被跳過 |
| **[意見回饋](M21-issue-rescan.html)** | **什麼都送不出去**——被資料庫規則擋住 |

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

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

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

### 第 5 條：公告與部門的關聯表沒有客戶隔離（低）

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

::: {.callout .decided}
**裁定：那張關聯表不補客戶隔離，靠公告主表擋。**

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

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

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

::: {.callout .warn}
**要留一句提醒給下一個人**

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

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

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

---

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

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

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

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

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

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

::: {.callout .decided}
**教訓：判密碼外洩的嚴重度，第一個要問的不是「這把還活著嗎」，也不是「散在幾個檔案」**

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

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

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

---

## 這塊的結論

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

**建議，照這個順序**：

1. **第 3、4 條加上第 1 條的「靜默清空」，用同一套設計一次解掉**——發送對象改成必選，「全公司」或「指定部門」擇一，都沒選就不能發布。這是這一頁最值得做的一件事：**它不是補三個洞，是把「系統分不出你刻意選全公司還是忘了選」這個模糊地帶整個拿掉**。
2. **第 1 條的歸屬檢查**（改與刪之前先確認這則公告是不是自己發的）——這是這塊唯一一條「現在就能被實際利用」的，和上面那套設計同一支程式，併成同一次改動。
3. **第 2 條必須與 [AI 儀表板那塊](M15-ai-dashboard.html)一起修**——那是同一件事的兩面，分開修會各修一半。而且要注意：那條路現在會當場出錯，**修那個小錯的人如果沒有同步補上守門，洞立刻恢復**。
4. **第 5 條已裁定不補**，但要在程式旁留一句提醒：靠主表擋的前提是沒有任何入口直接查那張關聯表。

**這塊的五條，與產品裡其他模組的「只驗登入、不檢查歸屬」是同一大類**——那是這整輪檢視裡數量最多、也最該優先處理的一組。建議不要單獨排這塊，**併進那一組一起規劃**。
:::

| 統計 | |
|---|---|
| 檢視輪數 | 2 輪、62 個檔案 |
| 本頁列出的問題 | **11 條**——公告這塊 5 條、順手做密碼掃描在整個程式庫撈到的 6 條（與公告無關）。<br>另有 3 條是人工另外查出來的，**都是目前的程式走不到的路徑**，所以沒有列進表裡 |
| 最嚴重 | 0 條（最高為中風險） |
| 已修正 | **5 條**——順手撈到那六條裡外流的金鑰與密碼全部撤銷換發完畢，**剩版控殘留字串待清**（工單 CM-1607／1608／1629） |
| 還要處理 | **公告這塊 5 條**（決策者裁定先不開工單，發現已登記進總表，待全部模組檢視完後統一分類開卡；其中第 5 條已裁定不補、只留一句提醒）<br>**加上安裝程式預設管理員密碼 1 條**（已裁定維持記錄、暫不調整）<br>**加上版控殘留字串清理** |

---

| 你想知道 | 看哪裡 |
|---|---|
| 我們用什麼方法查、結果有多可信 | [檢視方法與工具](GUIDE-01-method-and-tools.html) |
| 其他模組的檢視結果 | [回總報告首頁](index.html) |
