本卡屬 FR-114 資安修正(母卡 CM-2019),b4 總測回報收集卡 CM-2231 #35(V4 驗證 CM-2267 #95 抓到)與 #37(決策者 09-27 手測撞到)。兩件都在 jedi-iam 套件、都是小改,併一張。

問題是什麼(白話)

件一(#37,中):登入時系統會產一組新的 email 驗證碼,而「產碼」與「重新發送」共用同一個 60 秒冷卻。結果同一帳號 60 秒內第二次登入直接被拒(HTTP 429『驗證碼寄送過於頻繁』),連第一關帳密都不讓過。最常撞的流程是「新帳號首次登入 → 強制改密碼 → 必須重登」:改完密碼馬上登,離上次登入不到 60 秒,必撞。190 log 今晚 11 次 429 全在 POST /login,其中 21:25:13 登入→21:25:15 改密碼成功→21:25:27 重登 429(retry_after=33s)。CM-1557 之後所有版本都有,不是 b4 回歸,但客戶第一天裝機就會撞。

件二(#35,高):AI 儀表板回應端已遮掉密碼雜湊等欄位,但 jedi-iam UserService.get_users() 的 INFO log 直接 f"Get users: {users}" 把整個 entity 清單 repr 進 log——190 api 容器 stdout 一行就是六個帳號的 password=(bcrypt 雜湊)、salt=、is_super_admin= 原值。同型寫法在套件內共八處(見在哪裡),roles 那處 STATE follow-up 早就記過「api log 印整個 RoleEntity 含 bcrypt hash」。拿得到 log 的人等於拿到全站密碼雜湊。

工作區

在哪裡

件一
jedi_iam/mfa/app/service/email_service.py:80-106   gen_mfa_code()::88 cooldown_key、:90-92 有 ttl 就拋 429、:97-98 存新碼(300 秒)、:104-105 起 60 秒冷卻。docstring :83-86 明寫「登入首發與重送共用此路徑」——就是這句造成的
jedi_iam/mfa/app/service/email_service.py:108-120  send_otp()::120 呼叫 gen_mfa_code
jedi_iam/api/routes/otp_route.py:148-160           重新發送端點 OtpCodeResendRoute → :160 send_otp(這條才該吃冷卻)
宿主 core/plugins/identity.py:161(BE 工作區)        登入首發 dispatcher → send_otp(這條不該被冷卻擋登入)
jedi_iam/app/service/login_orchestrator.py:175      outcome.mfa_type = self._mfa.dispatch(user_uid)(首發入口)

件二(八處,全部改)
jedi_iam/app/service/user_service.py:98    logger.info(f"Get users: {users}")         ← 190 抓到的就是這行
jedi_iam/app/service/user_service.py:414   logger.info(f"Get users: {users}")(menu)
jedi_iam/app/service/role_service.py:54    logger.info(f"Get roles: {roles}")
jedi_iam/app/service/role_service.py:108   logger.info(f"Get roles(menu): {roles}")
jedi_iam/app/service/tenant_service.py:54  / :141
jedi_iam/app/service/org_unit_service.py:56 / :121
jedi_iam/app/service/capability_service.py:55
jedi_iam/app/service/ui_route_service.py:51

怎麼修

要寫測試

件一屬登入核心,要寫。既有 tests/test_otp_verify_token_identity.py、tests/test_mfa_captcha_merge.py 看它們怎麼 mock RedisClient,同風格加 tests/test_otp_first_send_no_cooldown.py:① 首發兩次(間隔 <60s、舊碼未過期)第二次不拋、回同一碼;② 重送在冷卻內拋 OtpResendTooFrequentError;③ 首發但舊碼已過期 → 產新碼。突變:把首發改回走冷卻判斷,①要紅。件二用 caplog 斷言 get_users 的 log 不含 password=/salt=(突變:改回印 entity 要紅)。跑法 cd /Users/chouraymond/Projects/Jedicogy/module/jedi-python-package/.claude/worktrees/jedi-wt-fix-security/jedi-iam && PYTHONPATH=. python -m pytest tests -q。

手測

連帶