資安掃描進度追蹤

這份文件給工程師頭頭掌握現況、跟 PM 會報用。本文是 2026-09-06 晚間快照,實況以 handoff/security-scan-STATE.md 為準。

2026-10-01 現況:本檔下列「修正中/盤點中/暫緩/待重跑」皆已過時——S6、S7 已重跑完成;CM-1579 已修(方案 A);CM-1559 已修(FR-094 CM-1788,1.21.0 出貨);CM-1582 裁定記錄不修(總表 M18-7,交付首位客戶前再處理);其餘修正卡 1.21.0 出貨(早期卡 09-21 作廢,逐條狀態見 docs/security-report/SUMMARY.md)。

§1

給 PM 的摘要

我們用 Claude Code 官方的 claude-security plugin(一種 AI 深度審查工具,會派出多個子代理去讀程式碼、互相驗證彼此的發現)掃了三大塊:

  1. jedi-iam——身分認證套件,管全產品的登入、密碼、MFA、租戶隔離
  2. jedi-license-runtime——授權驗證,裝在產品裡負責「這張授權碼是不是真的」
  3. license_center——授權簽發站,獨立系統,私鑰放在這裡,負責「發一張授權碼給客戶」

這個工具的價值是補上 Sonar/Semgrep 這類靜態掃描工具看不到的東西——它們抓得到「這段程式碼有 SQL injection」,但抓不到「這支 API 的業務邏輯設計錯了,讓任何人都能接管別人的帳號」。這次找到的最嚴重問題(忘記密碼洩漏憑證)就是後者。

掃到哪:共規劃 11 棒,9 棒完成、S6 換大記憶體模型重跑中、S7 決策者裁定排今晚深夜重跑。 找到多少:累計 24 張問題卡(原 21 張+S5 新回報衍生 2 張+CM-1572③ 衍生 1 張全站上傳限制),其中 1 張最高等級(CRITICAL)、8 張高風險(HIGH)、13 張中風險(MEDIUM)、1 張低風險(LOW)、1 張機械修正。 修到哪:21 張已修完並驗收通過、1 張正在修(CM-1579,決策者已裁定現在做)、1 張正在盤點(CM-1559,修法待盤點結果與 S6/S7 掃完後裁定)、1 張暫緩(CM-1582)。排隊 0,序列組 A/B 全部做完。


§2

一、掃描進度

jedi-iam(FR-075,共 7 棒)

棒 範圍 檔數 狀態 發現數 開卡數
S1 認證與外部身分綁定 11 ✅ 完成已驗 8(5 HIGH/3 MEDIUM) 5
S2 授權守門與提權路徑 8 ✅ 完成已驗 0 首腦另加開 1 張(CM-1559)
S3 第二因子(MFA)與人機驗證 34 ✅ 完成已驗 2(1 HIGH/1 MEDIUM) 2
S4 使用者本體與密碼變更 43 ✅ 完成已驗 5(2 CRITICAL/1 HIGH/2 MEDIUM) 4
S5 角色與權限能力 49 ✅ 完成已驗 2(2 MEDIUM)→ 開 CM-1585/1586 2
S6 租戶與組織單位 47 ✅ 重跑完成(原寫「Opus 重跑中」) 1(MEDIUM) CM-1588/1589(已修)
S7 登入態、UI 路由、middleware 74 ✅ 已收窄 29 檔重跑完成(原寫「排今晚深夜重跑」) 首輪跑完但 8/9 條是跟 S1 重複,核心範圍其實沒真的掃到 1(Redis TLS,CM-1565)

S6、S7 為什麼要重跑:這兩棒和 S4、S5 一樣,因為要看的檔案數超過 40 支,掃描工具用的模型「記性」(context 容量)不夠大,讀到一半就被系統判定當機、砍掉重來,最後什麼結果都拿不到(S6),或是讀到別的地方去、把已經抓過的問題當新發現重報一次(S7)。解法是換一顆記性更大的模型,S4、S5 用這個方法都成功了,S6 目前正用這個方法重跑,S7 由決策者裁定排到今晚深夜再跑。

License 產品端(FR-076,L1-L2)

棒 範圍 repo 檔數 狀態 發現數 開卡數
L1 License 驗章核心(密碼學層) jedi-license-runtime 10 ✅ 完成已驗 1 HIGH(範圍內) 1(另有 1 張越界處置卡)
L2 License 授權狀態與 API jedi-license-runtime 33 ✅ 完成已驗 1 LOW(範圍內)+人工復核多發現 1 HIGH 2

License Center 簽發站(FR-076,L3-L4)

棒 範圍 repo 檔數 狀態 發現數 開卡數
L3 簽發核心與模組 license_center 32 ✅ 完成已驗 2 條範圍內(併入 CM-1583)+3 條與 L4 重複(交叉驗證,不重開) 併卡處理
L4 網頁後台 license_center 43 ✅ 完成已驗 4 MEDIUM 2

11 棒中 9 棒完成、2 棒待重跑(S6、S7)。


§3

二、問題與修復狀況

兩個系統分開列表,各一張表、共用同樣欄位。處理狀態欄取代原本的分組,四種值: ✅ 已修完/🔵 修正中/🔍 盤點中(附卡住原因)/⏸ 暫緩(附理由)。

jedi-iam 問題清單(FR-075,16 張)

卡號 嚴重度 來源棒 問題是什麼 怎麼修 處理狀態
CM-1575 🔴 CRITICAL S4 使用者按「忘記密碼」時,系統把重設用的一次性憑證直接放進 API 回應裡吐回去——原本這個憑證應該只出現在寄給本人的信裡。攻擊者只要知道對方的 email,兩步就能改掉任何人(含系統管理員)的密碼 成功與失敗都回傳固定的空內容,讓憑證不再外洩,同時讓「信箱存在/不存在」也真正無法區分 ✅ 已修完(決策者裁不做 nginx 臨時節流,與 CM-1583、LC 端修正一起排上版)
CM-1557 HIGH S3 MFA(第二重驗證碼)沒有次數限制,密碼被偷的人還能靠猜驗證碼在時限內硬猜出正確答案 加上每人失敗次數限制,5 次即鎖 ✅ 已修完(email 碼失效/TOTP 15 分鐘窗,沿用既有鎖定時間設定)
CM-1560 HIGH S1 LDAP(企業內部帳號系統)登入有三個洞:非 AD 模式下從不驗證密碼、搜尋條件可被操控、連線不驗證憑證真偽 三個洞一起補:強制驗密碼、過濾特殊字元、開啟憑證驗證 ✅ 已修完(TLS 項一度退回重做——原做成 .env 變數,決策者裁不符產品形狀,改成設定頁「驗證伺服器憑證」開關+「自訂 CA 憑證」欄位,對齊 GitLab/Grafana 慣例)
CM-1558 MEDIUM S3 人機驗證(防機器人)功能如果忘記設定金鑰,系統會悄悄改用「永遠通過」的測試金鑰,沒有任何警示 改成直接報錯,測試金鑰只在明確標示的開發模式下才給 ✅ 已修完(沿用既有 DEBUG 旗標,不新開變數)
CM-1563 MEDIUM S1 登入失敗時,「帳號不存在」跟「密碼錯誤」回應不一樣,攻擊者可以拿名單逐一測試篩出哪些帳號真的存在 兩種情況統一回一樣的錯誤訊息與回應時間 ✅ 已修完(帳號不存在時也跑一次假的密碼比對墊時間,堵掉時間差破綻)
CM-1565 MEDIUM S7 連接 Redis(快取/驗證碼暫存)的程式碼把「驗證伺服器憑證是否為真」寫死關閉,就算設定裡開啟加密連線,也擋不住有心人偽裝成 Redis 攔截資料 開啟加密連線時強制要求驗證憑證 ✅ 已修完(新增選填的 CA 憑證路徑設定;DEV 環境沒有 TLS 版 Redis,只跑到單元測試)
CM-1562 HIGH S1 把外部帳號(如 AD)綁到自己帳號的功能,沒有檢查「你是不是本人」,任何登入者都能把自己的外部帳號綁去別人(含管理員)身上,接管對方 加上本人身分檢查 ✅ 已修完(守門放在 LDAP 驗證之前)
CM-1561 HIGH S1 Google 登入功能其實從未真正做完,是個空殼,任何人都能透過它偽造身分登入 原規劃直接拔除,決策者後來改裁:不拔除、改成關閉 ✅ 已修完(登入與綁定白名單擋掉 google,程式碼保留並加註解;FE 本來就沒有這個入口)
CM-1564 MEDIUM S1 LDAP 設定頁的「測試連線」功能會沿用已存的密碼去測,但連線位址是使用者自己填的,可以把位址改成自己的機器,藉此偷到系統真正的服務帳密 改位址時強制要求重新輸入密碼 ✅ 已修完(沒改位址仍沿用已存密碼,不影響正常使用)
CM-1576 HIGH S4 使用者修改自己個人資料的功能沒有限制能改哪些欄位,理論上可以把自己升級成系統管理員 改成只允許修改指定的欄位,角色等敏感欄位不接受自助修改 ✅ 已修完(runner 先實測攻擊確實成立——一般帳號真的能把自己改成 admin——才動手修)
CM-1577 MEDIUM S4 有兩條可以改密碼的路徑沒有檢查密碼強度規則,可以設成 1234 這種弱密碼 補上密碼強度檢查,與忘記密碼共用同一份強度政策 ✅ 已修完(批次匯入使用者只驗使用者手填的密碼欄位)
CM-1578 MEDIUM S4 匯入使用者名單用 Excel 上傳時,暫存檔案的資料夾名稱直接用上傳者帳號拼出來,理論上帳號名稱帶特殊符號可以讓檔案跑到預期以外的資料夾(實際觸發門檻高,目前環境沒有踩到) 補上格式驗證、改用固定的內部代號組路徑 ✅ 已修完(帳號建立時就用格式檢查擋在源頭,上傳目錄再加一層檔名淨化當縱深防護)
CM-1585 MEDIUM S5 角色清單、單筆角色、角色選單這些「讀」的端點完全沒有權限檢查,任何登入者都能拿到整份「誰是管理員、誰有哪些能力」的對照表,還附帶每個角色成員的 email、電話、職稱 三支讀端點補上讀取用的權限檢查點,列表回應不再附帶成員名單、只留人數 ✅ 已修完(查過既有 seed 資料不需要補)
CM-1586 MEDIUM S5 系統在計算「這個人有哪些權限」時,把已停用、已刪除、已過期、甚至別的租戶的角色權限也一起算了進去。管理員停用某個角色,畫面上看不到了,但 API 權限沒收回 補上過濾條件,API 端與側邊選單改用同一套「有效角色」判讀邏輯(是否在有效期內、是否同租戶、是否停用/已刪除) ✅ 已修完(DEV 環境目前 0 筆資料受這條規則影響)
CM-1587 MEDIUM CM-1572③ 盤點衍生 主產品整站沒有請求大小上限,任何登入者可以送一個超大請求把伺服器記憶體塞爆 全站請求本體上限設 50MB,環境變數 MAX_REQUEST_BODY_MB 可調,超過回 413 並給明確錯誤訊息 ✅ 已修完(已補登進凍結 error code 基準表)
CM-1559 MEDIUM(影響面最廣) 首腦復核加開 資料庫的租戶隔離機制在「沒有登入身分」或「租戶編號填 0」時,會直接給予最高權限,等於隔離形同虛設 補上更嚴謹的判定,去除這兩個會被利用的漏洞 ✅ 已修(原寫「盤點中」;FR-094 CM-1788 無身分改 fail-closed,總表 M04-2,1.21.0 出貨)

