---
title: 意見回饋
eyebrow: Guidant AI 資安檢視 · 模組報告
h1: 意見回饋（jedi-issue）
lede: 使用者按「意見回饋」回報問題，系統可以把它同時開成 GitLab／GitHub 上的一張真問題單。**七個操作裡只有「匯出」做了權限檢查**，另外六個任何登入帳號都能用。這一塊另外撞出全案最廣的一次密碼外流：資料庫密碼散在 249 個檔案裡。
---

::: {.callout .warn}
**先說明這一頁的時間點**

這一頁講的是**第一次檢視（2026-09-09～10）的結果**。之後這塊功能整組搬過家（原本一半在主程式、一半在模組裡，後來全部搬進模組），**有變動的部分另外重查過一次**——那次重查的結果在[〈意見回饋重新檢視〉](M21-issue-rescan.html)。

**兩頁要一起看**：這一頁是「當時查到什麼」，那一頁回答「搬家之後還在不在」（答案是：**都還在**）。
:::

## 這塊在產品裡做什麼

使用者在系統裡遇到問題，可以按「意見回饋」回報：寫標題、寫描述、貼附件、選分類標籤。

回報進來之後，管理員可以看清單、改內容、刪掉、匯出成 Excel 或 CSV。**另外還有一條路**——系統可以拿公司的通行證去 GitLab 或 GitHub **開一張真正的問題單**，把使用者填的字與上傳的檔案一起送過去；回饋被刪掉時，那張外部問題單也會跟著被關掉。

---

## 檢視軌跡

**檢視期間**：2026-09-09 ～ 2026-09-10，另 2026-09-23 補一輪｜**範圍**：158 個檔案（模組本體 127、接線 31）；另加主系統接線一輪（與設備清冊並排查，共 8 個檔）｜**共 7 輪**

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

