---
title: 共用基礎
eyebrow: Guidant AI 資安檢視 · 模組報告
h1: 共用基礎（jedi-common）
lede: 這是全產品的地基，每一個功能都踩在它上面，**它出問題等於全部功能一起中**。這裡也是全案少數「查出來、修掉、而且連已經寫進去的髒資料都清乾淨」的地方；但後來補查出全案唯一一條**不必登入就能讓產品停擺**的問題，也落在這塊。
---

## 這塊在產品裡做什麼

**這是全產品的地基**——現役 21 支套件裡有 **20 支**依賴它，主專案有 **282 個檔案**在用它。**它出問題，等於每一塊功能都出問題。**

客戶在產品裡看不到這一塊，但每一次操作都會經過它。它是所有功能共用的地基，負責三件事：

::: grid2
::: {.card .plain}
#### 它平常在做什麼
1. **每一次查資料**都先經過它，由它告訴資料庫「現在是誰在查、只能看哪一家客戶的資料」
2. **每一行運作紀錄**（什麼時候誰做了什麼）寫成什麼樣子、寫到哪裡，由它決定
3. **每一次回覆給畫面的資料**長什麼樣子、出錯時回什麼訊息，也由它決定
:::
::: {.card .crit}
#### 為什麼這塊特別要緊
其他每一塊都只影響自己那個功能。**這一塊影響全部。**

客戶資料互相隔開的機制就是在這裡啟動的——這道機制是「就算功能本身漏了檢查，資料庫也會擋一下」的最後一道保險。它要是失靈，前面所有功能補的檢查都少了兜底。
:::
:::

---

## 檢視軌跡

**檢視期間**：2026-09-10 ～ 2026-09-11，另 2026-09-23 補一輪｜**範圍**：91 個檔案；另加我們主系統那道「每個請求都經過」的攔截程式（在日誌接線那一輪一起查）｜**共 3 輪＋1 輪相關**

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