License 問題清單(FR-076,8 張)

卡號 嚴重度 來源棒 問題是什麼 怎麼修 處理狀態
CM-1572 HIGH L1 驗證授權碼簽章前,先把裡面的壓縮內容全部解開,攻擊者送一個「壓縮得極小、解開變超大」的假檔案,能讓後端把記憶體塞爆、整個服務打掛,且不需要登入 解壓縮設上限、先檢查長度再解,另外兩個套件裡的同款寫法也一併修掉 ✅ 已修完(③ 全站上傳大小上限已由 CM-1587 定案 50MB)
CM-1580 LOW L2 兩個客戶同時上傳同一張授權碼時,因為檢查跟寫入不是同一個瞬間完成,理論上兩邊都能通過檢查、都寫進去,造成一張碼被兩個客戶共用 在資料庫加一條規則,同一張碼在「目前生效」的狀態下只能存在一筆 ✅ 已修完(開發環境已建好這條規則,正式與展示環境待排程套用)
CM-1581 機械修正 首腦復核發現 出貨打包腳本裡有一道「檢查有沒有帶到正式簽發私鑰」的安全把關,但它檢查的檔案路徑早就搬家了,導致這道把關現在形同虛設 改成用程式動態找到正確路徑 ✅ 已修完
CM-1584 MEDIUM L4 簽發站登入頁的網址可以帶一個「登入完要導去哪」的參數,程式完全沒檢查就照導。攻擊者可以做一個看起來像真站的假登入頁連結,受害者登入成功後被導去假頁面,密碼因此被騙走 檢查這個導轉網址,只允許導回本站內部頁面 ✅ 已修完
CM-1573 MEDIUM L1 越界發現 另一個內部套件(jedi-issue)曾經把一份含有 GitHub/GitLab 存取密鑰的設定檔打包發布出去。原始碼雖然已經刪除,但已發布的舊版安裝包裡那份密鑰檔案還在 確認密鑰已作廢(決策者裁定跳過另撤銷)、清舊版安裝包、清本地殘留檔案、修改打包規則避免再次發生 ✅ 已修完
CM-1583 MEDIUM→HIGH L3+L4 簽發站的「開通授權碼」端點沒有登入保護(設計如此,因為開通前客戶還沒有帳號),但防止亂猜授權碼的機制形同虛設——鎖定條件用錯了 key,導致攻擊者怎麼猜都不會被鎖;而且記錄猜測次數的地方沒有上限、還會把授權碼整串印進系統日誌 比照同系統後台登入早就做對的方式:鎖定記錄改存資料庫、按來源 IP 計數、日誌印遮蔽過的內容、拉高授權碼的複雜度(七項合併修,含補發語意改採「產新序號、舊碼作廢」) ✅ 已修完(DEV 環境 alembic 已套到最新;STG/POC 待排上版)
CM-1579 HIGH(僅影響多租戶/SaaS 模式) L2 人工發現 系統管理員停用某個租戶後,該租戶只要自己上傳一張舊的授權碼,停用狀態就會被自動解除,不需要管理員介入 決策者改裁:不等到有 SaaS 客戶才修,現在就做,方案 A(停權語意從照移到租戶層) ✅ 已修(原寫「修正中」;方案 A,總表 M18-1)
CM-1582 待正式簽發環境建立才能定嚴重度 L1 三環境硬編公鑰議題 系統目前同時認得開發、測試、展示三種環境的驗證金鑰,理論上用開發環境的私鑰簽的授權碼,正式出貨的產品也會認 出包按環境只編對應公鑰、客戶包不含 DEV 鑰 📋 裁定記錄不修(原寫「暫緩」;總表 M18-7,交付首位客戶前改成只認正式簽發站公鑰)

