本卡屬 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=False、verify_mode=CERT_NONE。不會拋任何例外,應用層完全察覺不到。
① 密碼進 log:
logger.error("Error sending email via %s:%s as %s", self.config.smtp_server, self.config.port, self.config.username)SmtpEmailConfigDTO.password 改宣告為 SecretStr,pydantic 會在任何地方都渲染成遮罩,不只這兩個呼叫點。取值時用 .get_secret_value(),只在 server.login() 那一行。注意這會影響所有建構 DTO 的呼叫端(主專案有三處:test_mail_service.py、notification_service.py、notify_config_test_service.py),要一起確認。② starttls 不驗憑證:
server.starttls(context=ssl.create_default_context())——預設就是 check_hostname=True、verify_mode=CERT_REQUIRED。.env 環境變數,判準是「客戶會依自己伺服器狀況調整」)。所以在 SMTP 設定頁加 verify_cert(預設開)與 ca_cert_pem 兩個欄位,與 host/port 擺在一起。先讀 CM-1560 的 commit(jedi-iam 20c6713、BE f503fba3、FE 78903b2)照同一 pattern 做。屬「核心共用邏輯」例外,要寫: