檢查期間 2026-09-09~10|分六次檢查完成|首腦已驗收
分六次檢查了 158 個檔案,去除重複後歸納成 6 件要修的事:
| 緊急程度 | 幾件 | 說明 |
|---|---|---|
| 🔴 高(建議優先) | 1 | 資料庫密碼外洩,且指向等同正式環境的機器 |
| 🟡 中 | 4 | 權限沒檢查、金鑰外洩、連線沒加密 |
| ⚪ 低 | 1 | 程式碼寫錯一半,目前還打不到 |
現況:全數已修(1.21.0 出貨)——當時(09-10)只是建卡歸檔,09-21 起改照內化版問題總表(docs/security-report/M23-issue.md)派工,早期六張卡作廢;對照見 README 修正卡段。
| 卡號 | 這是什麼問題 | 出事會怎樣 | 緊急度 | 建議順序 |
|---|---|---|---|---|
| CM-1629 | 資料庫管理員密碼(也是 Redis 密碼,同一組)被寫進 249 個文件裡。其中一份還把主機、帳號、密碼湊成一條可直接使用的連線指令 | 拿得到程式碼的人可直接連進資料庫,這個帳號會繞過所有客戶隔離,所有客戶資料可讀可寫。而那份文件指的是 POC 機器(規定上等同正式環境) | 🔴 高 | 1 |
| CM-1630 | 「意見回饋」六個功能只有「匯出」會檢查權限,其餘五個任何登入者都能用;而且修改、刪除從不檢查那筆是不是你的 | 任何員工可以刪掉或竄改別人的回饋,連帶關閉 GitHub 上的問題單,稽核紀錄還把他記成合法操作人 | 🟡 中 | 2 |
| CM-1631 | Google 雲端硬碟的應用程式密鑰與加密金鑰外洩(22 個檔) | 配合上一條拿到資料庫後,可解密所有客戶存的雲端硬碟權杖,讀取客戶檔案 | 🟡 中 | 3 |
| CM-1632 | 連 GitHub 時沒檢查對方是不是真的 GitHub(兩處) | 網路中間人可假冒 GitHub,攔走公司的 GitHub 存取權杖 | 🟡 中 | 4 |
| CM-1634 | 公司內部套件倉庫走沒加密的連線,而且是主要來源(26 個專案全部一樣) | 內網有心人可在安裝套件時掉包,等於在打包機上執行他的程式碼——而打包機產出的是客戶拿到的安裝檔 | 🟡 中 | 5 |
| CM-1633 | 刪除附件時安全檢查只做了一半(算了過濾結果卻沒拿去用) | 目前打不到;未來若做批次刪除功能,會靜默刪錯檔案且不報錯 | ⚪ 低 | 6 |
另有 3 件(登入憑證金鑰、驗證碼金鑰、舊套件設定檔)併入既有的 CM-1607 一起清理,不另開卡。
現況:已修(CM-2049,commit 5d4c14221,1.21.0 出貨;原卡 CM-1629 作廢)。249 檔殘留字串已清、再寫進去的路已堵;密碼換發決策者裁排在正式環境上版前。
分三步,順序不能顛倒。
第一步:先堵住再發生的路(不做這步,清了還會再長出來)
| 做什麼 | 為什麼 |
|---|---|
改用 ~/.pgpass 或 PGPASSFILE 存密碼 |
psql 原生支援。指令裡就不必打密碼,對話紀錄自然錄不到 |
| 對話紀錄歸檔前先遮罩 | 在既有的歸檔流程加一步,把 PGPASSWORD='...' 換成 *** |
| 評估加自動秘密掃描 | 在提交前或 CI 擋下來。新增基礎設施要決策者裁,本卡只做評估回報 |
第二步:清掉已經寫進去的
.env」2026-05-28-poc-db-migration-plan.md)優先處理第三步:換密碼 ⚠️ 這步要決策者明確指示
做完怎麼驗:搜尋新密碼在版控中的出現次數應為 0(驗證時不要把密碼印在畫面或提交訊息裡);~/.pgpass 的用法要實際跑一次 migration 確認可用。
現況:已修(FR-114.1-7/CM-2030,1.21.0 出貨;原卡 CM-1630 作廢)。
分兩層改,兩層都要。
第一層:加上「你有沒有權限做這件事」的檢查
common/authz/),不要另外寫一套feedback.export 已經存在,要確認「讀、新增、修改、刪除」有沒有也定義了
第二層:加上「這筆資料是不是你的」的檢查
放在業務邏輯層(依專案規範,這種檢查不能放在最外層,因為要先把資料撈出來才知道要判誰):
@transaction
def delete_feedback(self, uid, login_user_name):
fb = self.feedback_issue_domain_service.get_by_uid(uid)
if fb is None:
raise NotFound(...)
if fb.created_user != login_user_name and not <有管理權限>:
raise ForbiddenError(...)
...還要決定一件事:列表功能應該是「只看自己的」還是「同公司都看得到」?這是產品決策,動手前先問決策者。
要寫測試(權限邏輯屬專案測試政策的例外,必須寫):把歸屬檢查那行拿掉,測試要變紅。
手動測試:用帳號 A 建一筆 → 用帳號 B 嘗試改/刪 → 應該被擋(403);帳號 A 改自己那筆 → 應該成功。
現況:已修(CM-2051,1.21.0 出貨;原卡 CM-1631 作廢)。15 檔殘留已清;金鑰重發與既有通行證重新加密另案處理。
⚠️ 重設金鑰要決策者明確指示。
| 金鑰 | 怎麼換 | 注意事項 |
|---|---|---|
| Google 應用程式密鑰 | 到 Google Cloud 後台重設,新值分發到各環境 | STG/POC 屬環境異動要放行。重設後檢查後台的授權紀錄有沒有異常活動 |
| 雲端硬碟加密金鑰 | 產新的(每個環境一把,不要共用) | 🔴 要先寫好「把既有權杖重新加密」的程式再換,否則客戶既有的雲端硬碟整合會全部失效。或者直接作廢,讓客戶重新授權 |
清理可以先做,不用等重設:22 個檔裡的金鑰換成「請查 .env」,文件改完重跑產生指令。
做完怎麼驗:在 DEV 環境實際跑一次 Google Drive 授權與同步流程。
現況:已修(CM-2066,1.21.0 出貨;原卡 CM-1632 作廢)。兩處 verify=False 已拿掉。
改動很小,但兩處要一起改。
- Github(auth=auth, per_page=100, timeout=10, verify=False)
+ Github(auth=auth, per_page=100, timeout=10)兩處:infra/github.py:22 和 github_issue_adapter.py:43。只改一處沒用,權杖還是會從另一邊漏出去。
如果真的有「自己架的 GitHub 企業版」需求(要先查證有沒有,不要假設):改成從設定讀憑證路徑,不要關掉檢查。設定要放哪裡照 CM-1560 的教訓——客戶會依自家環境調整的走設定頁,部署階段定死的才走環境變數。
順手做兩件:確認 GitLab 那側的預設行為(首腦已確認沒有同款問題,但預設值要查一次);gitlab.py 沒有設連線逾時,補上避免卡死。
要寫測試(加密/連線安全屬測試政策的例外):驗證建出來的連線物件其憑證檢查沒有被關掉。
現況:已修(版本鎖定檔入版控,1.21.0 出貨;原卡 CM-1634 作廢)。連線維持 http,決策者裁倉庫與打包機都在內網。
⚠️ 這條不是改程式碼,是基礎設施要先動,要決策者裁。
正解:
http:// 改成 https://——這步機械性,但要等 Nexus 那頭先就緒,否則全部裝不了套件過渡期的緩解(HTTPS 就緒前):
poetry update 當成特權操作,只在可信任的網段執行順便要確認的事:Nexus 上 jedi_issue 0.0.14/0.0.15 兩個舊套件檔是否已下架(那兩版把含密碼的設定檔打包進去了,是 CM-1573 沒收乾淨的尾巴。程式碼查不到,要連 Nexus 確認)。
現況:已修(CM-2066,1.21.0 出貨;原卡 CM-1633 作廢)。
改一行。
- file_is_deleted = self.file_upload_service.delete_files(file_uids)
+ file_is_deleted = self.file_upload_service.delete_files(list(matched_file_uids))順便看一下 GitLab/GitHub 兩個附件處理檔有沒有同樣問題。
要寫測試:傳入「一個合法編號 + 一個別張單的編號」,確認只有合法那個的檔案被刪掉。
三個理由疊在一起:那個密碼是活的、它指向的 POC 依規定等同正式環境、而且有一份文件把連線需要的五樣資訊湊在相鄰兩行——不需要任何拼湊就能直接使用。
這件事有一半是治本。 249 個檔的來源已經查清楚:九成是查資料庫時打的指令被寫進對話紀錄(112 個檔)與需求文件。不是有人故意寫的,是「工作過程被完整記錄」的副作用——而這個機制已經是第三次了(.env.test 事件、登入憑證金鑰、這次)。所以要先改工作方式,再清檔案。
前面幾件都需要特殊條件(要在內網做中間人、要讀得到程式碼倉庫)。這件只要有一個普通帳號就能做。
而且它是本專案第三次犯同樣的錯(CM-1585、CM-1589 已修完,FR-079 的公告功能後來也已修,FR-114.1-5a),修法可以直接抄。
登入憑證金鑰、資料庫密碼每套安裝都會各自產生一把新的,所以外流的只是我們自己環境那把。但 Google 的應用程式密鑰是在 Google 後台建的一組,所有環境共用同一把——客戶端不會各自產生。它的「外洩」比另外兩件更實在。
poetry update 當特權操作要分兩件事回答,不能籠統說「檢查過了」。
六次檢查的驗證投票全部完整跑完——這是本專案六個檢查案以來第一次做到:
| 次別 | 檢查什麼 | 檔數 | 提出問題 | 投票數 | 漏投 | 中斷 | 耗時 |
|---|---|---|---|---|---|---|---|
| I1 | 跟 GitHub/GitLab 連線的部分 | 25 | 5→3 | 9 | 0 | 0 | 47 分 |
| I2 | 主專案的意見回饋功能 | 31 | 13→10 | 30 | 0 | 0 | 136 分 |
| I3 | 附件上傳下載 | 14 | 4→3 | 9 | 0 | 0 | 91 分 |
| I4 | 對外 API 與插件裝配 | 20 | 4→4 | 12 | 0 | 0 | 95 分 |
| I5 | 業務邏輯層 | 40 | 7→5 | 15 | 0 | 0 | 87 分 |
| I6 | 資料存取層 | 28 | 3→3 | 9 | 0 | 0 | 105 分 |
怎麼讀這張表:工具會派三個獨立檢查員對每條發現各投一票。「投票數 30」的意思是「10 條發現 × 3 個檢查員,30 票全數投出」。漏投 0、中斷 0,代表每一條都被完整檢查過。
而且首腦沒有只採信工具:那條「高」的 249 個檔是自己數出來的(與報告吻合)、六個功能的權限檢查是自己打開檔案看的、六張表沒有客戶隔離是自己查資料庫結構查出來的。
六次都是快篩模式,不是徹底審查:沒有全面盤點、沒有威脅建模、沒有廣度掃蕩。
三個具體的缺口:
docs/、scripts/ 這兩個目錄從來不是檢查目標。結論:這六件事都是真的;但「這個套件只有這六個問題」這句話不成立。
之前的規矩是「一次不超過 30 個檔」,因為 FR-077 那次檢查 42 個檔時,105 個檢查員全部因額度用盡掛掉、21 張票一張都沒投出來。
這次 I5 故意放到 40 個檔,結果15 票全投、零漏投、零中斷、一輪跑完。
差別不在檔數,在有沒有和別的工作搶額度。 這次六次全部分開跑,一次都沒撞到額度,對照 FR-079 那次撞了兩次、跑了 14 小時、還踩到工具的併檔陷阱。
規矩可以改成:「40 個檔可行,前提是一次只跑一次。」這是決策者堅持「分開來跑」直接換到的。
套件的說明文件自稱「GitLab/GitHub 整合目前產品沒有在用,約佔套件 58%」——開卡前查證發現這是錯的(主專案有六個地方實際在呼叫)。
如果照說明文件跳過,就會漏掉整個第三方整合的部分,而那正是唯一會帶著密碼對外連線的地方。 六張卡都帶了這個警告。
I3 那條被三票否決的候選,首腦核對後發現檢查員的推理三點全對——但他們漏看了同一個函式裡第 92 行和第 99 行用了不同變數(算了過濾結果卻只用一半)。
所以:卡片點名要查的重點,就算被工具否決,首腦仍要自己看一眼。
現況:殘留字串已清(CM-2049,1.21.0 出貨);密碼換發決策者裁排在正式環境上版前。
依 FR-079 換來的判準查過安裝程式:每套安裝確實各自產生密碼,客戶端是安全的。
但這條不能因此降級——這把密碼同時用在 DEV、出貨基線庫、以及我們自己的 STG/POC。POC 依規定是「等同正式環境、對外 demo」,基線庫是出貨映像檔的來源。這與登入憑證金鑰那條「只影響開發機」不同。
若要換:DEV 與基線庫可以規劃;STG/POC 屬環境異動,依鐵律要您當次明示才可以動。 另建議把 Redis 與資料庫密碼分成兩組(目前是同一組)。
現況:檔案殘留已清(CM-2051,1.21.0 出貨);Google 後台重發與既有通行證重新加密另案處理。
這把跟前面幾條不同——不是每套安裝各自產生的,是 Google 後台的一組、所有環境共用。轉了要重新發到各環境。
另外加密金鑰若要換,得先寫好「把既有權杖重新加密」的程式,否則客戶既有的雲端硬碟整合會全部失效。
這是 CM-1573 沒收乾淨的尾巴——那兩版把含密碼的設定檔打包進去發布了。程式碼查不到,要連 Nexus 才能確認。
| 報告 | 檢查什麼 | 範圍內新問題 |
|---|---|---|
| I1 跟 GitHub/GitLab 連線的部分 | 25 檔 | 2(連線沒檢查身分) |
| I2 主專案的意見回饋功能 | 31 檔 | 5(含唯一的「高」) |
| I3 附件上傳下載 | 14 檔 | 0(工具)+1(首腦補查) |
| I4 對外 API 與插件裝配 | 20 檔 | 0(兩項查證首腦補做) |
| I5 業務邏輯層 | 40 檔 | 1(套件倉庫沒加密,全新) |
| I6 資料存取層 | 28 檔 | 0(工具)+1(首腦查出六張表沒隔離) |