| 輪次 | 查什麼 | 檔數 | 覆核投票 | 結果 | 原始報告檔名 |
|---|---|---:|---|---|---|
| **I1** | 怎麼連去外部問題單系統、通行證怎麼帶 | 25 | 9 票全投完 | 找到 2 條 | `scan-I1-third-party-integration` |
| **I2** | 我們主系統這端怎麼把它接上來 | 31 | **30 票全投完** | 找到 5 條，**含這一塊唯一的高風險** | `scan-I2-host-wiring` |
| **I3** | 附件的上傳與下載 | 14 | 9 票全投完 | 工具零發現＋**人工另查出 1 條** | `scan-I3-attachments` |
| **I4** | 對外介面與模組的掛載契約 | 20 | 12 票全投完 | 工具零發現（兩項查證由統籌者補做） | `scan-I4-plugin-contract` |
| **I5** | 業務邏輯層 | 40 | 15 票全投完 | 找到 1 條（連內部套件庫走沒加密的連線） | `scan-I5-app-domain` |
| **I6** | 資料怎麼存、怎麼取 | 28 | 9 票全投完 | 工具零發現＋**統籌者查出多張資料表完全沒有客戶隔離** | `scan-I6-persistence` |
| **W6**（主系統接線，與另一塊並排查） | 我們主系統這一端怎麼把設備清冊與意見回饋兩塊接上來 | 8 | 6 票全投完 | **沒有找到新問題**——自動檢視報的兩條都是已登記的舊帳；執行者另查出一件「修好的模組還沒發版」（不是新問題，已記在修正排程裡）。意見回饋這一半的接線範圍內，這一輪沒有新發現 | `scan-W6`（在 [FR-115 站](https://guidantai-feature-doc.jedicotech.com/FR-115-2609-host-wiring-security-scan/)） |

::: {.callout .ok}
**六輪覆核全部完整跑完、零漏投、零中斷——這是全案第一次**

三位覆核員一共投了 84 票，沒有一條漏投、沒有一輪中途被截斷。**這一批是目前唯一做到「每條發現都投完票」的。**

（要注意的是：**完整不等於乾淨**。有三輪工具零產出，真正的發現是人工查出來的，見下。）
:::

::: {.callout .warn}
**開工前就先抓到一個錯誤的前提，值得記一筆**

模組的說明文字白紙黑字寫著「GitLab／GitHub 整合目前產品沒有在用，大約佔這個模組的 58%」。

**統籌者從主系統那一端查證，這句話是錯的**——主系統裡有六處實際在呼叫那些功能，由兩個開關控制。

**為什麼這件事要緊**：如果照那句話跳過那 58%，跳過的恰好是**風險最高的那一半**（對外連線＋通行證）。**說明文字會騙人，要看程式實際怎麼被呼叫。**
:::

---

## 問題一覽

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

| # | 問題 | 風險 | 分類 | 出事會怎樣（誰受影響） | 怎麼修 | 狀態 |
|---|---|---|---|---|---|---|
| **1** | **資料庫管理員密碼散在 249 個檔案裡，其中一份把主機、帳號、密碼三行湊成一條可直接使用的連線指令** | 🟠 高 | 密碼外流 | **拿得到程式碼的人（含離職者、外包）可以直接連進資料庫**。這個帳號**可以繞過所有客戶隔離**，每一家客戶的資料可讀可寫。而且**同一組密碼同時通行四處**：開發環境、**出貨基線資料庫**（客戶拿到的安裝檔就是從這座資料庫長出來的），以及**兩座標示「已退役」但機器上還活著、拿這組密碼照樣連得進去的舊資料庫**——等於兩個沒人在看的入口。這組密碼同時也是快取服務的密碼 | 三步、順序不能顛倒：先堵住再寫進去的路 → 清掉已經寫進去的 → 換密碼（換密碼要決策者明確指示） | ✅ **已修（CM-2049）** |
| **2** | **七個意見回饋操作裡只有「匯出」檢查權限**，其餘六個任何登入帳號都能用；改別人的、刪別人的也從不檢查是不是自己的。整個模組對外九支入口，**八支只驗登入** | 🟡 中 | 只驗登入、不檢查歸屬 | **這是目前所有問題裡唯一一條「一般員工現在就能實際操作利用」的**，不需要任何特殊權限。任何員工可以刪掉或竄改別人的回饋，**連帶把外部問題單系統上的那張單子也一起關掉**；而稽核紀錄還會把操作人記成合法本人 | 後端補上權限檢查（十顆權限資料庫裡都已定好，純接線）、再加上「這筆資料是不是你的」的檢查；清單分兩套邏輯（已裁定，見下）——**已修：六個操作全部補上權限檢查（讀取那兩個收「管理」或「唯讀檢視」任一顆，唯讀檢視的角色才不會被擋在門外）；改、刪、刪附件三個在動手前比對「這筆是不是你送的」，不是本人一律擋下，而刪除的比對放在關閉外部問題單之前——非本人時一次外部呼叫都不會送出。清單分兩套的做法照決策者裁定：不開第二支入口，改在現有清單入口加一個「查看範圍」參數（我的／全部），「全部」需要「唯讀檢視」那顆權限，不帶參數時依呼叫者的權限推定（行為與修改前一致，只有原本不該看到全部的人被收窄）。實查開發環境 14 個角色五顆權限全部都有，今天不會有任何人被擋** | ✅ **已修（FR-114.1-7）**（工單 CM-1630） |
| **3** | **Google 雲端硬碟的應用程式密鑰與加密金鑰外流**（散在 15 個檔案裡） | 🟡 中 | 密碼外流 | 這組密鑰**不是每套安裝各自產生的，而是 Google 後台唯一一組、所有客戶環境共用**——一旦外流，影響範圍比其他金鑰外流都大。配合第 1 條拿到資料庫之後，可以**解密所有客戶存的雲端硬碟通行證、讀取客戶檔案** | 到 Google 後台重設應用程式密鑰；加密金鑰要**每個環境一把、不要共用**，而且換之前要先寫好「把既有通行證重新加密」的程式 | ✅ **已修（CM-2051）** |
| **4** | **連線到 GitHub 時把憑證驗證關掉**（兩處） | 🟡 中 | 密碼外流 | 帶著公司的 GitHub 通行證走一條**不確認對方是不是真的 GitHub** 的連線——網路上的中間人可以假冒 GitHub，**把公司的通行證攔走**。這個模組的通行證**外流過一次**（已處置），現在同一個模組還在用這種方式傳它 | 把那兩處的關閉開關拿掉。實際做法：拿掉兩處 Github client 的 verify=False，恢復預設憑證驗證 | ✅ **已修（CM-2066）** |
| **5** | **連公司內部套件倉庫走的是沒有加密的連線**，而且這是安裝套件的主要來源（28 支程式庫全部一樣） | 🟡 中 | 密碼外流 | **內部網路裡有心人可以在安裝套件的當下偷偷掉包內容**，等於在打包機上執行他的程式碼——**而打包機產出的就是客戶實際拿到的安裝檔**。這是整條供應鏈最根本的一個破口 | 內部套件倉庫改走加密連線；順便把鎖定套件版本的那份檔案納入版本控制（第二道防線目前也沒跟著出貨） | ⬜ **未修**（工單 CM-1634） |
| **6** | **打包前端映像檔的那支腳本裡直接寫著一組帳號密碼，而那支腳本本身就進了版本控制**（跨兩個程式碼倉庫、至少四個檔） | 🟡 中 | 密碼外流 | **與第 1 條同一個形狀——憑證寫進版本控制**，任何拿得到程式碼的人（含離職者、外包）都拿得到它，刪掉檔案也沒用。拿到的是**內部套件倉庫的帳號**，而那個倉庫是所有套件的來源——**等於拿到往打包流程裡塞東西的位置**，而打包出來的就是客戶安裝的檔案 | 把帳密移出版本控制，改從環境變數或設定檔讀。⚠️ **要先去後台確認那個帳號到底是不是唯讀**——腳本裡註解說它是唯讀所以「非機密」，但帳號名是管理員層級，這句話要實際查過才算數；若不是唯讀，嚴重度要上調並且要換密碼 | 📋 **已裁記錄不修（1.21.0）**（工單 CM-1998） |
| **7** | 刪除附件時的安全檢查只做了一半（算出了該過濾掉哪些，卻沒有真的用那個結果） | ⚪ 低 | 外部送什麼就收什麼 | **目前打不到**——現在的操作流程一次只會傳一個檔案編號。但**未來如果做批次刪除功能，會在使用者不知情的情況下刪錯檔案，而且不會報錯** | 把算出來的過濾結果真的拿去用（一行）。實際做法：delete_attachment 刪檔案時改用已算好的 matched_file_uids（過濾後結果） | ✅ **已修（CM-2066）** |

### 這塊另外查到的資料表隔離狀況

這條**不是工具查到的，是統籌者補查出來的**，屬於設計層級的註記而非立即可利用的漏洞。**原本登記為「六張表完全沒有客戶隔離」，逐支入口核對後收斂成下面這個樣子。**

這塊的資料表在資料庫層確實都沒有開客戶隔離、也沒有「這是哪家客戶的」標記欄位。**但「沒有隔離」不等於「會外流」**——判準只有一個：**有沒有一支入口可以直接查這張表？** 有就必須處理；沒有、只能從已經受隔離的主檔連過來，那就靠主檔那道規則就好，在下游再做一次反而變成兩套真相、日後兩邊會各自漂移。

照這個判準，原本登記的那批表分成三類：

| 類別 | 哪幾張 | 實際狀況 |
|---|---|---|
| **A 有直接入口｜唯一要處理的** | **成員名冊**（1 張） | 那支入口只要登入就打得到，底層是**整張表倒出來**——沒有任何條件、也沒有參數可以篩，內容含電子郵件。（查證後確認：現在沒有任何地方在呼叫它、三個環境的表也都是 0 筆，**所以它是一塊該刪的死程式**，裁定整塊移除） |
| **B 靠主檔擋｜不用重複做** | 問題單、標籤對應、指派對應、附件對應（4 張） | 四條意見回饋入口**全部先查受隔離保護的回饋主檔**，拿到本客戶的回饋之後才去取對應內容，**沒有一條能讓外面直接指定要哪一筆**。（指派對應那張另註：往它寫資料的功能根本沒有對外入口，實質是死功能。） |
| **C 不是缺口** | 標籤字典（1 張） | 內容是系統預設的回饋類型與功能選單，由安裝腳本建立，**沒有任何入口能讓客戶自己新增標籤**——它不是客戶資料，全部回傳本來就是正確行為 |

🔴 **所以六張裡只有成員名冊那一張需要處理，而處理方式已經裁定是「連同那支入口整塊移除」**——不是「待補隔離」。詳見[〈意見回饋重新檢視〉](M21-issue-rescan.html)第 2 條。

**六張改成五張的更正**（標籤字典那張本來就不是缺口）也記在那一頁。

**本報告撰寫時連開發環境唯讀實查**：這幾張表的隔離開關確實是關的、規則 0 條、也都沒有客戶歸屬欄位；對照之下，後來新建的那張回饋表有四條隔離規則、也有客戶欄位，**差異非常明顯**。

**而且新表沒踩到「方向寫反」那個坑**——它用的是有做路徑前綴比對的正規寫法，而且明確處理了「沒設定就一律拒絕」這種邊界情形。弱點檢測那塊被查出方向寫反的，是另一種「把路徑切成一段一段、再比編號大小」的寫法，兩者不同源。

**順帶一個觀察**（不影響結論）：程式在組意見回饋清單時，是**先把整張問題單表讀進記憶體、再用本客戶的回饋清單過濾**。結果沒有外流，**但這個寫法很脆**——哪天有人為了效能改成「直接把讀進來的這批回出去」，防線就破了。

---

## 第 1 條：一組密碼，249 個檔案 {#db-password}

::: {.callout .crit}
**這是全案散布範圍最廣的一次密碼外流。**

而且外流的那一組**可以繞過所有客戶隔離**——拿到它，每一家客戶的資料都可讀可寫。
:::

### 問題是什麼

一組資料庫管理員密碼（同時也是快取服務的密碼），**被寫進了 249 個進版本控制的檔案裡**——大多是開發過程留下的對話紀錄、維運腳本、遷移計畫。

其中最麻煩的一份，把**主機位址、帳號、密碼三行湊在一起**，等於一條可以直接複製貼上使用的連線指令。

**這組密碼現在通行四個地方**：開發環境、**出貨基線資料庫**，以及**兩座標示「已退役」、但機器上其實還活著、拿這組密碼照樣連得進去的舊資料庫**。展示機已經在 2026-08-29 改成安裝版之後獨立出去，不再是同一組——**但這件事沒有讓嚴重程度變低**：

- **出貨基線資料庫比展示機更要緊**——客戶拿到的安裝檔，裡面那套資料庫結構與預設資料就是從這座資料庫長出來的。動得了它，等於動得了往後每一家客戶裝出來的系統。
- **那兩座舊資料庫是沒人在看的入口**。它們被當成退役的，所以不會有人注意到有誰連進去過。

**本報告撰寫時重新數了一次，仍然是 249 個檔案。**

### 為什麼這條評為高風險

| 判斷依據 | 這件事 |
|---|---|
| 這組帳號能做什麼 | **繞過所有客戶隔離**，每一家客戶的資料可讀可寫 |
| 有幾個地方共用 | **四處**——開發環境、出貨基線資料庫（安裝檔的來源），加上兩座「已退役但還連得進去」的舊資料庫 |
| 誰拿得到 | **任何接觸過程式碼的人**，包含離職者與外包 |
| 刪掉檔案有用嗎 | **沒有**，已經留在版本紀錄裡 |

### 怎麼修

**三步，順序不能顛倒。**

| 步驟 | 做什麼 | 為什麼不能省 |
|---|---|---|
| **一、先堵住再寫進去的路** | 改用作業系統原生的密碼檔存密碼，指令裡就不必打密碼；對話紀錄歸檔前先自動遮蓋；評估在提交前加自動掃描 | **不做這一步，清乾淨了還會再長出來** |
| **二、清掉已經寫進去的** | 249 個檔裡的密碼字串換成「請查設定檔」。那份有完整連線指令的優先處理 | 網頁版不用手改——它們是從文件產生的，改完文件重跑產生指令即可 |
| **三、換密碼** ⚠️ | 換的時候要同步：設定檔、機器上的環境變數、快取服務設定、所有連線腳本。**建議把快取服務與資料庫的密碼分開**（目前是同一組）。**順便把那兩座已退役的舊資料庫關掉或收回**——留著只是多兩個沒人看的入口 | ⚠️ **這一步要決策者明確指示**——要動的是出貨基線資料庫與那兩座舊資料庫，屬環境異動 |

**不重寫版本紀錄**（決策者已裁定）：重寫會改動所有提交編號、影響每個下載過程式碼的人，**而對已經散出去的內容毫無補救效果**。

---

## 第 2 條：七個操作只守住一個 {#missing-guard}

### 問題是什麼

意見回饋有七個操作。**只有「匯出」掛了權限檢查。**

| 操作 | 誰能做 |
|---|---|
| 新增回饋 | 任何登入帳號 |
| 看清單 | 任何登入帳號 |
| 看單筆明細 | 任何登入帳號 |
| **改別人的回饋** | 任何登入帳號，**而且不檢查那筆是不是你的** |
| **刪別人的回饋** | 任何登入帳號，**而且不檢查那筆是不是你的** |
| **刪除附件** | 任何登入帳號 |
| 匯出 | ✅ 有檢查權限 |

**再往外看一層，整塊還更寬**：這個模組對外一共開了**九支入口**，其中**八支只驗登入**——除了上面那七個操作，另外兩支是：

- **全站使用者名冊**：登入就能撈，回來的是所有客戶的人（含電子郵件）。**這一支就是〈意見回饋重新檢視〉第 2 條講的同一件事**，兩頁指的是同一條入口；那一頁已裁定把它連同背後的成員表一起移除。
- **標籤下拉清單**：登入就能取，內容是系統預設的回饋分類選項（不是客戶資料，見上面資料表那節的 C 類）。

**這兩支在原本的報告裡完全沒提到，這次補上。**

**本報告撰寫時開檔核對這些功能與背後的業務邏輯，改與刪那兩支從頭到尾沒有任何「這筆是不是你的」比對。**

### 為什麼這條要緊

::: {.callout .warn}
**這是目前所有問題裡唯一一條「一般員工現在就能實際操作利用」的**——不需要任何特殊權限，不需要任何技術手段，**在正常的操作介面上就做得到**。
:::

而且它的影響會**跨出我們的系統**：刪掉一筆回饋時，系統會順手把外部問題單系統上的那張單子也關掉。**等於任何員工都能關掉開發團隊正在處理的問題單。**

更麻煩的是**稽核紀錄會把操作人記成合法本人**——事後追查時看不出異常。

### 怎麼修

**兩層都要補，缺一不可。**

| 層次 | 做什麼 | 注意 |
|---|---|---|
| **一、權限檢查** | 用現有的統一守門機制，**不要另外寫一套** | **這一條不卡任何決策**：十顆權限項目在資料庫裡全部已經定義好、也已經綁在前端選單上，缺的純粹是後端那一段接線 |
| **二、歸屬檢查** | 改或刪之前先確認那筆是不是自己的（或有沒有管理權限） | 要放在業務邏輯那一層，因為要先把資料撈出來才知道要判誰 |

::: {.callout .decided}
**裁定一：後端要補上權限檢查（這是改裁，過程值得記一筆）**

現況是：這塊宣告了**十顆權限**，後端真正在擋的只有**五顆**（整合設定四顆＋匯出一顆），**另外五顆（新增／讀取／修改／刪除／管理頁）只有前端選單在認**——選單藏起來了，但把網址直接貼到瀏覽器一樣進得去。

**決策者一度裁「不補」**，理由是怕重演設備清冊那塊的狀況：那邊補了檢查之後，使用者看到的是一個空的下拉選單，不知道發生什麼事。

**統籌者回頭查證，發現兩塊的形狀根本不同，那個理由在這裡不成立，決策者據此改裁「要補」。** 三項查證全部是綠燈：

| 查什麼 | 結果 |
|---|---|
| 這些入口有沒有被別的頁面借去當資料來源？ | **沒有**——只有意見回饋自己那兩支頁面在呼叫。**設備清冊那塊是「一個讀取入口被四個不相干的頁面當資料來源」，形狀完全不同** |
| 補下去會不會擋掉現在的人？ | **不會**——開發環境 14 個角色、41 個使用者，**14 個角色五顆權限全部都有**，零缺漏 |
| 新客戶裝起來會不會踩到？ | **不會**——出貨的初始資料只建一個管理員角色，**五顆全給**，兩條選單路由也都掛好了 |

**實際效果**：把前端本來就在認的那套規則，在後端也認一次。**今天不會有任何人被擋；等到哪天管理員真的去取消某個角色的勾選，那一格才會生效**——而不是像現在，選單藏了、網址貼上去照樣能用。
:::

::: {.callout .decided}
**裁定二：清單要分成兩套邏輯——但要修的是兩件事，不是一件**

**裁定結果**：
- **「我的意見回饋」那一頁**：只回自己建立的回饋。
- **管理頁**：回整個客戶的（但要真的檢查呼叫的人有沒有管理權限）。

🔴 **報告原本把這條寫成「補權限檢查」，漏掉了前面那件更基本的事**：

**後端目前分不出這兩頁是誰在呼叫。** 前端確實有兩個路由、選單也各自綁了一顆權限，**但兩頁用的是同一個畫面元件、打的是同一支功能入口**——後端只看到「有人要清單」，就把整個客戶的回饋全部回傳。

所以順序是：

| 步驟 | 做什麼 | 為什麼不能省 |
|---|---|---|
| **一** | 先讓後端能分辨這兩種呼叫 | **沒有這一步，第二步做不到**——後端根本不知道該套哪一套規則 |
| **二** | 兩邊各自套不同規則 | 自己那頁只回自己的；管理頁回整個客戶的，但先確認有管理權限 |

**連帶一件**：自己那頁只看得到自己的回饋，改與刪自然也只改得到自己的。**但後端仍然要獨立檢查一次**——「看不到所以改不到」是前端在擋，把網址直接貼上去就繞過去了。
:::

---

## 第 3～7 條：其餘五條

### 第 3 條：Google 雲端硬碟密鑰外流（中）

**問題**：應用程式密鑰、加密金鑰、應用程式編號，以及一把對外介接用的金鑰，**一共散在 15 個檔案裡，全部集中在開發過程留下的對話紀錄裡、也全部進了版本控制**。

⚠️ **這個數字修正過**：原始報告寫 22，那是把分開記的「密鑰 12 檔」與「加密金鑰 10 檔」直接相加；**但兩批檔案本來就有重疊**，取聯集只有 13 檔，再把應用程式編號與那把對外介接金鑰算進去才是 15。

**這組不是每套安裝各自產生的，而是 Google 後台唯一一組、所有客戶環境共用**——這是它比其他金鑰外流更嚴重的原因。**新的佐證**：比對展示機上那把應用程式密鑰，與開發環境**完全相同**（只比對了雜湊值，沒有取出明文）。

**修法**：⚠️ **重設金鑰要決策者明確指示。** 應用程式密鑰到 Google 後台重設，重設後檢查後台的授權紀錄有沒有異常活動；加密金鑰要**每個環境一把、不要共用**，而且**換之前要先寫好「把既有通行證重新加密」的程式**，否則客戶既有的雲端硬碟整合會全部失效。清理工作可以先做，不用等重設。

### 第 4 條：連 GitHub 時關掉憑證驗證（中）

**問題**：兩處程式碼在連線到 GitHub 時，**關掉了「確認對方是不是真的 GitHub」這道檢查**。帶過去的是公司的通行證。

**本報告撰寫時開檔核對，兩處都還在。**

**前科**：這個模組的通行證**外流過一次**（已處置）。現在同一個模組還在用這種方式傳它。

**原始報告寫「這是同一種問題第四次出現」（另外三次是員工帳號目錄、快取服務、寄信），這次逐條重查，結論要改**：

| 原本列的 | 重查實況 |
|---|---|
| **GitHub 兩處** | ✅ **仍然成立，一行未改**（GitLab 那一半沒有這個問題） |
| **員工帳號目錄** | ➖ **不再計入**。已經改成客戶自己可以選的設定項，而且**預設是有驗的**；真的關掉時程式會寫一行警告說這只建議用在測試環境。性質從「寫死不驗」變成「可設定」 |
| **快取服務** | ⚠️ **一邊修了、一邊沒修**。共用模組那邊已經改成「只要開了加密就一定要驗、不給退回」，**但主專案自己那支仍然寫著不驗**——而且主專案有兩個地方在用它 |
| **寄信** | ❌ **這條不成立**。寄信程式用的那套語言內建元件，**加密連線預設本來就不驗對方身分**——那是語言層自己的行為，不是我們這支程式去把它關掉的。真要計入的話，說法得改成「語言預設不驗、我們沒有額外加強」——與「程式寫死關掉」不是同一件事 |

🔴 **所以目前真正「程式寫死不驗」的是三處：GitHub 兩處，加上主專案的快取服務一處。** 那個「共用模組修了、主專案自己那支漏掉」的形狀特別值得記——**同一件事修一半，剩下那一半沒人再回頭看。**

### 第 5 條：內部套件倉庫走沒加密的連線（中）

**問題**：連線到公司內部套件倉庫走的是沒有加密的連線，**而且這是安裝套件的主要來源**——**28 支程式庫全部一樣**（現役套件 21 支、已封存 6 支，加上主專案本身 1 支）。原始報告寫 26，那是 2026-09-09 當時的數量。

**本報告撰寫時核對，主專案與模組的設定檔都還是這樣，一個都沒有改。**

**為什麼這是供應鏈最根本的破口**：內部網路裡有心人可以在安裝套件的當下偷偷掉包內容，等於在打包機上執行他的程式碼——**而打包機產出的就是客戶實際拿到的安裝檔**。

**同一條後來在另外兩塊獨立撞到過**（共用地基那塊、稽核流程引擎那塊），**證實這不是單一程式庫的疏漏，是全部程式庫都一樣**。

🔴 **這次另外查到一件事，已經獨立成下面的第 6 條**：打包前端映像檔的那支腳本裡，除了明文網址之外還直接寫著一組帳號密碼。**那是另一件事、修法也不一樣**，所以不併在這一條裡（原因見第 6 條）。

**連帶一件：鎖定套件版本的那份檔案**（用途是「用雜湊比對確認套件沒被掉包」這第二道防線）——**原始報告寫「被排除在版本控制外」，這次查證是一半對**：

- **共用模組那邊仍然沒有**：27 支套件裡有 17 支本機產生了這個檔，**但一支都沒有進版本控制**，全被忽略規則擋掉了。
- **主專案已經補上了**，自 2026-08-15 起入版本控制。

### 第 6 條：打包腳本裡寫著帳號密碼，而腳本進了版本控制（中）

**問題**：我們打包前端程式、做出客戶要安裝的映像檔時會跑一支腳本。那支腳本為了去公司內部的套件倉庫抓東西，**在程式碼裡直接寫死了一組帳號密碼**。

密碼不是明文，是用一種**看起來像亂碼、但任何人一行指令就能還原**的編碼寫的。**這不是加密，只是換個樣子寫**——等於把鑰匙放在門口地墊下面。

**本報告撰寫時開檔核對，而且往外查了一輪**：

- 這支腳本**確實已經進版本控制**。
- **同一組帳密在前端那個程式碼倉庫的三個流程設定檔裡也有**，同樣入版控——**所以這是跨兩個程式碼倉庫、至少四個檔的事，不是只有一支腳本。**
- 解出來的帳號是**管理員層級的名字**，不是一個限定唯讀的專用帳號。
- 這組密碼與第 1 條那組資料庫密碼**不是同一組**，是各自獨立的兩組。

**為什麼要跟第 5 條分開**（同一支腳本的相鄰兩行，但是兩件事）：

| | 第 5 條 | 第 6 條（這一條） |
|---|---|---|
| 問題 | 連線沒有加密 | 憑證寫進版本控制 |
| 性質 | 傳輸過程可能被偷看、被掉包 | **東西已經流出去了**，刪檔案也收不回 |
| 修法 | 把網址改成加密連線 | 把帳密移出版本控制 |

🔴 **分開列的實際理由**：兩者在腳本裡是上下相鄰的兩行。**如果併成一條，去修「連線沒加密」的人很可能改完網址就收工，完全沒注意到下一行那組帳密還躺在那裡。**

::: {.callout .warn}
**腳本裡那句「非機密」的註解，不能照單全收**

腳本裡寫著這組帳密「非機密，是內網倉庫的唯讀憑證，前端倉庫裡本來就是明文」。

**前半段是事實**（前端倉庫確實也有，已經核對過）——**但那不構成「非機密」的理由，它只說明同一個問題有四個檔。**

**「唯讀」這件事則還沒有人實際查過。** 帳號名是管理員層級，**要去後台看過才算數**：

- 若**確實唯讀、而且只讀得到公開代理的內容** → 風險確實低，但仍應移出版本控制，並把那句註解改成準確的說法。
- 若**不是唯讀、或讀得到內部套件** → **嚴重程度要往上調，而且要換密碼**。

**這是一個提醒**：程式碼裡的註解會過期、也會寫錯，**與資料庫是不是真的那樣是兩件事**。這一塊開頭那個「整合功能沒在用」的錯誤前提，也是同一種狀況。
:::

### 第 7 條：刪附件的安全檢查只做一半（低）

**問題**：程式算出了「該過濾掉哪些檔案」，接下來卻用回了**沒過濾的那一份清單**。這是明確的程式錯誤。

**本報告撰寫時開檔核對，一行未改。**

**目前打不到**——現在的操作流程一次只會傳一個檔案編號。但**未來如果做批次刪除功能，會在使用者不知情的情況下刪錯檔案，而且不會報錯**。

::: {.callout .warn}
**這一條是自動工具的一個盲點，值得記住**

工具**兩次掃描都沒有報它**（第一次與後來的重查都沒有）。原因是它的推論停在一個問題上：「**現在打得到嗎**？」——答案是打不到，所以判定不成立。

**「程式寫錯了」與「現在有人打得進來嗎」是兩件事。** 我們的方法擅長回答後者，前者要靠人開檔看。
:::

---

## 這塊的結論

::: {.callout .warn}
**一句話**：這塊的技術檢視品質是全案最好的一批（六輪覆核全部完整跑完，唯一一批做到），**但它同時撞出了全案散布範圍最廣的一次密碼外流**——一組可以繞過所有客戶隔離的資料庫密碼，散在 249 個檔案裡。

**建議**：

1. **第 1 條建議最優先**——它是這塊唯一的高風險，而且拿到的那組帳號可以繞過所有客戶隔離。**三步的順序不能顛倒**，先堵住再發生的路，不然清乾淨了還會再長回來。
2. **第 2 條的兩件產品決策都已經裁完了**（後端要補上權限檢查、清單分兩套邏輯），**現在純粹是技術工作**。要注意的是清單那件要做兩步：先讓後端分得出是哪一頁在呼叫，才談得上各套各的規則。
3. **第 4 條與主專案快取服務那一處一起清**——真正「程式寫死不驗」的目前是三處（GitHub 兩處＋快取一處），同一種病、同一種修法。原本列的員工帳號目錄與寄信這次查證後已不計入。
4. **第 5 條影響範圍是全部程式庫，不只這一塊**——它不屬於任何單一模組，建議另外當成一件事處理。
5. **第 6 條建議跟第 1 條一起清**——那是同一種病（憑證寫進版本控制）。🔴 **但它要跟第 5 條分開追**：兩者是同一支腳本相鄰的兩行，合在一起做，很容易改完連線就收工、漏掉那組帳密。**另外要先去後台確認那個帳號是不是真的唯讀**，那會決定它的嚴重程度要不要往上調。

**另外要提醒**：這塊功能後來整組搬過家、有重新檢視過。**重查確認：最早那六張工單開出之後，經過一次大規模的程式重整，一件都沒有被順手修掉。** 詳見[〈意見回饋重新檢視〉](M21-issue-rescan.html)。
:::

| 統計 | |
|---|---|
| 檢視輪數 | 7 輪（原 6 輪 158 個檔案＋主系統接線 1 輪，與設備清冊並排共 8 個檔，**沒有新發現**） |
| 通過的發現 | 19 條（去重後歸成 **7 件事**——原為 6 件，本次內化把打包腳本內嵌帳密那項從第 5 條底下獨立出來，成為第 6 條） |
| 最嚴重 | 0 條 |
| 高風險 | 1 條 |
| 已修 | 0 條 |
| 已開工單未排期 | **7 張**（CM-1629～1634，加上本次新開的 CM-1998） |

---

| 你想知道 | 看哪裡 |
|---|---|
| 搬家之後這些問題還在不在 | [意見回饋重新檢視](M21-issue-rescan.html) |
| 我們用什麼方法查、結果有多可信 | [檢視方法與工具](GUIDE-01-method-and-tools.html) |
| 其他模組的檢視結果 | [回總報告首頁](index.html) |
