本卡屬 FR-078(母卡 CM-1602),修 N1(CM-1603)的兩條發現。面板完整跑完,兩條都 3/3 全票通過。 兩條同檔同性質(都是「密碼在傳輸或落地時沒被保護」),併一張卡。

問題是什麼(白話)

① 寄信失敗時,SMTP 密碼會被完整寫進系統 log。 那份 log 不只落在主機檔案,還會寫進資料庫的 system_logs 表,若客戶開了 log 轉發,還會被送到客戶自己的 log 收集器(可能在信任邊界之外)。能讀 log 的人遠多於能改 SMTP 設定的人。

② 郵件伺服器的加密連線不驗憑證。 設定裡勾了 TLS,但實際建立連線時沒有驗證對方是誰——有加密沒有認證。網路路徑上的攻擊者可以冒充郵件伺服器,拿到 SMTP 帳密,以及信件全文。這條特別要緊,因為登入 OTP 與新帳號的初始密碼信都走這條路。

這是本 codebase 第三次犯同一類病:LDAP TLS(CM-1560)、Redis TLS(CM-1565)都已修過。修法可直接參照。

首腦核對過的證據

首腦已開檔確認,且比對過已部署的 jedi-notification==0.0.11,兩條在部署版逐字相同、行號一致(不是只存在於未發版的 HEAD):

jedi-notification/jedi_notification/infra/smtp_mail/smtp_mail_adapter.py:52
  logger.error(f"Timeout sending email smtp info: {self.config}")
jedi-notification/jedi_notification/infra/smtp_mail/smtp_mail_adapter.py:57
  logger.error(f"Error sending email smtp info: {self.config}")
  ↑ self.config 是 SmtpEmailConfigDTO,password 欄位是裸的 Optional[str]

jedi-notification/jedi_notification/infra/smtp_mail/smtp_mail_adapter.py:43
  server.starttls()          ← 沒帶 context
jedi-notification/jedi_notification/infra/smtp_mail/smtp_mail_adapter.py:47
  server.login(user=..., password=...)   ← 下一行就送帳密

jedi-notification/jedi_notification/app/dto/smtp_email_config_dto.py:13
  password: Optional[str] = None   ← 沒有 SecretStr

同檔第 17 行的成功路徑本來就是逐欄印的(只印 server/port/from/user),只有兩條錯誤路徑整包內插。也就是說正確做法在同一支檔案裡就有現成範例。

研究員查證了 CPython 的行為:smtplib.starttls() 不帶 context 時會走 ssl._create_stdlib_context(),那是 _create_unverified_context 的別名,check_hostname=Falseverify_mode=CERT_NONE不會拋任何例外,應用層完全察覺不到。

怎麼修

① 密碼進 log

② starttls 不驗憑證

要寫測試

屬「核心共用邏輯」例外,要寫