§4

三、嚴重度總覽

嚴重度 總張數 已修完 修正中 盤點中 暫緩
🔴 CRITICAL 1 1(CM-1575) 0 0 0
🟠 HIGH 8 7(CM-1572/1557/1560/1562/1561/1576/1583) 1(CM-1579) 0 0
🟡 MEDIUM 13 11(CM-1573/1584/1558/1563/1565/1564/1577/1578/1585/1586/1587) 0 1(CM-1559) 1(CM-1582)
🟢 LOW 1 1(CM-1580) 0 0 0
機械修正 1 1(CM-1581) 0 0 0
合計 24 21 1 1 1

CM-1583 卡片嚴重度為 MEDIUM→HIGH(log 洩憑證追加後升級),此表計入 HIGH 列。 現況(2026-10-01):表中「修正中/盤點中」兩欄的 CM-1579、CM-1559 均已修;「暫緩」的 CM-1582 裁定記錄不修。


§5

四、為什麼不一次修完

原本三條排隊規則(先修 FR-076 再動 jedi-iam/改同一支程式碼的卡片依序做/動核心連線邏輯的卡等掃描完),前兩條已隨修正完成解除——序列組 A(LDAP 四張)與序列組 B(使用者資料三張)全部做完。

目前只剩 CM-1559 受第三條限制。而且盤點後發現它不是單一修正,是「全站資料庫連線身分判斷」的重構等級改動,已改為等決策者裁方向(見七)。