| 輪次 | 查什麼 | 檔數 | 覆核投票 | 結果 | 原始報告檔名 |
|---|---|---:|---|---|---|
| **C1** | 客戶資料隔離是怎麼啟動的、身分怎麼帶進來、出錯時怎麼處理 | 29 | 24 票全投完 | 找到 3 條（2 高 1 中）＋人工另查出 2 條 | `scan-C1-authz-core` |
| **C2** | 運作紀錄全鏈——寫什麼、寫到哪、怎麼標身分 | 32 | 12 票全投完 | 自動檢視零發現，**7 條全部是人工查出來的**（1 高 4 中 2 低） | `scan-C2-logger` |
| **C3** | 回覆給畫面的資料、分頁、共用工具、出貨打包設定 | 30 | 18 票全投完 | 找到 4 條（3 中 1 低）＋人工另查證 4 條 | `scan-C3-output-and-packaging` |
| **W5**（日誌接線，相關） | 主系統那道攔截程式怎麼把每個請求記進日誌、記之前怎麼遮密碼 | 5 | 1 個疑點、3 票全投完、被否決 | 自動檢視零發現；**執行者開檔實測查出第 15 條**（不必登入就能讓全產品停擺）。原始報告在 [FR-115 站](https://guidantai-feature-doc.jedicotech.com/FR-115-2609-host-wiring-security-scan/)，同一輪另兩條屬[系統日誌](M09-log.html) | `scan-W5` |

::: {.callout .warn}
**第二輪自動檢視「零發現」，但那一輪其實找到 7 條**

這一輪是很好的例子，說明為什麼不能只看工具的結果——**7 條全部是人工查出來的**，包含這塊風險最高的那一條（密碼原文寫進紀錄檔）。自動檢視提出的四個疑點反而全部被否決。

詳見[檢視方法與工具 → 為什麼不能只看 AI 的結果](GUIDE-01-method-and-tools.html#why-human)。
:::

::: {.callout .ok}
**開工時列的六個懷疑，有三個被推翻——這是健康的**

被推翻的其中一條特別值得記：原本擔心「運作紀錄上『是誰做的』這個欄位可以被偽造」，查下去發現**那段程式從來沒有被啟用過**，是一段死程式。

如果沒有這層覆核，會有人花時間去修一段根本沒在跑的程式。
:::

::: {.callout .warn}
**第一輪沒有留下「每個檔案都讀過」的紀錄**

所以這裡只能說「這一輪在這 29 個檔案裡找到這幾條」，**不能說「這塊的權限邏輯只有這三個問題」**。而且這支套件裡的紀錄、共用工具、常數那些程式，第一輪完全沒有讀過（後面兩輪才涵蓋到）。
:::

---

## 問題一覽

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

| # | 問題 | 風險 | 分類 | 出事會怎樣（誰受影響） | 怎麼修 | 狀態 |
|---|---|---|---|---|---|---|
| **1** | 使用者的登入密碼與通行證，原文寫進紀錄檔與資料庫 | 🟠 高 | 敏感內容寫進日誌 | **每一次登入都會發生，不是偶發**。拿得到紀錄檔的人（含外包、離職者、以及拿到回廠診斷包的人）就等於拿到密碼與可以直接冒用的通行證，不必破解任何東西 | 寫進紀錄前先把密碼與通行證遮掉，已經寫進去的一併清掉 | ✅ **已修正**（遮蔽已補上，兩張紀錄表現在都是 0 筆，含明文的紀錄檔已全數刪除，見[下方](#password-log)） |
| **2** | 沒有登入身分時，客戶資料隔離會整個關掉 | 🟠 高 | 客戶資料沒隔開 | 這是全案影響面最廣的一條——**它是所有功能的最後一道保險**。這道保險關掉時，那段程式的每一次查詢與寫入都橫跨全部客戶，任何小錯誤都會從「影響一家公司」放大成「影響全部公司」 | 沒有身分時改成什麼都看不到，繞過隔離必須是一個明說的動作 | ✅ **已修**（見[下方](#rls-failopen)） |
| **15** | 🔴 **不必登入，送一個特製內容的請求就讓整個產品對所有客戶停止回應** | 🟠 高 | 資源耗盡 | **網路上任何人都能做，不需要帳號。** 每個請求進來、還沒檢查是誰之前，系統就先把內容拿去「遮密碼」再記進日誌，一次請求遮兩遍；遮密碼那段比對遇到特定形狀的內容，耗時跟長度的平方一起長。開發環境實測：打一個不存在的網址、帶 128KB 的內容，卡 6.4 秒；後端只有 4 條處理程序，同時送幾個就全卡住，**所有客戶一起連不上** | 兩層都要修：①共用套件裡那段比對改成不會越跑越慢的寫法（根因）；②遮之前先限制長度，超過就截斷並標「已截斷」 | ✅ **已修（FR-114 CM-2171，BE commit `908f9dd0e`，套件 jedi-common commit `cc61f18e`／jedi-iam commit `960d737e`）** |
| **3** | 全站唯一那道密碼遮蔽，遇到密碼裡有雙引號就只遮一半 | 🟡 中 | 敏感內容寫進日誌 | **密碼越強越容易中招**——強密碼本來就會帶特殊字元。遮不乾淨的半截密碼會留在資料庫裡九十天，受害最深的是系統整合用的密碼與金鑰密語 | **改一行**：讓比對認得被跳脫的雙引號（見[下方](#mask-quote)）。實際做法：遮蔽規則改成認得跳脫的雙引號 | ✅ **已修（CM-2065）** |
| **4** | 兩張運作紀錄表沒有客戶隔離，連可以拿來隔離的欄位都沒有 | 🟡 中 | 客戶資料沒隔開 | 能連進資料庫的人看得到全部客戶的運作紀錄 | **裁定不做隔離**，改成明確定位成維運資料並限縮誰能讀（見[下方](#ops-tables)） | ✅ **裁定不修** |
| **5** | 完整的程式出錯內容被寫進那張沒有隔離的紀錄表 | 🟡 中 | 客戶資料沒隔開 | 看得到那張表的只有維運人員，而完整的出錯內容正是他們除錯要用的 | **裁定維持原樣**（與第 4 條同一套理由，見[下方](#ops-tables)） | ✅ **裁定不修** |
| **6** | 主系統連暫存資料庫時，寫死成「不確認對方是不是真的那台伺服器」 | 🟡 中 | 密碼外流 | 那條連線跑在容器內部、沒有對外。要攔截它得**先進到這台主機上**，而到了那一步，直接讀設定檔就有密碼 | **裁定不修**（見[下方](#redis-tls)） | ✅ **裁定不修** |
| **7** | 一次可以要求回傳幾筆資料，沒有上限 | 🟡 中 | 資源耗盡 | 任何登入的人送一個很大的數字，就能叫資料庫把整張表一次全撈，重複幾次**整個產品對所有客戶停止回應**。既有的「請求大小上限」擋不住，因為送出去的請求很小、要回來的資料很大 | **分兩步**：先盤點誰在要求很大的筆數，處理完再加上限（見[下方](#page-size)）。實際做法：每頁筆數加上限 1000 | ✅ **已修（CM-2065）** |
| **8** | 環境設定值打錯字時，程式會悄悄退回開發用的紀錄設定 | 🟡 中 | 要先做產品決策 | 客戶那邊已有兩層保底（安裝程式會設、部署設定也有預設），剩下的縫是**值打錯字**——例如寫成 `production` 而不是 `prod`，程式認不得就悄悄退回開發設定 | 認不得時改成套用正式設定。**不要報錯、不要擋住啟動**（見[下方](#run-env)）。實際做法：設定認不得時退到正式模式 | ✅ **已修（CM-2065）** |
| **9** | 兩種紀錄的詳細程度被寫死成最詳細 | ⚪ 低 | 資源耗盡 | **沒有安全問題**（這兩種紀錄只寫檔案、不進資料庫），但紀錄量會暴增、吃掉磁碟 | 兩處紀錄等級調回正常。實測發現資料庫對映紀錄本身就是一般等級、調回一般等級擋不住（開機一分鐘仍倒約 6,000 行），已改成只留警告以上；輸出到畫面的那一層也不再寫死最詳細，改跟隨紀錄等級設定 | ✅ **已修（CM-2065）** |
| **10** | 程式一載入就自動對外連一條不加密的監控連線 | ⚪ 低 | 防竄改機制被削弱 | 目前沒有任何地方接這條線，所以只是無效重試；但哪天有人架起接收端，追蹤資料就會以明文送出去 | 改成需要明確呼叫才啟動、位址改設定檔控制 | ✅ **已修**（改成不呼叫就完全不啟動，沒設環境變數就不連；**那條連線本身仍然是不加密的**，只是現在要有人主動開才會走到） |
| **11** | 一支開發用的小工具混在正式出貨的套件裡 | ⚪ 低 | 回應夾帶不該送的欄位 | 它的檔頭自己就寫明「不是產品程式碼」，但**它會跟著出貨的套件一起裝到客戶機器上** | **直接刪掉**（已確認五個程式庫全部零引用，見[下方](#dead-code)）。已整支刪除，連帶移除選配依賴宣告 | 🗑️ **已裁定刪除，已刪除**（FR-114.3-3，1.21.0 出貨） |
| **12** | 共用的「資料轉成回覆內容」工具，資料上有什麼就吐什麼、完全不過濾 | ⚪ 低 | 回應夾帶不該送的欄位 | 呼叫的人只要忘了自己先篩選，畫面就會拿到不該看到的內部項目。**已經真的出過事**——[AI 儀表板那塊](M15-ai-dashboard.html)的回覆夾帶密碼加密用的鹽值，走的正是這支工具 | 修法已在 AI 儀表板那塊裁定（見[下方](#serializer)） | ✅ **已修（CM-2038）** |

### 另外兩條「不是漏洞，但寫法本身是地雷」

這兩條經覆核確認**目前不構成漏洞**，列出來是因為它們是「現在沒事、哪天會出事」的形狀。

| # | 問題 | 分類 | 為什麼還是要處理 | 狀態 |
|---|---|---|---|---|
| **13** | 隔離用的設定值是用字串拼接組進查詢語句裡，沒有用安全的寫法 | 外部送什麼就收什麼 | 目前安全**完全依賴「現在沒有人把外部輸入帶進這條路」這個當下的事實**。哪天有人加一條新路徑把外部輸入帶進來，漏洞就成立了，**而且加那條路的人不會知道自己踩到了什麼**。但**這不是順手改一下就好的東西**（見[下方](#string-concat)） | ✅ **裁定不修**（三個值目前都是乾淨的） |
| **14** | 一個在每次連線時都會被設定、但全系統沒有任何地方讀它的開關 | 要先做產品決策 | 它現在完全沒有效果，**但留著會讓之後讀程式的人誤以為「這裡已經有一道部門層級的檢查」，反而不會去補真正需要的檢查** | ✅ **已修（FR-114.3-3）**：已裁定刪除（見[下方](#dead-flag)），設定碼已刪、四處提及它的說明文件已同步更新 |

---

下面只展開需要你判斷、或容易被誤解的幾條。其餘的修法明確，看上面表格「怎麼修」那一欄就夠了。

---

## 第 1 條：密碼原文寫進紀錄檔——已經修正 {#password-log}

::: {.callout .ok}
**這條值得單獨講，因為它是全案少數「查出來、修掉、而且連已經寫進去的髒資料都清乾淨」的完整循環。**
:::

### 問題是什麼

產品在每個請求進來時都會記錄兩件事：這次請求帶了哪些資訊、送了什麼內容。問題是**這兩行都沒有遮蔽任何東西**——

| 記了什麼 | 裡面有什麼 |
|---|---|
| 請求帶的資訊 | **可以直接冒用身分的通行證** |
| 請求送的內容 | **使用者的登入密碼原文** |

而且同一支程式再往下五行、要寫進另一張紀錄表之前，**是有呼叫遮蔽功能的**——就是這兩行沒用上。

這些紀錄會寫到三個地方：畫面、伺服器上的紀錄檔、以及資料庫裡的紀錄表（那張表沒有客戶隔離，見第 4 條）。**而回廠診斷包會把紀錄檔整包帶走**——等於把這個問題從「客戶自己的機器」擴大到整條回廠路徑。

### 怎麼修好的

| 改了什麼 | 做法 |
|---|---|
| **請求帶的資訊** | 改成白名單——只有名單上的項目才印出真值，其餘只印名字、值一律遮成星號 |
| **請求送的內容** | 套上現成的遮蔽功能 |
| **回覆的內容** | 也一起套上遮蔽（心跳回應會夾帶解密後的工具密碼，只遮請求那一半等於沒遮） |

用白名單而不是黑名單是刻意的：**黑名單漏掉的是「未來新增的憑證型項目」，那種漏法沒有人會發現；白名單漏掉的是「某個診斷項目看不到值」，一眼就看得出來。**

### 已經寫進去的也清乾淨了

| 清了什麼 | 現況 |
|---|---|
| **資料庫的兩張紀錄表** | **現在都是 0 筆**（原本一張十六萬多筆、一張九萬多筆） |
| **含明文密碼的紀錄檔** | **全數刪除，現在是 0 個** |

**那些明文從來沒有進過版控**——存放紀錄檔的目錄本來就被版控排除，所以不存在「已經散到程式碼倉庫、刪不掉」的問題。

**換密碼不需要**：這些明文只出現在開發環境，決策者已裁定不必為此更換任何密碼。

### 一個已知的邊界

遮蔽是靠比對資料裡的項目名稱，**另一種表單格式的請求擋不到**。登入本身走的是有遮的那種格式（已實測），所以最要緊的那條路是蓋住的。

---

## 第 2 條：沒有登入身分時，客戶資料隔離整個關掉 {#rls-failopen}

::: {.callout .crit}
**這條的特別之處：它是「最後一道保險」本身失靈。**

所有功能層面補的檢查，指望的都是「就算我漏了，資料庫那層會擋」。這條說的是那一層在某些情況下會整個打開。
:::

### 問題是什麼

每一次查資料前，系統要先告訴資料庫「現在是誰在查」。原本的寫法是：**如果查不到身分，就當作最高權限的系統管理員處理**——資料庫因此放行全部客戶的資料。

而「沒有身分」**不是罕見狀況**。至少有兩條路每次都處在這個狀態：登入功能本身（還沒登入當然沒身分），以及一個完全沒有守門的雲端硬碟通知入口。

### 現在的狀況

**完全沒有身分就什麼都看不到**，而且**會留下一行警告，標出是哪裡沒帶身分就進來了**，方便找出還沒接好的地方。

程式裡同時明文寫下一條規定：**繞過隔離只認明說的訊號，不可以再加「缺某個欄位就當最高權限」這種條件**——從缺值推論最高權限，任何忘記設值的路徑都會悄悄拿到全部資料的可見性。

目前有 **25 處**在用繞過機制（主專案 11 處、套件 14 處），**每一處都寫了名字與理由**，全是背景排程或登入流程本身（登入時還沒有身分，是雞生蛋的問題）。

::: {.callout .ok}
**這個形狀值得講**

繞過從「預設的副作用」變成「**要明說的動作**」。修改前，忘記帶身分的下場是「悄悄看到全部」；修改後，忘記帶身分的下場是「什麼都看不到，而且留下一行警告」。**前者沒有人會發現，後者一定會有人回報。**
:::

::: {.callout .warn}
**連帶要注意：兩支背景排程本來是靠這個舊行為在跑的**

它們沒有登入身分，原本正是靠「沒身分就給最高權限」才運作得起來。改成擋下之後，這兩支如果沒有一起接上具名身分，會**悄悄停止運作而且不會報錯**。（檢視當時已確認它們已改成具名系統身分。）
:::

---

## 第 3 條：把比對式子寫對，改一行 {#mask-quote}

### 問題是什麼

全站唯一那道密碼遮蔽，做法是**在要寫進紀錄的那段文字裡找出密碼那一段，再把它換成星號**。

**這個方向本來就是對的。** 錯的只有一個地方：它**假設密碼裡不會出現雙引號**。所以密碼是 `ab"cd` 這種時，它以為在第一個引號就結束了，**後半截沒遮到**。

### 修法：讓比對認得被跳脫的雙引號

**一行。**

### 為什麼不改成「先把整段照格式解開、再換掉密碼那一項」

這個方向乍看更乾淨，但**對紀錄檔來說更危險**：

| | 比對文字 | 先照格式解開 |
|---|---|---|
| 遇到格式壞掉的內容 | 照樣遮 | **解不開** |
| 解不開的後果 | — | **整段不遮，原文直接寫進紀錄** |

而紀錄檔裡**常有半截的、壞掉的東西**——連線中斷、寫到一半、格式不符的請求，全部會落在這裡。**在最需要遮的時候失效，比遮不乾淨更糟。**

::: {.callout}
**效能不是考量點**——兩種做法都是微秒等級，而寫紀錄檔本身（要碰硬碟）比它們慢好幾個數量級。不必為了效能在兩者之間權衡。
:::

---

## 第 4、5 條：兩張運作紀錄表是維運工具——裁定不做客戶隔離 {#ops-tables}

::: {.callout .decided}
**裁定：這兩張表不做客戶隔離，改成明確定位成維運資料、限縮誰能讀。**
:::

### 兩個理由，第二個更根本

**一、技術上分不乾淨。** 有些紀錄天生就沒有客戶：

| 這種紀錄 | 為什麼分不出客戶 |
|---|---|
| **登入失敗** | 還沒登入，根本不知道是誰 |
| **系統啟動** | 沒有人在操作 |
| **背景排程** | 系統自己跑的，不屬於任何一家 |

標不了的那些只有兩條路：**留空**——隔離規則會把它們全部擋掉，**紀錄等於廢掉**；或**塞假值**——追查時更混亂。**部分能分等於不能分。**

佐證：那張表**有「使用者」欄位、但沒有客戶欄位**——**連現成的資料都不足以分**。

**二、以落地版為主。** **一家客戶裝一套，整座資料庫就是他的**，「跨客戶看得到」這件事在落地版根本不存在。

### 第 5 條（完整出錯內容）是同一套邏輯

那張表本來就沒開放給外面查，**看得到的都是維運人員，而完整的出錯內容正是他們除錯要用的**。看得到的人本來就該看得到。

**日誌轉送到外部那條路也會帶完整出錯內容，同樣不列入修法範圍**（同一個理由）。

**這兩張表就是維運工具，讀的人是自己人。**

---

## 第 6 條：暫存資料庫連線——裁定不修 {#redis-tls}

::: {.callout .decided}
**裁定：不修。那個暫存資料庫跑在容器內部網路、沒有對外。**
:::

原本的顧慮是「能插進內部網路的人可以假扮成那台伺服器」。**這句話少了一個前提**：以目前的部署形態，那條線跑在同一台主機的容器內部網路裡。

要攔截它，得**先進到這台主機上**——**而到了那一步，直接讀設定檔就有密碼，攔線沒有意義。**

---

## 第 7 條：回傳筆數沒上限——這頁投報率最高的一條 {#page-size}

::: {.callout .decided}
**裁定：要修，但不可以直接加上限就上線，要分兩步。**
:::

### 為什麼投報率最高

**11 支套件 ＋ 主專案**吃到同一份設定。**改一個地方，十二個地方一起好。**

順帶一提：那份設定裡「第幾頁」是有檢查下限的，只有「一頁幾筆」的上限漏掉了——**不是沒想到，是漏了一半。**

### 修法分兩步，順序不能顛倒

| 步驟 | 做什麼 | 為什麼不能省 |
|---|---|---|
| **第一步** | **盤點「誰在要求很大的筆數」** | **有些統計可能是「撈全部回來自己數」的寫法**，直接加上限會讓**統計失真**——數字變小了，而且不會報錯 |
| **第二步** | 處理完那些，再加上限 | — |

### 上限數字不用精算

抓一個明顯夠用的（例如一千）就好。兩種抓錯的後果差很多：

| 抓錯的方向 | 後果 |
|---|---|
| **抓太小** | 頂多有人回報清單載不出來——**看得到、改得掉** |
| **不設** | 整個產品對所有客戶停止回應——**看不到、來不及救** |

---

## 第 8 條：環境設定值打錯字時會悄悄退回開發設定 {#run-env}

### 問題是什麼

負責讀環境設定值的那段程式，**認不得那個值的時候會自動退回成「開發環境」**。開發環境的設定會把「寫進資料庫」這條紀錄路徑掛在七種紀錄類別上（正式環境的設定本來完全沒有這段）。

### 現在的縫只剩一個

客戶那邊已經有兩層保底——**安裝程式會設，部署設定也有預設**。所以「完全沒設」這件事在照標準方式裝出來的環境不會發生。

剩下的縫是**值打錯字**：例如寫成 `production` 而不是 `prod`，程式認不得，就悄悄退回開發設定。**而那才是客戶端真的可能發生的。**

### 裁定的修法

::: {.callout .decided}
**認不得那個設定值的時候，用正式設定（不是現在的開發設定）。不要報錯、不要擋住啟動。**
:::

決策者的原話值得寫進來：

> **你不能因為設定錯誤就不讓啟動了，客服會瘋掉。**

這是一個好判準——**安全措施如果會讓正常客戶用不了，那個措施本身就是問題。** 把預設從「開發」改成「正式」，打錯字的後果就從「悄悄變寬鬆」變成「悄悄變嚴格」，方向反過來，而且不影響任何人開機。

---

## 第 11、14 條：兩處已裁定刪除 {#dead-code}

這兩條是同一個形狀：**沒人用的東西不要留著。** 稽核流程那塊、意見回饋那塊也有同形狀的幾條，同樣都裁定直接刪掉。

### 第 11 條：一支開發用的小工具

**那是什麼**：一支開發時用的小工具（讓 AI 幫程式碼自動寫註解）。**它的檔頭自己就寫明「不是產品程式碼」**——但**打包出來的安裝檔裡有它，會裝到客戶機器上**。

**檢查範圍**：五個程式庫（套件庫、主專案、畫面端、測試、授權中心）**全部零引用**，也沒有被設成可執行指令。

::: {.callout .ok}
**更有價值的一點**

那支工具需要一個很大的外部元件（十幾萬行），而**打包時有人特地寫了程式把它排除掉**。所以**刪掉這支工具，那段排除的程式與選配設定也可以一起清掉**——省下的不只是一個檔案，還有為了它而長出來的一圈包裝。
:::

### 第 14 條：一個死掉的開關 {#dead-flag}

**那是什麼**：**一個在每次連線時都會被設定、但全系統沒有任何地方讀它的開關。**

**查證結果全部是零**：

| 查了哪裡 | 命中 |
|---|---|
| 21 支套件、主專案、畫面端、測試、代理程式、授權中心的程式碼 | **0** |
| 開發環境的 259 條隔離規則 | **0** |
| 資料庫裡自己寫的那些程式、檢視表、觸發程序 | **全部 0** |
| **出貨基線**（新客戶裝的那份） | **0** |
| 全部資料庫異動腳本 | **0** |

**唯一的命中是四份說明文件**——**刪掉程式的同時那幾處也要改**，否則文件會描述一個不存在的東西。

::: {.callout .warn}
**一個限制要寫明**

只查了開發環境的資料庫。**其他環境如果有人手動加過規則，要各自再數一次。** 不過**出貨基線是 0，新裝的客戶不會有**。
:::

::: {.callout .ok}
**旁邊就有一個正面對照**

**同一支檔案裡、設定它那一行的旁邊，有一個長得很像但「活的」開關**——**有 5 條隔離規則在讀它，而且它是被明確呼叫時才設的，不是無條件設**。

所以這條不只是「刪三行」，而是**正確的形狀就在旁邊，照著它的樣子做**。
:::

---

## 第 12 條：根源在這裡，後果在 AI 儀表板那塊 {#serializer}

這塊有一支共用工具，負責把查到的資料轉成回覆內容。它**資料上有什麼就吐什麼、完全不過濾**。

**[AI 儀表板那塊](M15-ai-dashboard.html)的第 4 條（回覆夾帶密碼加密用的鹽值與「是不是超級管理員」的標記），根源就是這一條。** 那頁講後果，這頁講根源。

修法已在那邊裁定，分兩步：

1. **馬上止血**——把鹽值與超級管理員那個標記加進這支工具既有的「不要送」清單；
2. **長期**——改成在資料本身打記號，看到記號就跳過。

**全產品只有兩個地方在用這支工具**（都在 AI 儀表板那塊），主專案零使用。

---

## 第 13 條：不動，但別以為順手就能改 {#string-concat}

::: {.callout .decided}
**裁定：不動。那三個值現在都是乾淨的，而部門路徑根本沒人填。**
:::

**那句提醒仍然成立**：哪天有人加一條新路徑把外部輸入帶進來，漏洞就成立了，**而且加那條路的人不會知道自己踩到了什麼**。

**但不要以為順手改一下就好。** 這段的標準安全寫法用不了——那個指令**不支援「把句子和要填的值分開傳」**（一般查詢可以，這個指令不行）。真正的改法是**改成呼叫資料庫內建的功能**，而且**要確認隔離規則還讀得到值**。**這正是當初這段改很久的原因。**

## 第 15 條：不必登入就能讓整個產品停擺 {#mask-dos}

**在哪個畫面發生**：沒有畫面。任何人對我們網站的任何網址（連不存在的網址都算）送一個請求，內容照特定形狀排。

**為什麼不必登入**：我們在每個請求進來時，會先記一筆操作日誌，而記之前會先把內容裡的密碼遮掉——**這兩步都發生在檢查身分之前**。所以不管送的人有沒有帳號，那段遮密碼的比對都會跑。

**為什麼一個請求就能卡好幾秒**：遮密碼用的是一條比對規則，碰到刻意排過的內容，要比對的次數會跟內容長度的平方一起長。內容加長一倍，時間變四倍。（形狀刻意不寫在這裡。）

| 量了什麼 | 結果 |
|---|---|
| 一個 128KB 的請求（遠低於全站 50MB 上限） | 卡住一條處理程序 **6.4 秒** |
| 後端處理程序數量 | 預設 **4 條** |
| 同時送幾個 | **四條全卡住，所有客戶一起連不上** |
| 已完成但還沒發版的另一張修正（改同一段比對） | 同樣 128KB 從 3.2 秒變 **26.8 秒**——慢約 8 倍 |

::: {.callout .crit}
**為什麼它是這一輪最要緊的一條**

全案所有「讓產品對所有人停擺」的問題裡，**其他每一條都要先登入**，只有這一條網路上任何人都能打。

而那張已完成、還沒發版的修正會讓它更嚴重——**那張發版之前，這條必須一起修進去**，否則出貨版本比現在還差。
:::

**怎麼修**：兩層都做。①共用套件裡那段比對規則改成耗時跟長度成正比的寫法——這是根因；②主系統那道攔截程式在遮密碼之前先限制長度，超過就截斷並標「已截斷」——這是保險。修完用同一組長度重測，確認耗時隨長度等比增加。

**在哪裡**：`common/middleware/app_mw.py:160-174`（記日誌前遮密碼，印檔一次、寫資料庫一次）；比對規則在 jedi-common 套件 `jedi_common/utils/common_utils.py` 的 `mark_password`。

**狀態**：這條執行者開檔實測查出，**沒有經過三人重查投票**，由統籌者開檔核對屬實；嚴重度原判中，決策者裁升為高。

---

## 這塊的結論

::: {.callout .warn}
**一句話**：這塊原本的兩條高風險**都已經修好了**；但補查主系統那道攔截程式時多出一條新的高風險（第 15 條：不必登入就能讓產品停擺），**要排在最前面**。剩下的分量較輕，而且經過逐條裁定之後，**有四條看過之後確認不用改、兩條直接刪掉**，真正還要動手的有八條。

**建議**：

1. 🔴 **第 15 條最優先**——全案唯一不必登入就能讓產品停擺的一條，而且另一張已完成的修正發版前必須跟它一起修。
1. **第 7 條（回傳筆數沒上限）接著做，這是這頁投報率最高的一條**——**11 支套件加主專案吃同一份設定，改一個地方十二個地方一起好**。但要記得先盤點「誰在要求很大的筆數」再加上限，否則會把統計弄失真。
2. **第 3 條（遮蔽遮不乾淨）跟第 1 條放在一起理解**——第 1 條是「根本沒遮」已經修好，第 3 條是「遮了但遮不乾淨」，**改一行**就好，成本極低。
3. **第 11、14 條（兩處已裁定刪除）適合併著一起清**——都是「沒人用但留著會誤導後人」的東西，零引用已查證完畢。刪第 14 條時**同時要改那四份說明文件**。
4. **第 8 條（環境設定值打錯字）把預設從「開發」改成「正式」即可**，**不要讓它擋住啟動**；第 9 條（紀錄太詳細）還剩兩處要調回一般等級，順手併進同一批。
5. **第 12 條的止血動作在 [AI 儀表板那塊](M15-ai-dashboard.html)一併做**，這裡是根源、那裡是後果，一起處理才不會做兩次。
:::

| 統計 | |
|---|---|
| 檢視輪數 | 3 輪、91 個檔案（另有日誌接線那一輪涵蓋主系統的攔截程式） |
| 找到的問題 | 15 條（13 條資安 ＋ 2 條寫法地雷） |
| 高風險 | 3 條（全部**已修好**） |
| 已修好 | 9 條（第 1、2、3、7、8、9、10、12、15 條，1.21.0 出貨） |
| 看過之後裁定不修 | 4 條（第 4、5、6、13 條） |
| 裁定直接刪掉，已刪除 | 2 條（第 11、14 條，1.21.0 出貨） |

---

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