本卡屬 FR-114 資安修正(母卡 CM-2019),第 3 批「刪除沒人用的」卡 3-1,修 SUMMARY M13-10(出自 M13-iam)。中。修法規格來自內化卡 CM-1565,計畫在 docs/features/FR-114-2609-security-fix-dispatch/batches/plan-b3.md「卡 N-1」段。
問題是什麼(白話)
系統連「快取伺服器」(Redis,暫存登入狀態、驗證碼的那一台)時,同一段連線程式主專案自己放了一份複製品,跟套件裡的正版不一樣。套件那份已經修好——加密連線一定會確認對方真的是那台機器;主專案自己這份複製品沒修,問卷與 AI 助手兩個功能正在用它。現在加密沒開所以還沒被實際利用,但只要開起來,這兩條路就不驗身分,同網路的人可以冒充快取伺服器、攔截登入狀態與驗證碼。
首腦核對:
- 主專案自己的複製品:common/util/redis_client_util.py(80 行,class RedisClient),問題在 :32 的 ssl_cert_reqs=False(不驗證憑證)
- 套件已修好的正版:jedi-iam/jedi_iam/common/utils/redis_client_util.py 的 RedisClient,:42-47 已改為 ssl=True 時強制 ssl_cert_reqs="required" + ssl_check_hostname=True,並支援 REDIS_SSL_CA_CERTS
- 套件有回歸測試:jedi-iam/tests/unittest/test_redis_client_util_tls.py:43
- 呼叫主專案那份壞複製品的只有兩處(grep 驗證過,除此之外的 hit 都是文件/測試字串,不是真呼叫):infra/survey/adapters.py:29-31、core/plugins/ai_bot.py:37-39
工作區
- BE 側:在 /Users/chouraymond/Projects/Billows/Audit-Manager/compliance-manager-be/.claude/worktrees/wt-fix-security(branch fix/security-b1)改,不要碰主 checkout(掃描線正在讀它)。跑服務用 PORT=8001。
- 跨 repo:BE 工作區 pyproject.toml 把 jedi-iam 的 pin 改成 path dependency 指向套件工作區 /Users/chouraymond/Projects/Jedicogy/module/jedi-python-package/.claude/worktrees/jedi-wt-fix-security/jedi-iam,develop = true,跑 poetry update jedi-iam。這個 path 改動不 commit(git add 時跳過 pyproject.toml)。
在哪裡
common/util/redis_client_util.py 整支刪除(80 行,class RedisClient,:32 ssl_cert_reqs=False 是壞的那一段)
infra/survey/adapters.py:29-31 lazy import + :31 return RedisClient() → 改 import 套件版
core/plugins/ai_bot.py:37-39 lazy import + :39 self._redis = RedisClient() → 改 import 套件版
jedi-iam/jedi_iam/common/utils/redis_client_util.py:42-47 套件已修好的正版(參考用,不改)
common/iam_ports.py:32 ISettingsReader 的 EnvSettingsReader 實作(設定來源,見下方陷阱)
怎麼修
- ① 刪除 common/util/redis_client_util.py 整支檔案。
- ② infra/survey/adapters.py:29-31、core/plugins/ai_bot.py:37-39 兩處 import 改成從 jedi_iam.common.utils.redis_client_util import RedisClient(或套件實際 export 路徑,先查套件 init.py 確認正確 import 路徑)。
- ③ 🔴 先查再寫,這裡有一個容易讓功能安靜壞掉的陷阱:主專案舊版直接用 ConfigUtils().config 讀 REDIS_HOST / REDIS_SECRET(當 REDIS_SECRET 是純字串),套件版改用 jedi_iam.common.settings.get_setting()(ISettingsReader port)且把 REDIS_SECRET 當 JSON 解析(取 redis_user_name / redis_user_password 兩個 key)。若主專案的 ISettingsReader 實作(common/iam_ports.py:32 的 EnvSettingsReader)沒有把 REDIS_SECRET 正確餵成 JSON 格式,套件版的 connect() 會在 except 區塊只 logger.error、不拋例外——連線悄悄失敗,功能看起來正常但其實沒連上快取。動手前先讀 common/iam_ports.py:32 確認格式一致,並用 test/test_iam_wiring.py:78-84 對照。
- ④ 套件版方法名與主專案版完全對應(set / get / lpop_key / rpop_key / lpush / rpush / lrem / get_all_from_list / get_last_one_from_list / delete_key / ping),呼叫端不需改方法名,只需改 import 來源;套件版另外多了 ttl() / incr(),不用管。
手測
- DEV:跑一次問卷功能完整流程(含驗證碼流程)→ 期待驗證碼寄送、驗證都正常,不因換了 RedisClient 而失敗