§6

五、下一步

  1. 等 S6 掃描回報(Opus 重跑中)
  2. 等 S7 今晚深夜重跑回報
  3. 決策者裁 CM-1559 修法方向(A/B/C,見七)
  4. 等 CM-1579 驗收回報(方案 A,修正中)
  5. CM-1559 盤點順帶發現三件,建議各開一張 followup:DEV 與 STG 的表擁有者不一致(讓 DEV 測不出 RLS 問題)/projects 等三張表有 policy 但 RLS 未啟用(打開會直接壞)/users.tenant_id 可為 NULL(NULL 會靜默變成最高權限)
  6. 全部修正卡收尾後,安排端到端回歸測試
  7. 依「上版待辦」安排主產品與 License Center 一起上版

§7

六、上版待辦

修正都在各自 repo 完成,尚未推上 STG/POC:

  • jedi-iam 套件:累計 10 張修正(CM-1575/1557/1558/1560/1562/1563/1565/1576/1577/1578;CM-1561 也動了此套件)需發版 → 主專案 pin 新版 → 出 image → 套 STG → 套 POC
  • 主專案自身改動:CM-1585(FE)/1586(DI)/1587(全站上傳上限)/1564(LDAP 設定)隨同一顆 image 帶上
  • License Center:CM-1583、CM-1584 走既定四步部署(pull → 裝依賴 → alembic 遷移 PYTHONPATH=src → 重啟),2 支 alembic migration 要在 STG/POC 套用
  • CM-1580 migration:目前只在 DEV 建好,STG/POC 待補套
  • 上 STG 後留意兩個行為變化:
    • CM-1565(Redis TLS 驗憑證)——DEV 沒有 TLS 版 Redis,只跑到單元測試;STG/POC 若走加密連線要實際驗證
    • CM-1560(LDAP 憑證驗證)——上版後預設開啟「驗證伺服器憑證」,若 STG/POC 的 LDAP 伺服器是自簽憑證,要在設定頁貼 CA 或取消勾選

§8

七、待決策者裁定的事項

待裁(2 項,09-06 當時;現況:CM-1559 已修〔FR-094 CM-1788〕,jedi-iam 修正已隨 1.21.0 出貨)

  1. CM-1559 修法方向——盤點已回報(卡片有完整清單)。結論:X-Tenant-ID: 0 那條攻擊路徑已被 FR-069.16 擋死,本卡降為「縱深防禦少一層」;但直接翻成 fail-closed 會炸全站認證(JWT 解析、登入、忘記密碼、agent 心跳、Drive webhook 都靠無 context 讀 RLS 表才跑得動),且壞法是靜默不報錯。三個選項:
    • A 全做:重構 session 進入點,三類分流接線,回歸驗證必須在 STG(DEV 會假綠)
    • B 只下第二刀:先拿掉 not tenant_id 那一行(爆炸面只有 signed_token 一處)+ fail-closed 分支加 WARNING log,第一刀另開 arc 綁 SaaS 前
    • C 記錄不修:寫進 followup 綁 SaaS 上線前
    • 首腦建議 B:落地版單租戶,RLS 縱深的實際風險低;第一刀成本與風險都不該塞進這個修正批次
  2. 上版時機——見六

已裁(不重問)

  • CM-1575 進版前要不要在 STG/POC nginx 加臨時節流(不加,等一起上版)
  • CM-1572③ 全站上傳大小上限設多大(50MB,環境變數可調,衍生 CM-1587)
  • CM-1583⑦ 補發語意(B 案:產新序號、舊碼作廢)
  • CM-1561 Google 登入拔除還是關閉(關閉不拔除)
  • CM-1579 等 SaaS 還是現在做(現在做,方案 A)
  • CM-1582 客戶包公鑰白名單(維持暫緩,綁正式 License Center)
  • CM-1559 直接修還是先盤點(先盤點,已回報)
  • S7 要不要重跑(重跑,排今晚深夜)

本文是 2026-09-06 晚間快照,實況以 handoff/security-scan-STATE.md 為準。